From d8621177ed00f47aa4dd54a2a4c95464a932abd6 Mon Sep 17 00:00:00 2001 From: Olivia Di Matteo <2068515+glassnotes@users.noreply.github.com> Date: Sat, 24 Aug 2024 14:44:44 -0700 Subject: [PATCH] Release 0.3 (#22) * Update tests to conform with new qml.matrix argument requirements. * Create python-package.yml * Update python-package.yml * Update python-package.yml * Update python-package.yml * Update python-package.yml * Auto-format with black * Update python-package.yml * Start linting * Add pylintrc file * Continue linting files. Reorganize identity hunter. * Add linting to workflow * Update linting workflow * Add new linted files * Finish linting * Disable pylint on import order * Disable pylint wrong import order * Fix error * Update docstring * Update black to only check. * Try caching * Fix spacing * Pin PennyLane version * Break formatting to test CI * Fix formatting * Added pyproject.toml so that black lines 100 is set as default. * Pin pylint versions and fix issues in identity hunter * Add missing version * Track dependencies with Poetry * Try running CI with poetry build * Update lock file and pyproject.toml * Fix yaml file * convert tabs to spaces :( * add pre-commit to dependencies * remove unnecessary requirements files * Add pre-commit hooks. Convert CI to use ruff instead of pylint and black * Delete unused config files * Update preconfig * Update PennyLane version and Poetry lock file * Add documentation with readthedocs (#23) * Add readthedocs yaml file * Add rst files for sphinx documentation. Adjust some docstrings to conform. * Restructuring documentation files so web-based docs can be built. * Rename docs directory * Fix path in conf.py file * Update readthedocs yaml to use poetry * Update poetry lock file * Revert change to python version. * Add MS gate matrix example. Convert all docstrings to raw strings. * Restructure docs files. Add instructions and basic usage. * Update lock file * Fix math rendering in many docs * Clarify docs for identity hunter. * Fix formatting in docs * Fix formatting in docs * Formatting fix * Example for commute_through_ms_gates * Examples for virtualize_rz_gates and single_qubit_fusion_gpi * Update convert_to_gpi and add example * Add inverse cancellation pass * Example for ionize transform * Hide more contents of identity hunter module. Small text tweaks. * Example for lookup gate identity * Finish documenting utils. Update a JSON parameter based on new API. * Fix typo * Remove transform utils from docs. Fix tests for new API * Restructure identity hunter module * Run precommit hooks * Try enabling source linking * Remove attempted source link --------- Co-authored-by: Olivia Di Matteo * Add equivalence checking mechanism to transforms and tests (#24) * Add equivalence checking mechanism to transforms and tests * Relocate equivalence checker. Add a test suite for it. * Add description of equivalence checking to docs. * Push changes to basic usage section. * Remove print statements from tests * Glassnotes/21 update readme for release 03 (#25) * Example for flag_non_equivalence utility function * Update README to reflect new instructions * Fix formatting of links in README * Add docs badge to README * Remove broken badge. --------- Co-authored-by: glassnotes Co-authored-by: Olivia Di Matteo Co-authored-by: Gabriel Bottrill Co-authored-by: Gabriel Bottrill <78718539+Gabriel-Bottrill@users.noreply.github.com> --- .github/workflows/python-package.yml | 40 + .pre-commit-config.yaml | 83 ++ .readthedocs.yaml | 20 + README.md | 86 +- docs/Makefile | 20 + docs/api/decompositions.rst | 7 + docs/api/identity_hunter.rst | 5 + docs/api/ops.rst | 5 + docs/api/transforms.rst | 11 + docs/api/utils.rst | 5 + docs/conf.py | 41 + docs/index.rst | 56 + docs/make.bat | 35 + docs/notes/basic_usage.rst | 162 +++ docs/notes/installation.rst | 16 + ionizer/decompositions.py | 108 +- ionizer/identity_hunter.py | 313 +++-- ionizer/ops.py | 71 +- ionizer/resources/double_gate_identities.pkl | Bin 2779 -> 2611 bytes ionizer/resources/triple_gate_identities.pkl | Bin 80103 -> 76119 bytes ionizer/transform_utils.py | 94 -- ionizer/transforms.py | 355 ++++- ionizer/utils.py | 203 ++- poetry.lock | 1181 +++++++++++++++++ pyproject.toml | 42 + requirements.txt | 1 - setup.py | 20 - tests/test_decompositions.py | 31 +- ...sform_utils.py => test_identity_hunter.py} | 19 +- tests/test_transforms.py | 178 ++- tests/test_utils.py | 176 ++- 31 files changed, 2887 insertions(+), 497 deletions(-) create mode 100644 .github/workflows/python-package.yml create mode 100644 .pre-commit-config.yaml create mode 100644 .readthedocs.yaml create mode 100644 docs/Makefile create mode 100644 docs/api/decompositions.rst create mode 100644 docs/api/identity_hunter.rst create mode 100644 docs/api/ops.rst create mode 100644 docs/api/transforms.rst create mode 100644 docs/api/utils.rst create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/make.bat create mode 100644 docs/notes/basic_usage.rst create mode 100644 docs/notes/installation.rst delete mode 100644 ionizer/transform_utils.py create mode 100644 poetry.lock create mode 100644 pyproject.toml delete mode 100644 requirements.txt delete mode 100644 setup.py rename tests/{test_transform_utils.py => test_identity_hunter.py} (93%) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml new file mode 100644 index 0000000..459519b --- /dev/null +++ b/.github/workflows/python-package.yml @@ -0,0 +1,40 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: Python package + +on: + push: + branches: ["main"] + pull_request: + branches: ["main", "rc-0.*"] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.ref }} + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: "pip" + - name: Install dependencies + run: | + python -m pip install -U pip poetry + poetry --version + poetry install + - name: Check linting and formatting with ruff + run: | + poetry run ruff check ionizer/*.py tests/*.py + - name: Test with pytest + run: | + poetry run pytest tests/test_*.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..66b3710 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,83 @@ +# To run all pre-commit checks, use: +# +# pre-commit run -a +# +# To install pre-commit hooks that run every time you commit: +# +# pre-commit install +# +# This file was constructed based on an example file provided by our friends +# at TUM Chair for Design Automation - thanks! + +ci: + autoupdate_commit_msg: "update pre-commit hooks" + autofix_commit_msg: "pre-commit fixes" + +repos: + # Standard hooks + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: "v4.6.0" + hooks: + - id: check-added-large-files + - id: check-case-conflict + - id: check-docstring-first + - id: check-merge-conflict + - id: check-toml + - id: check-yaml + - id: debug-statements + - id: end-of-file-fixer + - id: mixed-line-ending + - id: trailing-whitespace + + # Handling unwanted unicode characters + - repo: https://github.com/sirosen/texthooks + rev: "0.6.6" + hooks: + - id: fix-ligatures + - id: fix-smartquotes + + # Check for common mistakes + - repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.10.0 + hooks: + - id: rst-backticks + - id: rst-directive-colons + - id: rst-inline-touching-normal + + # Check for spelling + - repo: https://github.com/codespell-project/codespell + rev: "v2.3.0" + hooks: + - id: codespell + + # Format configuration files with prettier + - repo: https://github.com/pre-commit/mirrors-prettier + rev: "v4.0.0-alpha.8" + hooks: + - id: prettier + types_or: [yaml, markdown, html, css, javascript, json] + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.4.9 + hooks: + - id: ruff + args: ["--fix", "--show-fixes"] + types_or: [python, pyi, jupyter] + - id: ruff-format + types_or: [python, pyi, jupyter] + + # Also run Black on examples in the documentation + - repo: https://github.com/adamchainz/blacken-docs + rev: 1.16.0 + hooks: + - id: blacken-docs + additional_dependencies: [black==24.*] + + # Catch common capitalization mistakes + - repo: local + hooks: + - id: disallow-caps + name: Disallow improper capitalization + language: pygrep + entry: PyBind|Numpy|Github|PyTest|Pennylane + exclude: .pre-commit-config.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..72f0a4d --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,20 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.12" + jobs: + post_create_environment: + - pip install poetry + post_install: + - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with docs + +sphinx: + configuration: docs/conf.py diff --git a/README.md b/README.md index 932145a..c621ece 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,17 @@ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.10761367.svg)](https://doi.org/10.5281/zenodo.10761367) -Transpile and optimize your [PennyLane](https://github.com/pennylaneai/pennylane) circuits into +Transpile and optimize your PennyLane circuits into IonQ's native trapped-ion gate set (GPI, GPI2, MS) with just a single extra line of code! ```python from ionizer.transforms import ionize + @qml.qnode(dev) @ionize -def circuit(x): +def circuit(x): qml.Hadamard(wires=0) qml.CNOT(wires=[0, 1]) qml.RX(x, wires=1) @@ -21,61 +22,62 @@ def circuit(x): ```pycon >>> qml.draw(circuit)(0.3) 0: ──GPI2(0.00)─╭MS──GPI2(-1.57)─────────────────────────┤ -1: ──GPI2(3.14)─╰MS──GPI2(1.57)───GPI(-1.42)──GPI2(1.57)─┤ +1: ──GPI2(3.14)─╰MS──GPI2(1.57)───GPI(-1.42)──GPI2(1.57)─┤ ``` - ## Installation -Requirements: - * PennyLane >= 0.33 -The Ionizer is not currently available via a package manager. To install, clone the repository and run +The Ionizer is available via PyPI: ``` -python -m pip install . +pip install ionizer ``` -or +The core requirement is [PennyLane](https://pennylane.ai/) 0.37. -``` -python setup.py install -``` +Python versions 3.10-3.12 are supported and tested against. -If you need to run Ionizer with a version of PennyLane between 0.29 and 0.32, -please use version 0.1.2 of the package. +To install from source, clone this repository and use +[Poetry](https://python-poetry.org/) to install the dependencies listed in the +`pyproject.toml` file. ## Examples +For more detailed explanations and usage examples, please check the full +online documentation. + The Ionizer is implemented using [quantum function transforms](https://arxiv.org/abs/2202.13414), similar to PennyLane's [existing compilation tools](https://docs.pennylane.ai/en/stable/introduction/compiling_circuits.html). To compile and execute the circuit using trapped ion gates, the -``@ionize`` decorator performs the following steps: +`@ionize` transform will - * Decomposes all operations into Paulis/Pauli rotations, Hadamard, and CNOT - * Merges all single-qubit rotations - * Converts everything except RZ to GPI/GPI2/MS gates (`@ionizer.transforms.convert_to_gpi`) - * Virtually applies all RZ gates (`@ionizer.transforms.virtualize_rz_gates`) - * Repeatedly applies gate fusion and commutation through MS gates which performs simplification based on some circuit identities (`@ionizer.transforms.single_qubit_fusion_gpi` and `@ionizer.transforms.commute_through_ms_gates`) + - Decompose all operations into Paulis/Pauli rotations, Hadamard, and CNOT + - Cancel inverses and merge single-qubit rotations + - Convert everything except RZ to GPI, GPI2, and MS gates + - Virtually apply RZ gates + - Repeatedly apply single-qubit gate fusion and commutation through MS gates, + and perform simplification based on a database of circuit identities. ```python from ionizer.transforms import ionize + @qml.qnode(dev) @ionize def circuit_ionized(params): for idx in range(5): qml.Hadamard(wires=idx) - + for idx in range(4): qml.RY(params[idx], wires=idx) - qml.CNOT(wires=[idx+1, idx]) - + qml.CNOT(wires=[idx + 1, idx]) + for wire in dev.wires: qml.PauliX(wires=wire) - + return qml.expval(qml.PauliX(0)) ``` @@ -97,10 +99,13 @@ tensor(0.99500417, requires_grad=True) ``` -Note that while this comes packaged together as the ``@ionize`` transform, the -individual transforms can also be accessed and used independently. +The consistuent transforms can also be accessed and used independently. -There is currently not direct support for other frameworks. However, if you would like to do this with a Qiskit circuit, it can be accomplished as follows through the [`pennylane-qiskit`](https://github.com/PennyLaneAI/pennylane-qiskit) package. +There is currently not direct support for other frameworks. However, if you +would like to apply the transforms to Qiskit circuits, this can be accomplished +using the +[`pennylane-qiskit`](https://github.com/PennyLaneAI/pennylane-qiskit) package as +shown belown. ```python qiskit_circuit = QuantumCircuit(...) @@ -108,6 +113,7 @@ qiskit_circuit = QuantumCircuit(...) # Turns a Qiskit circuit into a PennyLane quantum function qfunc = qml.from_qiskit(qiskit_circuit) + @qml.qnode(dev) @ionize def pennylane_circuit(): @@ -115,25 +121,31 @@ def pennylane_circuit(): return qml.expval(qml.PauliX(0)) ``` - ## Notes This package is a work in progress. While it has been verified to work on some fairly large circuits, we still need to work on: - * finding circuit identities involving the 2-qubit gate - * improving the documentation and usage instructions - * ensuring differentiability of variational parameters - * writing more tests (compile at your own risk!) +- finding circuit identities involving the 2-qubit gate +- ensuring differentiability of variational parameters +- writing more tests (compile at your own risk!) ## Resources - * [IonQ documentation](https://ionq.com/docs/getting-started-with-native-gates) - * [Basic circuit compilation techniques for an ion-trap quantum machine](https://arxiv.org/abs/1603.07678) - - ## Citing +- [IonQ documentation](https://ionq.com/docs/getting-started-with-native-gates) +- [Basic circuit compilation techniques for an ion-trap quantum machine](https://arxiv.org/abs/1603.07678) + + +## Contributing + +The Ionizer is available open source under the MIT License. Contributions are +welcome. Please open an issue if you are interested in contributing, or if you +encounter a bug. + + +## Reference - If you use the Ionizer as part of your workflow, we would appreciate if you cite it using the BibTeX below. +If you use the Ionizer as part of your workflow, we would appreciate if you cite it using the BibTeX below. ``` @software{di_matteo_2024_10761367, diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..ed88099 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/api/decompositions.rst b/docs/api/decompositions.rst new file mode 100644 index 0000000..6b56257 --- /dev/null +++ b/docs/api/decompositions.rst @@ -0,0 +1,7 @@ +.. _decompositions: + +Decompositions of trapped-ion native gates +========================================== + +.. automodule:: ionizer.decompositions + :members: diff --git a/docs/api/identity_hunter.rst b/docs/api/identity_hunter.rst new file mode 100644 index 0000000..e1429df --- /dev/null +++ b/docs/api/identity_hunter.rst @@ -0,0 +1,5 @@ +Circuit identity generation +=========================== + +.. automodule:: ionizer.identity_hunter + :members: diff --git a/docs/api/ops.rst b/docs/api/ops.rst new file mode 100644 index 0000000..1ea9e71 --- /dev/null +++ b/docs/api/ops.rst @@ -0,0 +1,5 @@ +Operations +========== + +.. automodule:: ionizer.ops + :members: diff --git a/docs/api/transforms.rst b/docs/api/transforms.rst new file mode 100644 index 0000000..89ccfac --- /dev/null +++ b/docs/api/transforms.rst @@ -0,0 +1,11 @@ +Compilation transforms +====================== + +.. automodule:: ionizer.transforms + :members: + + .. automethod:: ionizer.transforms.ionize + .. automethod:: ionizer.transforms.commute_through_ms_gates + .. automethod:: ionizer.transforms.virtualize_rz_gates + .. automethod:: ionizer.transforms.single_qubit_fusion_gpi + .. automethod:: ionizer.transforms.convert_to_gpi diff --git a/docs/api/utils.rst b/docs/api/utils.rst new file mode 100644 index 0000000..64b1d94 --- /dev/null +++ b/docs/api/utils.rst @@ -0,0 +1,5 @@ +Utility functions +================= + +.. automodule:: ionizer.utils + :members: diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..b8d8246 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,41 @@ +import sys +import os + +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = "Ionizer" +copyright = "2024, Olivia Di Matteo" +author = "Olivia Di Matteo" +release = "0.3" + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +sys.path.insert(0, os.path.abspath("../ionizer")) + +extensions = [ + "sphinx_rtd_theme", + "sphinx.ext.autodoc", + "sphinx.ext.autosectionlabel", + "sphinx.ext.autosummary", + "sphinx.ext.mathjax", + "sphinx.ext.napoleon", +] + +autosectionlabel_prefix_document = True + +templates_path = ["_templates"] +exclude_patterns = [] + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = "sphinx_rtd_theme" +html_static_path = ["_static"] diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..05ce07c --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,56 @@ +The Ionizer +=========== + +The Ionizer is a set of tools for transpiling and optimizing PennyLane circuits +into native trapped-ion gates. + +These documentation pages focus on usage of the transpilation tools. Technical +details and intuition about trapped ion gates can be found in this `PennyLane +blog post +`_ +about the Ionizer, and IonQ's documentation page, `Getting started with Native +Gates `_. + +.. toctree:: + :maxdepth: 1 + :caption: Getting started + + notes/installation + notes/basic_usage + +.. toctree:: + :maxdepth: 1 + :caption: API Documentation + + api/ops + api/transforms + api/decompositions + api/identity_hunter + api/utils + +Contributing +------------ + +The Ionizer is available open source on `GitHub +`_ under the MIT License. Contributions +are welcome. Please open an issue if you are interested in contributing, or if +you encounter a bug. + +Reference +--------- + +If you use the Ionizer as part of your project, we would appreciate if you cite +it using the BibTeX below. + +.. code:: + + @software{di_matteo_2024_10761367, + author = {Di Matteo, Olivia}, + title = {The Ionizer}, + month = mar, + year = 2024, + publisher = {Zenodo}, + version = {0.2}, + doi = {10.5281/zenodo.10761367}, + url = {https://doi.org/10.5281/zenodo.10761367} + } diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..747ffb7 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/notes/basic_usage.rst b/docs/notes/basic_usage.rst new file mode 100644 index 0000000..51e5f4d --- /dev/null +++ b/docs/notes/basic_usage.rst @@ -0,0 +1,162 @@ +.. _basic_usage: + +Basic usage +=========== + +The goal of the package is to provide a single, easy-to-use transform that will +both transpile and optimize a circuit expressed using arbitrary gates into the +native gate set of IonQ's trapped-ion devices, :class:`ionizer.ops.GPI`, +:class:`ionizer.ops.GPI2`, and :class:`ionizer.ops.MS`. + +For more information about these gates, see the `PennyLane blog post +`_ +and `IonQ documentation `_. + +Circuits expressed using any regular PennyLane gates can be decorated with the +:func:`ionizer.transforms.ionize` transform, and executed as normal. + +.. code:: + + import pennylane as qml + from ionizer.transforms import ionize + + dev = qml.device("default.qubit", wires=2) + + + @qml.qnode(dev) + @ionize + def circuit(x): + qml.Hadamard(wires=0) + qml.CNOT(wires=[0, 1]) + qml.RX(x, wires=1) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + +.. code:: + + >>> circuit(0.3) + 0.9553364891256048 + >>> qml.draw(circuit)(0.3) + 0: ──GPI2(0.00)─╭MS──GPI2(-1.57)─────────────────────────┤ ╭ + 1: ──GPI2(3.14)─╰MS──GPI2(1.57)───GPI(-1.42)──GPI2(1.57)─┤ ╰ + +.. warning:: + + Compiled circuits are not guaranteed to be optimal. However, they should be + significantly smaller than what one would obtain by performing a naive 1-1 + mapping to native gates. + + +Transforms can also be applied to QNodes directly, even after they are +constructed: + +.. code:: + + @qml.qnode(dev) + def circuit(x): + qml.Hadamard(wires=0) + qml.CNOT(wires=[0, 1]) + qml.RX(x, wires=1) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + ionized_qnode = ionize(circuit) + +.. code:: + + >>> ionized_qnode(0.3) + 0.9553364891256048 + +.. _basic_usage-equivalence_validation: + +Equivalence validation +---------------------- + +While all implemented transforms should preserve the behaviour of circuits, they +nevertheless contain a mechanism for under-the-hood equivalence checking (up to +a global phase). If the ``verify_equivalence`` flag is ``True``, an error will +be raised if the transpiled circuit is not equivalent to the original. This flag +is ``False`` by default because equivalence is checked at the unitary matrix +level. + +.. code:: + + from functools import partial + import pennylane as qml + from ionizer.transforms import ionize + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + @partial(ionize, verify_equivalence=True) + def circuit(x): + qml.Hadamard(wires=0) + qml.CNOT(wires=[0, 1]) + qml.RX(x, wires=1) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + +For more details, see :func:`ionizer.utils.flag_non_equivalence`. + +Ionizer and gradients +--------------------- + +.. warning:: + + Full automatic differentiation is not currently supported by the Ionizer. + +To execute circuits that evaluate gradients, is recommended to first construct +the required circuits with the desired parameters, then transpile those circuit +for execution. This can be done by applying the transform to one or more quantum +tapes directly. + +For example, the following code transpiles both quantum circuits required to the +parameter shift gradient of the circuit defined at the top of this page. + +.. code:: + + from pennylane import numpy as np + + # Execute the circuit once to construct the tape + x = np.array(0.5, requires_grad=True) + circuit(x) + + # Compute tapes required for gradient, and processing function that + # evaluates the gradients based on results + gradient_tapes, gradient_fn = qml.gradients.param_shift(circuit.qtape) + + for tape in gradient_tapes: + print(tape.draw(decimals=2)) + print() + + results = dev.execute(gradient_tapes) + print(f"Gradient from original tape execution = {gradient_fn(results)}", end="\n\n") + + # Transpile each of the gradient tapes. The same processing function + # can be applied to the results of the transpiled tapes. + transpiled_gradient_tapes, _ = ionize(gradient_tapes) + + for tape in transpiled_gradient_tapes: + print(tape.draw(decimals=2)) + print() + + transpiled_results = dev.execute(transpiled_gradient_tapes) + print(f"Gradient from transpiled tape execution = {gradient_fn(transpiled_results)}") + +The following output, showing both original and transpiled versions of the +gradient tape, is + +.. code:: + + 0: ──H─╭●───────────┤ ╭ + 1: ────╰X──RX(2.07)─┤ ╰ + + 0: ──H─╭●────────────┤ ╭ + 1: ────╰X──RX(-1.07)─┤ ╰ + + Gradient from original tape execution = -0.479425538604203 + + 0: ──GPI2(0.00)─╭MS──GPI2(-1.57)─────────────────────────┤ ╭ + 1: ──GPI2(3.14)─╰MS──GPI2(1.57)───GPI(-0.54)──GPI2(1.57)─┤ ╰ + + 0: ──GPI2(0.00)─╭MS──GPI2(-1.57)─────────────────────────┤ ╭ + 1: ──GPI2(3.14)─╰MS──GPI2(1.57)───GPI(-2.11)──GPI2(1.57)─┤ ╰ + + Gradient from transpiled tape execution = -0.479425538604203 diff --git a/docs/notes/installation.rst b/docs/notes/installation.rst new file mode 100644 index 0000000..5a9ece5 --- /dev/null +++ b/docs/notes/installation.rst @@ -0,0 +1,16 @@ +Installation +============ + +The Ionizer is now available on PyPI: + +.. code:: + + >>> pip install ionizer + +The core requirement is `PennyLane `_ 0.37. Python versions 3.10-3.12 are supported +and tested. + +To install from source, clone the `GitHub repository +`_ and use `Poetry +`_ to install the dependencies listed in the +``pyproject.toml`` file. diff --git a/ionizer/decompositions.py b/ionizer/decompositions.py index 1f3de53..f9ccae1 100644 --- a/ionizer/decompositions.py +++ b/ionizer/decompositions.py @@ -14,9 +14,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. +r""" +Custom decompositions of operations into the :math:`GPI`, :math:`GPI2`, and +:math:`MS` native gate set. """ -Custom decompositions of operations into the {GPI, GPI2, MS} native gate set. -""" + from pennylane import math import numpy as np @@ -26,75 +28,99 @@ # Non-parametrized operations (up to phases) def gpi_pauli_x(wires): - """PauliX decomposition as a GPI gate. + r"""Pauli :math:`X` decomposition as a :math:`GPI` gate. + + .. math:: + + X = GPI(0) Args: wires (Sequence[int] or pennylane.Wires): The wires this gate is acting on. Returns: - List[Operation]: The sequence of GPI rotations that implements the gate. + List[Operation]: The sequence of :math:`GPI` rotations that implements the gate. """ return [GPI(0.0, wires=wires)] def gpi_pauli_y(wires): - """PauliY decomposition as a GPI gate. + r"""Pauli :math:`Y` decomposition as a :math:`GPI` gate. + + .. math:: + + Y = GPI(\pi / 2) Args: wires (Sequence[int] or pennylane.Wires): The wires this gate is acting on. Returns: - List[Operation]: The sequence of GPI rotations that implements the gate. + List[Operation]: The sequence of :math:`GPI` rotations that implements the gate. """ return [GPI(np.pi / 2, wires=wires)] def gpi_pauli_z(wires): - """PauliZ decomposition into GPI gates. + r"""Pauli :math:`Z` decomposition into :math:`GPI` gates. + + .. math:: + + Z = GPI(-\pi / 2) GPI(0) Args: wires (Sequence[int] or pennylane.Wires): The wires this gate is acting on. Returns: - List[Operation]: The sequence of GPI rotations that implements + List[Operation]: The sequence of :math:`GPI` rotations that implements the gate up to a global phase. """ return [GPI(0.0, wires=wires), GPI(-np.pi / 2, wires=wires)] def gpi_hadamard(wires): - """Hadamard decomposition into GPI/GPI2 gates. + r"""Hadamard decomposition into :math:`GPI` and :math:`GPI2` gates. + + .. math:: + + H = GPI2(-\pi / 2) GPI(0) Args: wires (Sequence[int] or pennylane.Wires): The wires this gate is acting on. Returns: - List[Operation]: The sequence of GPI/GPI2 rotations that implements + List[Operation]: The sequence of :math:`GPI` and :math:`GPI2` rotations that implements the gate up to a global phase. """ return [GPI(0.0, wires=wires), GPI2(-np.pi / 2, wires=wires)] def gpi_sx(wires): - """Square-root of PauliX as a GPI2 gate. + r"""Square-root of :math:`X` decomposition into :math:`GPI2` gate. + + .. math:: + + \sqrt{X} = GPI2(0) Args: wires (Sequence[int] or pennylane.Wires): The wires this gate is acting on. Returns: - List[Operation]: The GPI2 rotation that implements the gate. + List[Operation]: The :math:`GPI2` rotation that implements the gate. """ return [GPI2(0.0, wires=wires)] def gpi_cnot(wires): - """CNOT decomposition into GPI2 and MS gate. + r""":math:`CNOT` decomposition into :math:`GPI2` and :math:`MS` gates. + + .. math:: + + CNOT_{ij} = GPI2_i(-\pi/2) GPI2_i(\pi) GPI2_j(\pi) MS_{ij} GPI2_i(\pi/2) Args: wires (Sequence[int] or pennylane.Wires): The wires this gate is acting on. Returns: - List[Operation]: The sequence of GPI2 rotations that implements + List[Operation]: The sequence of trapped ion gates that implements the gate up to a global phase. """ return [ @@ -108,14 +134,18 @@ def gpi_cnot(wires): # Parametrized operations (up to phases) def gpi_rx(phi, wires): - """RX decomposition into GPI/GPI2 gates. + r""":math:`RX` decomposition into :math:`GPI` and :math:`GPI2` gates. + + .. math:: + + RX(\phi) = GPI2(\pi/2) GPI(\phi/2 - \pi/2) GPI2(\pi/2) Args: phi (tensor): Rotation angle wires (Sequence[int] or pennylane.Wires): The wires this gate is acting on. Returns: - List[Operation]: The sequence of GPI/GPI2 rotations that implements + List[Operation]: The sequence of :math:`GPI` and :math:`GPI2` rotations that implements the gate up to a global phase. """ return [ @@ -126,68 +156,80 @@ def gpi_rx(phi, wires): def gpi_ry(phi, wires): - """RY decomposition into GPI/GPI2 gates. + r""":math:`RY` decomposition into :math:`GPI` and :math:`GPI2` gates. + + .. math:: + + RY(\phi) = GPI2(\pi) GPI(\phi/2) GPI2(\pi) Args: phi (tensor): Rotation angle wires (Sequence[int] or pennylane.Wires): The wires this gate is acting on. Returns: - List[Operation]: The sequence of GPI/GPI2 rotations that implements + List[Operation]: The sequence of :math:`GPI` and :math:`GPI2` rotations that implements the gate up to a global phase. """ - return [GPI2(np.pi, wires=wires), GPI(phi / 2, wires=wires), GPI2(np.pi, wires=wires)] + return [ + GPI2(np.pi, wires=wires), + GPI(phi / 2, wires=wires), + GPI2(np.pi, wires=wires), + ] def gpi_rz(phi, wires): - """RZ decomposition into GPI/GPI2 gates. + r""":math:`RZ` decomposition into :math:`GPI` gates. + + .. math:: + + RZ(\phi) = GPI(0) GPI(-\pi/2) Args: phi (tensor): Rotation angle wires (Sequence[int] or pennylane.Wires): The wires this gate is acting on. Returns: - List[Operation]: The sequence of GPI/GPI2 rotations that implements + List[Operation]: The sequence of :math:`GPI` rotations that implements the gate up to a global phase. """ return [GPI(-phi / 2, wires=wires), GPI(0.0, wires=wires)] -def gpi_single_qubit_unitary(U, wires): - """Single-qubit unitary matrix decomposition into GPI/GPI2 gates. +def gpi_single_qubit_unitary(unitary, wires): + r"""Single-qubit unitary matrix decomposition into :math:`GPI` and :math:`GPI2` gates. This function is modeled off of PennyLane's unitary_to_rot transform: https://docs.pennylane.ai/en/stable/code/api/pennylane.transforms.unitary_to_rot.html Args: - U (tensor): A unitary matrix. + unitary (tensor): A unitary matrix. wires (Sequence[int] or pennylane.Wires): The wires this gate is acting on. Returns: - List[Operation]: The sequence of GPI/GPI2 rotations that implements + List[Operation]: The sequence of :math:`GPI` and :math:`GPI2` rotations that implements the desired unitary up to a global phase. """ # Check in case we have the identity - if math.allclose(U, math.eye(2)): + if math.allclose(unitary, math.eye(2)): return [] # Special case: if we have off-diagonal elements this is a single GPI - if math.isclose(U[0, 0], 0.0): - angle = math.angle(U[1, 0]) + if math.isclose(unitary[0, 0], 0.0): + angle = math.angle(unitary[1, 0]) return [GPI(angle, wires=wires)] # Special case: if we have off-diagonal 0s but it is not the identity, # this is an RZ which is a sequence of two GPIs. - if math.allclose([U[0, 1], U[1, 0]], [0.0, 0.0]): - return gpi_rz(2 * math.angle(U[1, 1]), wires) + if math.allclose([unitary[0, 1], unitary[1, 0]], [0.0, 0.0]): + return gpi_rz(2 * math.angle(unitary[1, 1]), wires) # Special case: if both diagonal elements are 1/sqrt(2), this is a GPI2 - if math.allclose([U[0, 0], U[1, 1]], [1 / np.sqrt(2), 1 / np.sqrt(2)]): - angle = math.angle(U[1, 0]) + np.pi / 2 + if math.allclose([unitary[0, 0], unitary[1, 1]], [1 / np.sqrt(2), 1 / np.sqrt(2)]): + angle = math.angle(unitary[1, 0]) + np.pi / 2 return [GPI2(angle, wires=wires)] # In the general case we must compute and return all three angles. - gamma, beta, alpha = extract_gpi2_gpi_gpi2_angles(U) + gamma, beta, alpha = extract_gpi2_gpi_gpi2_angles(unitary) return [GPI2(gamma, wires=wires), GPI(beta, wires=wires), GPI2(alpha, wires=wires)] diff --git a/ionizer/identity_hunter.py b/ionizer/identity_hunter.py index 1684c11..da7794a 100644 --- a/ionizer/identity_hunter.py +++ b/ionizer/identity_hunter.py @@ -1,13 +1,19 @@ +r"""Submodule to generate and store a database of circuit identities involving +up to three successive :math:`GPI` and :math:`GPI2` gates. + +This module is primarily for internal use. It exposes only functions to query the +database for an identity involving a specified gate sequence. The database is +included as a set of pickle files with the package. + """ -Submodule to generate and store a database of circuit identities involving -up to 3 GPI/GPI2 gates. -""" + from importlib.resources import files from itertools import product import pickle import numpy as np +import pennylane as qml from pennylane import math from .ops import GPI, GPI2 @@ -17,10 +23,104 @@ TRIPLE_IDENTITY_FILE = files("ionizer.resources").joinpath("triple_gate_identities.pkl") -def generate_gate_identities(): - """Generates all 2- and 3-gate identities involving GPI/GPI2 and special angles. +def _test_inclusion_in_identity_db(db_subset, single_gates, candidate_angles, candidate_matrix): + """Helper function to test if a candidate gate identity was already found. + + Args: + db_subset (Dict[str, Tuple(Tuple(float), str, float)]): Subset of database we + wish to search for presence of identity. + single_gates (Dict[str, List[Tuple(float, tensor)]]): Dictionary containing + which gates to generate identities from, along with list of special + cases of angles/matrices to use in identity generation. + candidate_angles (List[float]): List of angles used in identity generation. + candidate_matrix (tensor): Unitary matrix for this particular set of angles. + + Returns: + Tuple(str or None, float or None): If a valid identity is found, returns the name + of the gate and its parameter. Otherwise, returns None, None. + """ + # Loop over GPI/GPI2 for all the special angles + for id_gate, gate_angle_list in single_gates.items(): + # Get their explicit reference angles and matrix representations + angles, matrices = ( + [x[0] for x in gate_angle_list], + [x[1] for x in gate_angle_list], + ) + + # Test each reference against the candidate to see if any are equivalent + for ref_angle, ref_matrix in zip(angles, matrices): + if are_mats_equivalent(candidate_matrix, ref_matrix): + # If they are equivalent, return the identity if not already in database + if not any( + np.allclose(candidate_angles, database_angles) + for database_angles in [identity[0] for identity in db_subset] + ): + return id_gate, ref_angle + + return None, None + + +def _generate_gate_identities(single_gates, id_angles, identity_length): + """Generates all identities involving specified angles for a sequence + of :math:`GPI` and :math:`GPI2` gates. + + Args: + single_gates (Dict[str, List[Tuple(float, tensor)]]): Dictionary containing + which gates to generate identities from, along with list of special + cases of angles/matrices to use in identity generation. + id_angles (List[float]): Special values of angles used in identity generation. + identity_length (int): How long a gate sequence to test. Must be 2 or 3. + + Returns: + Dict[str, Tuple(Tuple(float), str, float)]: Dictionary of identities + where the key is a concatenated string of gates, and the value contains + the angles involved in the identity, the resultant gate, and its argument. + + Example: + If ``identity_length=2``, an example return dictionary with one entry is + + ``{'GPIGPI2': ((0.7853981633974483, -2.356194490192345), 'GPI2', 0.7853981633974483)}`` + + This represents the equality :math:`GPI(0.785398) GPI2(-2.356194) = GPI2(0.78539)`. + """ + + gate_identities = {} + + # Generate combinations of gates to test if they are equivalent to a single one + for gate_list in product([GPI, GPI2], repeat=identity_length): + combo_name = "".join([gate.__name__ for gate in gate_list]) + + if combo_name not in gate_identities: + gate_identities[combo_name] = [] + + for angle_list in product(id_angles, repeat=identity_length): + matrix = math.linalg.multi_dot( + [gate.compute_matrix(angle) for gate, angle in zip(gate_list, angle_list)] + ) + + # Test in case we produced the identity + if are_mats_equivalent(matrix, np.eye(2)): + gate_identities[combo_name].append((angle_list, "Identity", 0.0)) + continue + + # Check if we produced something else instead; if so, add to database + equivalent_gate, equivalent_angle = _test_inclusion_in_identity_db( + gate_identities[combo_name], single_gates, angle_list, matrix + ) + + if equivalent_gate is not None: + gate_identities[combo_name].append((angle_list, equivalent_gate, equivalent_angle)) + + return gate_identities + + +def _generate_gate_identity_database(): + r"""Generates all 2- and 3-gate identities involving :math:`GPI` + and :math:`GPI2` and special angles. + + Special angles include: :math:`0, \pm \pi/4, \pm \pi/2, \pm 3\pi/4, \pm \pi`. - Results are stored in pkl files which can be used later on. + Results are stored in ``.pkl`` files which can be used later on. """ id_angles = [ @@ -40,100 +140,38 @@ def generate_gate_identities(): "GPI2": [([angle], GPI2.compute_matrix(angle)) for angle in id_angles], } - double_gate_identities = {} - - # Check which combinations of 2 gates reduces to a single one - for gate_1, gate_2 in product([GPI, GPI2], repeat=2): - combo_name = gate_1.__name__ + gate_2.__name__ - - for angle_1, angle_2 in product(id_angles, repeat=2): - matrix = math.dot(gate_1.compute_matrix(angle_1), gate_2.compute_matrix(angle_2)) - - # Test in case we produced the identity; - if not math.isclose(matrix[0, 0], 0.0): - if math.allclose(matrix / matrix[0, 0], math.eye(2)): - if combo_name not in list(double_gate_identities.keys()): - double_gate_identities[combo_name] = [] - double_gate_identities[combo_name].append(([angle_1, angle_2], "Identity", 0.0)) - continue - - for id_gate in list(single_gates.keys()): - angles, matrices = [x[0] for x in single_gates[id_gate]], [ - x[1] for x in single_gates[id_gate] - ] - - for ref_angle, ref_matrix in zip(angles, matrices): - if are_mats_equivalent(matrix, ref_matrix): - if combo_name not in list(double_gate_identities.keys()): - double_gate_identities[combo_name] = [] - - if not any( - np.allclose([angle_1, angle_2], database_angles) - for database_angles in [ - identity[0] for identity in double_gate_identities[combo_name] - ] - ): - double_gate_identities[combo_name].append( - ([angle_1, angle_2], id_gate, ref_angle) - ) + double_gate_identities = _generate_gate_identities(single_gates, id_angles, 2) + triple_gate_identities = _generate_gate_identities(single_gates, id_angles, 3) with DOUBLE_IDENTITY_FILE.open("wb") as outfile: pickle.dump(double_gate_identities, outfile) - triple_gate_identities = {} + with TRIPLE_IDENTITY_FILE.open("wb") as outfile: + pickle.dump(triple_gate_identities, outfile) - # Check which combinations of 2 gates reduces to a single one - for gate_1, gate_2, gate_3 in product([GPI, GPI2], repeat=3): - combo_name = gate_1.__name__ + gate_2.__name__ + gate_3.__name__ - for angle_1, angle_2, angle_3 in product(id_angles, repeat=3): - matrix = math.linalg.multi_dot( - [ - gate_1.compute_matrix(angle_1), - gate_2.compute_matrix(angle_2), - gate_3.compute_matrix(angle_3), - ] - ) +def lookup_gate_identity(gates): + """Given a sequence of two or three single-qubit gates, query a database of + known circuit identities for a shorter implementation. - # Test in case we produced the identity; - if not math.isclose(matrix[0, 0], 0.0): - if math.allclose(matrix / matrix[0, 0], math.eye(2)): - if combo_name not in list(triple_gate_identities.keys()): - triple_gate_identities[combo_name] = [] - triple_gate_identities[combo_name].append( - ([angle_1, angle_2, angle_3], "Identity", 0.0) - ) - continue - - for id_gate, _ in single_gates.items(): - angles, matrices = [x[0] for x in single_gates[id_gate]], [ - x[1] for x in single_gates[id_gate] - ] - - for ref_angle, ref_matrix in zip(angles, matrices): - if are_mats_equivalent(matrix, ref_matrix): - if combo_name not in list(triple_gate_identities.keys()): - triple_gate_identities[combo_name] = [] - - if not any( - np.allclose([angle_1, angle_2, angle_3], database_angles) - for database_angles in [ - identity[0] for identity in triple_gate_identities[combo_name] - ] - ): - triple_gate_identities[combo_name].append( - ([angle_1, angle_2, angle_3], id_gate, ref_angle) - ) + Args: + gates (List[Operation]): A list of two or three ``GPI`` and/or ``GPI2`` operations. + These should be ordered as they appear in the circuit diagram. - with TRIPLE_IDENTITY_FILE.open("wb") as outfile: - pickle.dump(triple_gate_identities, outfile) + Returns: + List[Operation]: If an equivalent but shorter sequence of ``GPI`` and ``GPI2`` gates is + found in the identity database, this will be returned. If no equivalent sequence + is found, the empty list is returned. + **Example** -def lookup_gate_identity(gates): - """Given a pair of input gates in the order they come in the circuit, - look up if there is a circuit identity in our database. Note that the - database is constructed using matrix multiplication so we will need to - exchange the order of the gates.""" + .. code:: + + >>> gate_list = [GPI(np.pi / 4, wires=0), GPI2(-3 * np.pi / 4)] + >>> lookup_gate_identity(gate_list) + [GPI2(0.7853981633974483, wires=[0])] + + """ if len(gates) not in [2, 3]: raise ValueError("Currently only 2- and 3-gate circuit identities are supported.") @@ -143,13 +181,15 @@ def lookup_gate_identity(gates): "Currently only 2- and 3-gate circuit identities on GPI/GPI2 gates are supported." ) + gate_identities = {} + if len(gates) == 2: try: with DOUBLE_IDENTITY_FILE.open("rb") as infile: gate_identities = pickle.load(infile) except FileNotFoundError: # Generate the file first and then load it - generate_gate_identities() + _generate_gate_identity_database() with DOUBLE_IDENTITY_FILE.open("rb") as infile: gate_identities = pickle.load(infile) elif len(gates) == 3: @@ -158,11 +198,13 @@ def lookup_gate_identity(gates): gate_identities = pickle.load(infile) except FileNotFoundError: # Generate the file first and then load it - generate_gate_identities() + _generate_gate_identity_database() with TRIPLE_IDENTITY_FILE.open("rb") as infile: gate_identities = pickle.load(infile) - # Get the information about this particular combination of gates + # Get the information about this particular combination of gates. Note that + # the database is constructed using matrix multiplication so we will need to + # exchange the order of the gates. combo_name = "".join([gate.name for gate in gates[::-1]]) combo_angles = [float(gate.data[0]) for gate in gates[::-1]] @@ -183,3 +225,88 @@ def lookup_gate_identity(gates): return [] return None + + +def search_and_apply_two_gate_identities(gates_to_apply): + """Try to simplify a sequence of two gates. + + Sequences that are found are queued to the current context; if no identity + is found, we simply queue the provided sequence of gates. + + Args: + gates_to_apply (List[pennylane.Operation]): A sequence of two gates + we would like to simplify. + + Returns: + List[pennylane.Operation]: The simplified or alternate gate sequence that + will be applied within the transform. + """ + if len(gates_to_apply) != 2: + raise ValueError( + "Only sets of 2 gates can be passed to search_and_apply_two_gate_identities" + ) + + # Make sure the gates share wires + if len(qml.wires.Wires.shared_wires([gates_to_apply[0].wires, gates_to_apply[1].wires])) != 1: + raise ValueError("Gates must share wires to find identities.") + + # Special case with no fixed angles: GPI2(x) GPI2(x) = GPI(x) + if gates_to_apply[0].name == "GPI2" and gates_to_apply[1].name == "GPI2": + if qml.math.isclose(gates_to_apply[0].data[0], gates_to_apply[1].data[0]): + return [GPI(gates_to_apply[0].data[0], wires=gates_to_apply[0].wires)] + + # Search for non-special identity + with qml.QueuingManager.stop_recording(): + identity_to_apply = lookup_gate_identity(gates_to_apply) + + # If there is an identity, return it; otherwise just return the gates + return identity_to_apply if identity_to_apply is not None else gates_to_apply + + +def search_and_apply_three_gate_identities(gates_to_apply): + """Try to simplify a sequence of three gates. + + Sequences that are found are queued to the current context; if no identity + is found, we simply queue the provided sequence of gates. + + Args: + gates_to_apply (List[pennylane.Operation]): A sequence of three gates + we would like to simplify. + + Returns: + List[pennylane.Operation]: The simplified or alternate gate sequence that + will be applied within the transform. + """ + if len(gates_to_apply) != 3: + raise ValueError( + "Only sets of 3 gates can be passed to search_and_apply_three_gate_identities" + ) + + # Make sure the gates share wires + if len(qml.wires.Wires.shared_wires([gate.wires for gate in gates_to_apply])) != 1: + raise ValueError("Gates must share wires to find identities.") + + # First, check if we can apply an identity to all three gates + with qml.QueuingManager.stop_recording(): + three_gate_identity_to_apply = lookup_gate_identity(gates_to_apply) + + if three_gate_identity_to_apply is not None: + return three_gate_identity_to_apply + + # If we can't apply a 3-gate identity, see if there is a 2-gate one on the + # first two gates. + with qml.QueuingManager.stop_recording(): + identity_to_apply = search_and_apply_two_gate_identities(gates_to_apply[:2]) + + if identity_to_apply is not None: + if len(identity_to_apply) < 2: + return identity_to_apply + [gates_to_apply[2]] + + # If not, apply the first gate, then check if there is anything to be + # done between the second and third. + identity_to_apply = search_and_apply_two_gate_identities(gates_to_apply[1:]) + if identity_to_apply is not None: + if len(identity_to_apply) < 2: + return [gates_to_apply[0]] + identity_to_apply + + return gates_to_apply diff --git a/ionizer/ops.py b/ionizer/ops.py index 8429afe..1f6af63 100644 --- a/ionizer/ops.py +++ b/ionizer/ops.py @@ -17,6 +17,7 @@ """ Native gates for IonQ hardware as PennyLane operations. """ + import numpy as np import pennylane as qml @@ -24,13 +25,11 @@ class GPI(Operation): - r""" - The single-qubit GPI rotation + r"""The single-qubit :math:`GPI` rotation .. math:: GPI(\phi) = \begin{bmatrix} - 0 & e^{-i\phi} \\ - e^{i\phi} & 0 - \end{bmatrix}. + 0 & e^{-i\phi} \\ e^{i\phi} & 0 + \end{bmatrix}. Args: phi (float): rotation angle :math:`\phi` @@ -38,12 +37,16 @@ class GPI(Operation): do_queue (bool): Indicates whether the operator should be immediately pushed into the Operator queue (optional) id (str or None): String representing the operation (optional) + """ + num_wires = 1 num_params = 1 ndim_params = (0,) - def __init__(self, phi, wires, id=None): + # Note: disable pylint complaint about redefined built-in, since the id + # value itself is coming from the class definition of Operators in PennyLane proper. + def __init__(self, phi, wires, id=None): # pylint: disable=redefined-builtin super().__init__(phi, wires=wires, id=id) @staticmethod @@ -61,6 +64,7 @@ def compute_matrix(phi): # pylint: disable=arguments-differ >>> GPI.compute_matrix(0.3) array([[0. +0.j , 0.95533649-0.29552021j], [0.95533649+0.29552021j, 0. +0.j ]]) + """ return qml.math.stack([[0, qml.math.exp(-1j * phi)], [qml.math.exp(1j * phi), 0]]) @@ -70,13 +74,11 @@ def adjoint(self): class GPI2(Operation): - r""" - The single-qubit GPI rotation + r"""The single-qubit :math:`GPI2` rotation .. math:: GPI2(\phi) = \frac{1}{\sqrt{2}} \begin{bmatrix} - 1 & -ie^{-i\phi} \\ - -ie^{i\phi} & 1 - \end{bmatrix}. + 1 & -ie^{-i\phi} \\ -ie^{i\phi} & 1 + \end{bmatrix}. Args: phi (float): rotation angle :math:`\phi` @@ -84,13 +86,14 @@ class GPI2(Operation): do_queue (bool): Indicates whether the operator should be immediately pushed into the Operator queue (optional) id (str or None): String representing the operation (optional) + """ num_wires = 1 num_params = 1 ndim_params = (0,) - def __init__(self, phi, wires, id=None): + def __init__(self, phi, wires, id=None): # pylint: disable=redefined-builtin super().__init__(phi, wires=wires, id=id) @staticmethod @@ -108,10 +111,14 @@ def compute_matrix(phi): # pylint: disable=arguments-differ >>> GPI2.compute_matrix(0.3) array([[ 0.70710678+0.j , -0.33900505-0.62054458j], [ 0.33900505-0.62054458j, 0.70710678+0.j ]]) + """ exponent = -1j * phi return qml.math.stack( - [[1, -1j * qml.math.exp(exponent)], [-1j * qml.math.exp(qml.math.conj(exponent)), 1]] + [ + [1, -1j * qml.math.exp(exponent)], + [-1j * qml.math.exp(qml.math.conj(exponent)), 1], + ] ) / np.sqrt(2) def adjoint(self): @@ -119,17 +126,16 @@ def adjoint(self): class MS(Operation): - r""" - The two-qubit Molmer-Sorenson operation. + r"""The two-qubit Mølmer-Sørensen (MS) operation. - In general this is a parametrized operation, but as the IonQ hardware permits - only the version where both parameters are 0, this is what we implement. + In general this is a parametrized operation, but the IonQ hardware permits + only this version, where both parameters are 0. .. math:: MS = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & 0 & 0 & -i \\ - 0 & 1 & -1j & 0 \\ - 0 & -1j & 1 & 0 \\ - -1j & 0 & 0 & 1 \\ + 0 & 1 & -i & 0 \\ + 0 & -i & 1 & 0 \\ + -i & 0 & 0 & 1 \\ \end{bmatrix}. Args: @@ -137,15 +143,38 @@ class MS(Operation): do_queue (bool): Indicates whether the operator should be immediately pushed into the Operator queue (optional) id (str or None): String representing the operation (optional) + """ + num_wires = 2 num_params = 0 - def __init__(self, wires, id=None): + def __init__(self, wires, id=None): # pylint: disable=redefined-builtin super().__init__(wires=wires, id=id) @staticmethod def compute_matrix(): # pylint: disable=arguments-differ + r"""Canonical matrix representation in computational basis. + + Args: + phi (tensor_like or float): rotation angle + + Returns: + tensor_like: canonical matrix + + **Example** + + >>> MS.compute_matrix() + array([[ 0.70710678+0.j , 0. +0.j , + 0. +0.j , -0. -0.70710678j], + [ 0. +0.j , 0.70710678+0.j , + -0. -0.70710678j, 0. +0.j ], + [ 0. +0.j , -0. -0.70710678j, + 0.70710678+0.j , 0. +0.j ], + [-0. -0.70710678j, 0. +0.j , + 0. +0.j , 0.70710678+0.j ]]) + + """ return qml.math.stack( [[1, 0, 0, -1j], [0, 1, -1j, 0], [0, -1j, 1, 0], [-1j, 0, 0, 1]] ) / np.sqrt(2) diff --git a/ionizer/resources/double_gate_identities.pkl b/ionizer/resources/double_gate_identities.pkl index 1e84d0a7ef50cba2fd67e73e9bb273e05f1a62d8..4365d154bf18fba4bc0b5bd5a244bfa345a8b438 100644 GIT binary patch literal 2611 zcmbtVJ5DP>5VUv}U`{|v<^&`gksu)~fmk^sAbr7;u(TvZCO`r~1c@WFBqSu|RQkP`~yXN4jGAZ~&2i=0p-0K^qAepVxiv;NTE-EJZjFgE9z5CuRgdBRhUhq7nwD zT$;1%au@;7$eu)6zTSg!D@2YOA9ViEC3l0_`d))@=?eB?4~{XpX4JNT8xd%m=B8FcpQ?YN9X32vrPR z0gC1Xkzi7ZzziHrbJP{M8z|qAR=BVaDVaf0$R4}cZHM}20n5a^#2!7gDd z6tVN%TNT^{uA(Gy7ZL!Ziz)tMfmHJV literal 2779 zcma)-zityj5XP-wg@h-dO_wG}XOt#V6iY-|xuc+^K;opOp-PHKM9|T=GEPH7llSmF zLPWppkezc!( zzi%#2o|{px4_5E%kJlfrZ?`U7o6Wuo+#q&b`?=W(WT|mSAbY<)eSUfL(=}=oAQ!(w zbvb1BN9)EYKrR$J0@3(xH=PK`ax|(JuG~0mU6fL5}g#qUhgz z7~(q(+;HSZc!P*Y?Ug?Z_soYOG0)YPuZGGm6JNxPe-(T*7%95)SBVdh!P@%19D<1TlR*^k__EF^wy{$*_B<6Orwzx5t*9Ar|g<9W*6Ux z+F%zkxtLvL-V{PJk}p*cpfO+EyWT@I17)Ee>I5V)@r@}<)kDh;L++K7g?gAn+&ezz z7x%7Pg|-IDN_~K&mQGm*vgLHsP5u8r3CgpltWTU8qJ;nP@4xZi!YeUNVbp#jdvv(p z_!%iLO=2R$2!Z&;+G14wII=)Y8u@7;Jth#SrM;6h5b86=hJQcu+6;ox=7JfHq8*rK zK#N9Kp1~j(lHq&c9uA^6n`OxP+V~&I^6QC8uR~7sf;awQaz9${2-FD58#4%oQdi%+ F_y@^ic546t diff --git a/ionizer/resources/triple_gate_identities.pkl b/ionizer/resources/triple_gate_identities.pkl index 50144c1af629d0c94ae2de2a2da206af432b8c0d..4d3b7152c2bd83a69d1c85705c1828bbc0fcf92e 100644 GIT binary patch literal 76119 zcmd6wKd)_BR^1DzmimH4?3AntAZR|od4Z)8nFFUCSfZq&1EMBC0^zKa3X6|p&Ot)5 zWXY0cNB#)+BUmJyz0drOwZ{B&_9FuUTDAAsV~zP+bFH)Qx$jl|w{QR9+i!m7o8y1} z?x$b;`0?F;{`1%W{`9M#e(~MoO~O5fb>FE?$u&d(mDI5<}N#F?4;F+m5c! zV(9uTUv_ksg<2h*xh%s<_;(Ups#vx+1!bbS^>*Jm+weHLb{y~~~N44r5BNH2qpizGX`-o?;) zMYtF`uLy~ubGk?jU7y9!^;rN^dlzDk?#vPt)NC4@E@zf1+0mU@ip0>JT&m=Ay&S*Z zcZEdGOEDN=I*O|+bgk7a834=~`YeX7ujsa;GrF3{(Dhjioo9*d z=UjM!e0*#leC{I2j;?nxbWRtSd+e68Xs-x~q3g2%sG*CCbzPsu(Dhji zon_%+?>ci?k{w;|V(5&nTnwF6B#EKxvlzNQi=peYFk|gq?)ZD1XNhKWbY77pJG$P* z(0N6;7&@;AiJ|MW7`i@-q3g2%sP-<+=ID+rlqg~vXdg9_9o>g$&;81C buW(RoEksPT01iX{2m4%MUUct+pne`o0W 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 literal 80103 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 PB$UCW?mzvTfBgRdUY``U diff --git a/ionizer/transform_utils.py b/ionizer/transform_utils.py deleted file mode 100644 index bb879e4..0000000 --- a/ionizer/transform_utils.py +++ /dev/null @@ -1,94 +0,0 @@ -""" -Utility functions for transpiling normal gates into trapped-ion gates, such -as circuit identities. -""" - -import pennylane as qml - -from .ops import GPI -from .identity_hunter import lookup_gate_identity - - -def search_and_apply_two_gate_identities(gates_to_apply): - """Try to simplify a sequence of two gates. - - Sequences that are found are queued to the current context; if no identity - is found, we simply queue the provided sequence of gates. - - Args: - gates_to_apply (List[pennylane.Operation]): A sequence of two gates - we would like to simplify. - - Returns: - List[pennylane.Operation]: The simplified or alternate gate sequence that - will be applied within the transform. - """ - if len(gates_to_apply) != 2: - raise ValueError( - "Only sets of 2 gates can be passed to search_and_apply_two_gate_identities" - ) - - # Make sure the gates share wires - if len(qml.wires.Wires.shared_wires([gates_to_apply[0].wires, gates_to_apply[1].wires])) != 1: - raise ValueError("Gates must share wires to find identities.") - - # Special case with no fixed angles: GPI2(x) GPI2(x) = GPI(x) - if gates_to_apply[0].name == "GPI2" and gates_to_apply[1].name == "GPI2": - if qml.math.isclose(gates_to_apply[0].data[0], gates_to_apply[1].data[0]): - return [GPI(gates_to_apply[0].data[0], wires=gates_to_apply[0].wires)] - - # Search for non-special identity - with qml.QueuingManager.stop_recording(): - identity_to_apply = lookup_gate_identity(gates_to_apply) - - # If there is an identity, return it; otherwise just return the gates - return identity_to_apply if identity_to_apply is not None else gates_to_apply - - -def search_and_apply_three_gate_identities(gates_to_apply): - """Try to simplify a sequence of three gates. - - Sequences that are found are queued to the current context; if no identity - is found, we simply queue the provided sequence of gates. - - Args: - gates_to_apply (List[pennylane.Operation]): A sequence of three gates - we would like to simplify. - - Returns: - List[pennylane.Operation]: The simplified or alternate gate sequence that - will be applied within the transform. - """ - if len(gates_to_apply) != 3: - raise ValueError( - "Only sets of 3 gates can be passed to search_and_apply_three_gate_identities" - ) - - # Make sure the gates share wires - if len(qml.wires.Wires.shared_wires([gate.wires for gate in gates_to_apply])) != 1: - raise ValueError("Gates must share wires to find identities.") - - # First, check if we can apply an identity to all three gates - with qml.QueuingManager.stop_recording(): - three_gate_identity_to_apply = lookup_gate_identity(gates_to_apply) - - if three_gate_identity_to_apply is not None: - return three_gate_identity_to_apply - - # If we can't apply a 3-gate identity, see if there is a 2-gate one on the - # first two gates. - with qml.QueuingManager.stop_recording(): - identity_to_apply = search_and_apply_two_gate_identities(gates_to_apply[:2]) - - if identity_to_apply is not None: - if len(identity_to_apply) < 2: - return identity_to_apply + [gates_to_apply[2]] - - # If not, apply the first gate, then check if there is anything to be - # done between the second and third. - identity_to_apply = search_and_apply_two_gate_identities(gates_to_apply[1:]) - if identity_to_apply is not None: - if len(identity_to_apply) < 2: - return [gates_to_apply[0]] + identity_to_apply - - return gates_to_apply diff --git a/ionizer/transforms.py b/ionizer/transforms.py index 0eec67d..e306b30 100644 --- a/ionizer/transforms.py +++ b/ionizer/transforms.py @@ -14,13 +14,20 @@ # 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. -""" -Transforms for transpiling normal gates into trapped-ion gates. +"""Transforms for transpiling textbook gates into native trapped-ion gates. + +The main transform in this module, :func:`ionizer.transforms.ionize`, +performs end-to-end transpilation and optimization of circuits. It calls a +number of helper transforms which can also be used individually. + +All transforms contain a mechanism for under-the-hood equivalence checking (up +to a global phase) through the ``verify_equivalence`` flag. When set, an error +will be raised if the transpiled circuit is not equivalent to the original. For +details and example usage see :ref:`basic_usage-equivalence_validation` and +:func:`ionizer.utils.flag_non_equivalence`. -The main transform, @ionizer.transforms.ionize, will perform a full sequence of -expansions and simplifications of the tape. The transforms it uses during this -process can also be called individually. """ + from typing import Sequence, Callable from functools import partial @@ -31,10 +38,10 @@ from pennylane.tape import QuantumTape from pennylane.transforms.optimization.optimization_utils import find_next_gate -from .utils import rescale_angles, extract_gpi2_gpi_gpi2_angles +from .utils import flag_non_equivalence, rescale_angles, extract_gpi2_gpi_gpi2_angles from .decompositions import decomp_map from .ops import GPI, GPI2 -from .transform_utils import ( +from .identity_hunter import ( search_and_apply_two_gate_identities, search_and_apply_three_gate_identities, ) @@ -42,25 +49,62 @@ @qml.transform def commute_through_ms_gates( - tape: QuantumTape, direction="right" + tape: QuantumTape, direction="right", verify_equivalence=False ) -> (Sequence[QuantumTape], Callable): - """Apply a transform that passes through a tape and pushes GPI/GPI2 - gates with appropriate (commuting) angles through MS gates. + r"""Commute :math:`GPI` and :math:`GPI2` gates with special angle values + through :class:`~ionizer.ops.MS` gates. + + The following gates commute with :math:`MS` gates when applied to either + qubit in the :math:`MS` gate: :math:`GPI2(0)`, :math:`GPI2(\pm \pi)`, + :math:`GPI(0)`, :math:`GPI(\pm \pi)`. - More specifically, the following commute through MS gates on either qubit: - GPI2(0), GPI2(π), GPI2(-π), GPI(0), GPI(π), GPI(-π) + When there are multiple adjacent :math:`MS` gates, commuting :math:`GPI` and + :math:`GPI2` gates are pushed as far as possible in the specified direction + (see example). - This function is modelled off PennyLane's commute_controlled transform - https://docs.pennylane.ai/en/stable/code/api/pennylane.transforms.commute_controlled.html + This function is based on PennyLane's `commute_controlled `_ + transform. Args: tape (pennylane.QuantumTape): A quantum tape to transform. - direction (str): Which direction to push the commuting gates in. + direction (str): Either ``"right"`` (default) or ``"left"`` to indicate + the direction gates should move (from a circuit diagram perspective). + verify_equivalence (bool): Whether to perform background equivalence + checking (up to global phase) of the circuit before and after the + transform. Returns: qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], function]: The transformed circuit as described in :func:`qml.transform `. + + **Example** + + .. code:: + + import pennylane as qml + from pennylane import numpy as np + from functools import partial + + dev = qml.device("default.qubit", wires=3) + + @qml.qnode(dev) + @partial(commute_through_ms_gates, direction="left") + def circuit(): + MS(wires=[0, 1]) + MS(wires=[1, 2]) + GPI2(np.pi, wires=0) + GPI(0, wires=1) + GPI(0.3, wires=2) + return qml.probs() + + .. code:: + + >>> qml.draw(circuit)() + 0: ──GPI2(3.14)─╭MS────────────────┤ Probs + 1: ──GPI(0.00)──╰MS─╭MS────────────┤ Probs + 2: ─────────────────╰MS──GPI(0.30)─┤ Probs + """ if direction not in ["left", "right"]: raise ValueError( @@ -75,7 +119,7 @@ def commute_through_ms_gates( list_copy = list_copy[::-1] with qml.QueuingManager.stop_recording(): - with qml.tape.QuantumTape() as commuted_tape: + with qml.tape.QuantumTape() as _: while len(list_copy) > 0: current_gate = list_copy[0] list_copy.pop(0) @@ -117,6 +161,9 @@ def commute_through_ms_gates( new_tape = type(tape)(new_operations, tape.measurements, shots=tape.shots) + if verify_equivalence: + flag_non_equivalence(tape, new_tape) + def null_postprocessing(results): """A postprocessing function returned by a transform that only converts the batch of results into a result for a single ``QuantumTape``. @@ -127,25 +174,64 @@ def null_postprocessing(results): @qml.transform -def virtualize_rz_gates(tape: QuantumTape) -> (Sequence[QuantumTape], Callable): - """When dealing with GPI/GPI2/MS gates, RZ gates can be implemented virtually - by pushing them through such gates and simply adjusting the phases of the - gates we pushed them through: - - GPI(x) RZ(z) = GPI(x - z/2) - - RZ(z) GPI(x) = GPI(x + z/2) - - GPI2(x) RZ(z) = RZ(z) GPI2(x - z) - - RZ(z) GPI2(x) = GPI2(x + z) RZ(z) - - This transform rolls through a tape, and adjusts the circuits so that - all the RZs get implemented virtually. +def virtualize_rz_gates(tape: QuantumTape, verify_equivalence=False) -> (Sequence[QuantumTape], Callable): + r"""Apply :math:`RZ` gates virtually by adjusting the phase of adjacent + :math:`GPI` and :math:`GPI2` gates. + + This transform reads a circuit from left to right, and applies the + following circuit identities (expressed in matrix order): + + - :math:`GPI(x) RZ(z) = GPI(x - z/2)` + - :math:`GPI2(x) RZ(z) = RZ(z) GPI2(x - z)` + + :math:`RZ` are pushed as far right as possible, until :math:`MS` gates are + encountered or the end of the circuit is reached. Any :math:`RZ(\phi)` that + are not absorbed into native gates are then implemented as + + .. math:: RZ(\phi) = GPI(0) GPI(-\phi/2). Args: - tape (pennylane.QuantumTape): A quantum tape to transform. + tape (pennylane.QuantumTape): A quantum tape to transform + verify_equivalence (bool): Whether to perform background equivalence + checking (up to global phase) of the circuit before and after the + transform. Returns: qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], function]: The transformed circuit as described in :func:`qml.transform `. + + **Example** + + .. code:: + + import pennylane as qml + from pennylane import numpy as np + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + @virtualize_rz_gates + def circuit(): + qml.RZ(0.3, wires=0) + GPI(0.5, wires=0) + + GPI2(0.2, wires=1) + qml.RZ(np.pi/2, wires=1) + GPI2(0.4, wires=1) + GPI(-np.pi/2, wires=1) + + MS(wires=[0, 1]) + + qml.RZ(0.8, wires=1) + return qml.probs() + + .. code:: + + >>> qml.draw(circuit)() + 0: ──GPI(0.35)───────────────────────────╭MS────────────────────────┤ Probs + 1: ──GPI2(0.20)──GPI2(-1.17)──GPI(-2.36)─╰MS──GPI(-0.40)──GPI(0.00)─┤ Probs + """ list_copy = tape.operations.copy() new_operations = [] @@ -211,7 +297,10 @@ def virtualize_rz_gates(tape: QuantumTape) -> (Sequence[QuantumTape], Callable): # this way we can commute it through. if apply_accumulated_phase_gate: new_operations.append( - GPI(-rescale_angles(accumulated_rz_phase) / 2, wires=current_gate.wires) + GPI( + -rescale_angles(accumulated_rz_phase) / 2, + wires=current_gate.wires, + ) ) new_operations.append(GPI(0.0, wires=current_gate.wires)) @@ -220,6 +309,9 @@ def virtualize_rz_gates(tape: QuantumTape) -> (Sequence[QuantumTape], Callable): new_tape = type(tape)(new_operations, tape.measurements, shots=tape.shots) + if verify_equivalence: + flag_non_equivalence(tape, new_tape) + def null_postprocessing(results): """A postprocessing function returned by a transform that only converts the batch of results into a result for a single ``QuantumTape``. @@ -230,20 +322,70 @@ def null_postprocessing(results): @qml.transform -def single_qubit_fusion_gpi(tape: QuantumTape) -> (Sequence[QuantumTape], Callable): - """Perform single-qubit fusion of all sequences of single-qubit gates into - no more than 3 GPI/GPI2 gates. +def single_qubit_fusion_gpi(tape: QuantumTape, verify_equivalence=False) -> (Sequence[QuantumTape], Callable): + r"""Simplify sequences of :math:`GPI` and :math:`GPI2` gates using gate + fusion and circuit identities. + + Any sequence of more than 3 gates will be fused and re-implemented up to + global phase using :math:`GPI` and :math:`GPI2` (see + :func:`ionizer.utils.extract_gpi2_gpi_gpi2_angles`). + + Sequences of two or three gates (including those obtained through gate + fusion) are then checked against the database of known circuit identities + for simplifications (see :func:`ionizer.identity_hunter.lookup_gate_identity`). - This transform is based on PennyLane's single_qubit_fusion transform. - https://docs.pennylane.ai/en/stable/code/api/pennylane.transforms.single_qubit_fusion.html + This transform is based on PennyLane's `single_qubit_fusion + `_ + transform. Args: tape (pennylane.QuantumTape): A quantum tape to transform. + verify_equivalence (bool): Whether to perform background equivalence + checking (up to global phase) of the circuit before and after the + transform. Returns: qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], function]: The transformed circuit as described in :func:`qml.transform `. + + **Example** + + .. code:: + + import pennylane as qml + from pennylane import numpy as np + + dev = qml.device("default.qubit", wires=3) + + @qml.qnode(dev) + @single_qubit_fusion_gpi + def circuit(): + # Known circuit identity + GPI(np.pi/4, wires=0) + GPI2(-3 * np.pi/4, wires=0) + + # Already three gates, but no known identity + GPI2(0.2, wires=1) + GPI2(0.4, wires=1) + GPI(-np.pi/2, wires=1) + + # Squished down to three gates + GPI2(0.1, wires=2) + GPI2(0.2, wires=2) + GPI(0.3, wires=2) + GPI(0.4, wires=2) + GPI2(0.5, wires=2) + + return qml.probs() + + .. code:: + + >>> qml.draw(circuit)() + 0: ──GPI2(0.79)───────────────────────────┤ Probs + 1: ──GPI2(0.20)───GPI2(0.40)──GPI(-1.57)──┤ Probs + 2: ──GPI2(-1.57)──GPI(2.65)───GPI2(-0.97)─┤ Probs + """ # Make a working copy of the list to traverse list_copy = tape.operations.copy() @@ -305,15 +447,18 @@ def single_qubit_fusion_gpi(tape: QuantumTape) -> (Sequence[QuantumTape], Callab continue # Construct the three new operations to apply - first_gate = GPI2(gamma, wires=current_gate.wires) - second_gate = GPI(beta, wires=current_gate.wires) - third_gate = GPI2(alpha, wires=current_gate.wires) - - gates_to_apply = [first_gate, second_gate, third_gate] + gates_to_apply = [ + GPI2(gamma, wires=current_gate.wires), + GPI(beta, wires=current_gate.wires), + GPI2(alpha, wires=current_gate.wires), + ] new_operations.extend(search_and_apply_three_gate_identities(gates_to_apply)) new_tape = type(tape)(new_operations, tape.measurements, shots=tape.shots) + if verify_equivalence: + flag_non_equivalence(tape, new_tape) + def null_postprocessing(results): """A postprocessing function returned by a transform that only converts the batch of results into a result for a single ``QuantumTape``. @@ -324,27 +469,73 @@ def null_postprocessing(results): @qml.transform -def convert_to_gpi(tape: QuantumTape, exclude_list=[]) -> (Sequence[QuantumTape], Callable): - """Transpile a tape directly to native trapped ion gates. - - Any operation without a decomposition in decompositions.py will remain - as-is. +def convert_to_gpi(tape: QuantumTape, exclude_list=None, verify_equivalence=False) -> (Sequence[QuantumTape], Callable): + r"""Transpile desired gates in a circuit to trapped-ion gates. Args: tape (pennylane.QuantumTape): A quantum tape to transform. exclude_list (list[str]): A list of names of gates to exclude from conversion (see the ionize transform for an example). + verify_equivalence (bool): Whether to perform background equivalence + checking (up to global phase) of the circuit before and after the + transform. Returns: qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], function]: The transformed circuit as described in :func:`qml.transform `. + + **Example** + + .. code:: + + import pennylane as qml + from pennylane import numpy as np + + dev = qml.device("default.qubit", wires=3) + + @qml.qnode(dev) + @partial(convert_to_gpi, exclude_list=["IsingYY"]) + def circuit(): + # Gates with known decompositions + qml.Hadamard(wires=0) + qml.PauliX(wires=1) + qml.RX(0.2, wires=2) + + # Should not be decomposed + qml.IsingYY(0.1, wires=[0, 1]) + qml.IsingYY(0.2, wires=[0, 2]) + + # Gets expanded into two RY and two CNOT gates, which are then decomposed + qml.CRY(0.3, wires=[0, 1]) + + return qml.probs() + + .. code:: + + >>> qml.draw(circuit)() + 0: ──GPI(0.00)───GPI2(-1.57)─╭IsingYY(0.10)─╭IsingYY(0.20)──GPI2(1.57)────────────────────────╭MS───GPI2(3.14)──GPI2(-1.57)──GPI2(1.57)─────────────╭MS──GPI2(3.14)──GPI2(-1.57)─┤ Probs + 1: ──GPI(0.00)───────────────╰IsingYY(0.10)─│───────────────GPI2(3.14)──GPI(0.07)──GPI2(3.14)─╰MS───GPI2(3.14)──GPI2(3.14)───GPI(-0.07)──GPI2(3.14)─╰MS──GPI2(3.14)──────────────┤ Probs + 2: ──GPI2(1.57)──GPI(-1.47)───GPI2(1.57)────╰IsingYY(0.20)───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ Probs + """ + if exclude_list is None: + exclude_list = [] + new_operations = [] + # We define a custom expansion function here to convert everything except + # the gates identified in exclude_list to gates with known decompositions. + def stop_at(op): + return op.name in decomp_map or op.name in exclude_list + + custom_expand_fn = qml.transforms.create_expand_fn(depth=9, stop_at=stop_at) + with qml.QueuingManager.stop_recording(): - for op in tape.operations: - if op.name not in exclude_list and op.name in decomp_map.keys(): + expanded_tape = custom_expand_fn(tape) + + for op in expanded_tape.operations: + if op.name not in exclude_list and op.name in decomp_map: if op.num_params > 0: new_operations.extend(decomp_map[op.name](*op.data, op.wires)) else: @@ -354,6 +545,9 @@ def convert_to_gpi(tape: QuantumTape, exclude_list=[]) -> (Sequence[QuantumTape] new_tape = type(tape)(new_operations, tape.measurements, shots=tape.shots) + if verify_equivalence: + flag_non_equivalence(tape, new_tape) + def null_postprocessing(results): """A postprocessing function returned by a transform that only converts the batch of results into a result for a single ``QuantumTape``. @@ -364,40 +558,78 @@ def null_postprocessing(results): @qml.transform -def ionize(tape: QuantumTape) -> (Sequence[QuantumTape], Callable): - """A full set of transpilation passes to apply to convert the circuit - into native gates and optimize it. - - It performs the following sequence of steps: - - Decomposes all operations into Paulis/Pauli rotations, Hadamard, and CNOT - - Merges all single-qubit rotations - - Converts everything except RZ to GPI/GPI2/MS gates - - Virtually applies all RZ gates - - Repeatedly applies gate fusion and commutation through MS gate - which performs simplification based on some circuit identities. - +def ionize(tape: QuantumTape, verify_equivalence=False) -> (Sequence[QuantumTape], Callable): + r"""Apply a sequence of passes to transpile and optimize a circuit + over the trapped-ion gate set :math:`GPI`, :math:`GPI2`, and :math:`MS`. + + The following sequence of passes is performed: + + - Decompose all operations into Paulis/Pauli rotations, Hadamard, and :math:`CNOT` + - Cancel inverses and merge single-qubit rotations + - Convert everything except :math:`RZ` to :math:`GPI`, :math:`GPI2`, and :math:`MS` gates + - Virtually apply :math:`RZ` gates + - Repeatedly apply single-qubit gate fusion and commutation through + :math:`MS` gates, and perform simplification based on a database of + circuit identities. + + .. note:: + + When ``verify_equivalence`` is set to ``True``, equivalence checking up + to a global phase is performed with respect to the initial and final + circuit matrices only. It is not checked for intermediate transforms. + Args: tape (pennylane.QuantumTape): A quantum tape to transform. + verify_equivalence (bool): Whether to perform background equivalence + checking (up to global phase) of the circuit before and after the + transform. Returns: qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], function]: The transformed circuit as described in :func:`qml.transform `. + + **Example** + + .. code:: + + import pennylane as qml + from ionizer.transforms import ionize + + dev = qml.device("default.qubit", wires=3) + + @qml.qnode(dev) + @ionize + def circuit(): + qml.Hadamard(wires=0) + qml.PauliX(wires=1) + qml.RX(0.2, wires=2) + qml.CRY(0.3, wires=[0, 1]) + return qml.probs() + + .. code:: + + >>> qml.draw(circuit)() + 0: ─────────────────────────────────────╭MS──────────────────────────────────────╭MS──GPI2(-1.57)─┤ Probs + 1: ──GPI2(1.57)──GPI(-0.86)──GPI2(1.42)─╰MS──GPI2(-1.57)──GPI(2.43)──GPI2(-1.42)─╰MS──────────────┤ Probs + 2: ──GPI2(1.57)──GPI(-1.47)──GPI2(1.57)───────────────────────────────────────────────────────────┤ Probs + """ - # The tape will first be expanded into known operations def stop_at(op): - return op.name in list(decomp_map.keys()) + return op.name in decomp_map custom_expand_fn = qml.transforms.create_expand_fn(depth=9, stop_at=stop_at) with qml.QueuingManager.stop_recording(): # Initial set of passes to decompose and translate the tape and virtualize RZ optimized_tape = custom_expand_fn(tape) + optimized_tape, _ = qml.transforms.cancel_inverses(optimized_tape) optimized_tape, _ = qml.transforms.merge_rotations(optimized_tape) optimized_tape, _ = partial(convert_to_gpi, exclude_list=["RZ"])(optimized_tape[0]) optimized_tape, _ = virtualize_rz_gates(optimized_tape[0]) + # Actual optimization passes # TODO: how many iterations do we actually have to do? for _ in range(5): optimized_tape, _ = partial(commute_through_ms_gates, direction="left")( @@ -407,6 +639,9 @@ def stop_at(op): new_tape = type(tape)(optimized_tape[0].operations, tape.measurements, shots=tape.shots) + if verify_equivalence: + flag_non_equivalence(tape, new_tape) + def null_postprocessing(results): """A postprocessing function returned by a transform that only converts the batch of results into a result for a single ``QuantumTape``. diff --git a/ionizer/utils.py b/ionizer/utils.py index 0b22604..c11ba2d 100644 --- a/ionizer/utils.py +++ b/ionizer/utils.py @@ -17,22 +17,33 @@ """ Utility functions. """ + +import pennylane as qml import numpy as np from pennylane import math -def are_mats_equivalent(mat1, mat2): - """Checks the equivalence of two unitary matrices. +def are_mats_equivalent(unitary1, unitary2): + r"""Checks the equivalence of two unitary matrices up to a global phase. - Args: - mat1 (tensor): First unitary matrix. - mat2 (tensor): Second unitary matrix. + Args: + unitary1 (tensor): First unitary matrix. + unitary2 (tensor): Second unitary matrix. Returns: bool: True if the two matrices are equivalent up to a global phase, False otherwise. + + **Example** + + .. code:: + + >>> matrix_T = np.diag([1, 1j]) + >>> matrix_RZ = np.diag([np.exp(-1j * np.pi / 4), np.exp(1j * np.pi / 4)]) + >>> are_mats_equivalent(matrix_T, matrix_RZ) + True """ - mat_product = math.dot(mat1, math.conj(math.T(mat2))) + mat_product = math.dot(unitary1, math.conj(math.T(unitary2))) # If the top-left entry is not 0, divide everything by it and test against identity if not math.isclose(mat_product[0, 0], 0.0): @@ -44,18 +55,85 @@ def are_mats_equivalent(mat1, mat2): return False +def flag_non_equivalence(tape1, tape2): + """Check equivalence of two circuits up to a global phase. + + Args: + tape1 (pennylane.QuantumTape): a quantum tape + tape2 (pennylane.QuantumTape): quantum tape to compare with ``tape1`` + + Raises: + ValueError if the two circuits are not equivalent. + + **Example** + + .. code:: + + with qml.tape.QuantumTape() as tape1: + qml.Hadamard(wires=0) + qml.CNOT(wires=[0, 1]) + qml.T(wires=1) + + with qml.tape.QuantumTape() as tape2_equiv: + qml.Hadamard(wires=0) + qml.CNOT(wires=[0, 1]) + qml.RZ(np.pi/4, wires=1) + + with qml.tape.QuantumTape() as tape3_nonequiv: + qml.Hadamard(wires=0) + qml.CNOT(wires=[0, 1]) + qml.RZ(np.pi/2, wires=1) + + .. code:: + + >>> flag_non_equivalence(tape1, tape2_equiv) + >>> flag_non_equivalence(tape1, tape3_nonequiv) + Traceback (most recent call last): + File "", line 1, in + File "ionizer/utils.py", line 98, in flag_non_equivalence + raise ValueError("Quantum circuits are not equivalent after transform.") + ValueError: Quantum circuits are not equivalent after transform. + + """ + # Compute matrix representation using a consistent wire order + joint_wires = qml.wires.Wires.all_wires([tape1.wires, tape2.wires]) + matrix_1 = qml.matrix(tape1, wire_order=joint_wires) + matrix_2 = qml.matrix(tape2, wire_order=joint_wires) + + if not are_mats_equivalent(matrix_1, matrix_2): + raise ValueError("Quantum circuits are not equivalent after transform.") + + + def rescale_angles(angles, renormalize_for_json=False): - r"""Rescale gate rotation angles into a fixed range between -np.pi and np.pi. + r"""Rescale gate rotation angles into a fixed range. + + By default, rescales between :math:`-\pi` and :math:`\pi`. However, IonQ's + native gate parameters are defined in terms of "turns", rather than radians, + where 1 turn is equivalent to :math:`2\pi`. Setting ``renormalize_for_json`` + to ``True`` converts radians to turns. See the `IonQ + documentation `_ + for more information. Args: angles (tensor): The angles to rescale. - renormalize_for_json (bool): By default, we rescale into the range -np.pi to - np.pi. If this is set to True, rescale instead into the range -1 to - 1 (-2\pi to 2\pi) as this the range of angles accepted by IonQ's - native gate input specs. + renormalize_for_json (bool): If ``True``, rescale angles into the range + :math:`-1` to :math:`1` (:math:`-2\pi` to :math:`2\pi`). Otherwise, + rescale to within :math:`-\pi` to :math:`\pi`. + + Returns: + tensor: The rescaled angles. + + **Example** + + .. code:: + + >>> angles = np.array([-4, -2, 0, -2, 4]) + >>> rescale_angles(angles) + [ 2.28318531 -2. 0. -2. -2.28318531] + >>> rescale_angles(angles, renormalize_for_json=True) + [ 0.36338023 -0.31830989 0. -0.31830989 -0.36338023] - Return: - (tensor): The rescaled angles. """ rescaled_angles = math.arctan2(math.sin(angles), math.cos(angles)) @@ -65,26 +143,44 @@ def rescale_angles(angles, renormalize_for_json=False): return rescaled_angles -def extract_gpi2_gpi_gpi2_angles(U): - r"""Given a matrix U, recovers a set of three angles alpha, beta, and - gamma such that - U = GPI2(alpha) GPI(beta) GPI2(gamma) +def extract_gpi2_gpi_gpi2_angles(unitary): + r"""Given unitary matrix, recovers a set of three angles :math:`\alpha`, + :math:`\beta`, and :math:`\gamma` such that + + .. math:: + + U = GPI2(\alpha) GPI(\beta) GPI2(\gamma) + up to a global phase. - This function is loosely based on the zyz_decomposition function implemented - in the PennyLane decomposition transform, adjusted for a different gate set. - https://docs.pennylane.ai/en/stable/code/api/pennylane.transforms.zyz_decomposition.html + This function is loosely based on PennyLane's + `zyz_decomposition `_. Args: - U (tensor): A unitary matrix. + unitary (tensor): A unitary matrix. Returns: - tensor: Rotation angles for the GPI/GPI2 gates. The order of the - returned angles corresponds to the order in which they would be - implemented in the circuit. + tensor: Rotation angles for the :math:`GPI` and :math:`GPI2` + gates. The order of the returned angles corresponds to the order in + which they would appear in a circuit, i.e., ``[gamma, beta, alpha]``. + + **Example** + + .. code:: + + >>> matrix_RZ = np.diag([np.exp(-1j * np.pi / 4), np.exp(1j * np.pi / 4)]) + >>> angles = extract_gpi2_gpi_gpi2_angles(matrix_RZ) + >>> angles + [ 2.35619449 3.14159265 -2.35619449] + >>> recovered_matrix = np.linalg.multi_dot( + ... [op.compute_matrix(angle) for op, angle in zip([GPI2, GPI, GPI2], angles[::-1])] + ... ) + >>> are_mats_equivalent(matrix_RZ, recovered_matrix) + True + """ - det = math.angle(math.linalg.det(U)) - su2_mat = math.exp(-1j * det / 2) * U + det = math.angle(math.linalg.det(unitary)) + su2_mat = math.exp(-1j * det / 2) * unitary phase_00 = math.angle(su2_mat[0, 0]) phase_10 = math.angle(su2_mat[1, 0]) @@ -100,8 +196,16 @@ def extract_gpi2_gpi_gpi2_angles(U): def tape_to_json(tape, name, shots=100, target="simulator"): - """Convert a quantum tape expressed in terms of GPI/GPI2/MS operations - into a JSON object suitable for job submission to hardware. + """Convert a quantum tape consisting of :math:`GPI`, :math:`GPI2` and + :math:`MS` operations into a JSON object suitable for job submission to hardware. + + Please see the `IonQ webpage + `_ for full documentation of the + job submission API. + + Note that this function is not tested against the API in an automated, or + manual way. If this function needs to be updated to work with API changes, + please `open an issue `_. Args: tape (QuantumTape): The quantum tape of the circuit to send. @@ -111,7 +215,40 @@ def tape_to_json(tape, name, shots=100, target="simulator"): particular hardware device. Returns: - Dict: JSON formatted for submission to hardware. + Dict: JSON formatted for submission to IonQ hardware. + + **Example** + + .. code:: + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + @ionize + def circuit(): + qml.Hadamard(wires=0) + qml.CNOT(wires=[0, 1]) + return qml.probs() + + # Note that the circuit must be executed once to generate the tape. + circuit() + + json_object = tape_to_json(circuit.qtape, "job_name", 1024, "simulator") + + .. code:: + + >>> pprint(json_object) + {'input': {'circuit': [{'gate': 'gpi2', 'phase': 0.0, 'target': 0}, + {'gate': 'gpi2', 'phase': 0.5, 'target': 1}, + {'gate': 'ms', 'phases': [0, 0], 'targets': [0, 1]}, + {'gate': 'gpi2', 'phase': -0.25, 'target': 0}], + 'gateset': 'native', + 'qubits': 2}, + 'lang': 'json', + 'name': 'job_name', + 'shots': 1024, + 'target': 'simulator'} + """ circuit_json = {} @@ -120,9 +257,9 @@ def tape_to_json(tape, name, shots=100, target="simulator"): circuit_json["target"] = target circuit_json["name"] = name - circuit_json["body"] = {} - circuit_json["body"]["gateset"] = "native" - circuit_json["body"]["qubits"] = len(tape.wires) + circuit_json["input"] = {} + circuit_json["input"]["gateset"] = "native" + circuit_json["input"]["qubits"] = len(tape.wires) circuit_op_list = [] @@ -140,6 +277,6 @@ def tape_to_json(tape, name, shots=100, target="simulator"): circuit_op_list.append(gate_dict) - circuit_json["body"]["circuit"] = circuit_op_list + circuit_json["input"]["circuit"] = circuit_op_list return circuit_json diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..ecb041e --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1181 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "alabaster" +version = "1.0.0" +description = "A light, configurable Sphinx theme" +optional = false +python-versions = ">=3.10" +files = [ + {file = "alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b"}, + {file = "alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e"}, +] + +[[package]] +name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = "*" +files = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] + +[[package]] +name = "autograd" +version = "1.6.2" +description = "Efficiently computes derivatives of numpy code." +optional = false +python-versions = "*" +files = [ + {file = "autograd-1.6.2-py3-none-any.whl", hash = "sha256:208dde2a938e63b4f8f5049b1985505139e529068b0d26f8cd7771fd3eb145d5"}, + {file = "autograd-1.6.2.tar.gz", hash = "sha256:8731e08a0c4e389d8695a40072ada4512641c113b6cace8f4cfbe8eb7e9aedeb"}, +] + +[package.dependencies] +future = ">=0.15.2" +numpy = ">=1.12" + +[[package]] +name = "autoray" +version = "0.6.12" +description = "Abstract your array operations." +optional = false +python-versions = ">=3.8" +files = [ + {file = "autoray-0.6.12-py3-none-any.whl", hash = "sha256:3ed7a4abcec052bcbb4f0447c426d0a0b9b9fa03ab71e76eaa77747ca43ac3e2"}, + {file = "autoray-0.6.12.tar.gz", hash = "sha256:721328aa06fc3577155d988052614a7b4bd6e4d01b340695344031ee4abd2a1e"}, +] + +[package.extras] +docs = ["astroid (<3)", "furo", "ipython (!=8.7.0)", "myst-nb", "setuptools-scm", "sphinx (>=2.0)", "sphinx-autoapi", "sphinx-copybutton"] +tests = ["coverage", "numpy", "pytest", "pytest-cov"] + +[[package]] +name = "babel" +version = "2.16.0" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.8" +files = [ + {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, + {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, +] + +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] + +[[package]] +name = "black" +version = "24.2.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-24.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6981eae48b3b33399c8757036c7f5d48a535b962a7c2310d19361edeef64ce29"}, + {file = "black-24.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d533d5e3259720fdbc1b37444491b024003e012c5173f7d06825a77508085430"}, + {file = "black-24.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61a0391772490ddfb8a693c067df1ef5227257e72b0e4108482b8d41b5aee13f"}, + {file = "black-24.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:992e451b04667116680cb88f63449267c13e1ad134f30087dec8527242e9862a"}, + {file = "black-24.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:163baf4ef40e6897a2a9b83890e59141cc8c2a98f2dda5080dc15c00ee1e62cd"}, + {file = "black-24.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e37c99f89929af50ffaf912454b3e3b47fd64109659026b678c091a4cd450fb2"}, + {file = "black-24.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9de21bafcba9683853f6c96c2d515e364aee631b178eaa5145fc1c61a3cc92"}, + {file = "black-24.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:9db528bccb9e8e20c08e716b3b09c6bdd64da0dd129b11e160bf082d4642ac23"}, + {file = "black-24.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d84f29eb3ee44859052073b7636533ec995bd0f64e2fb43aeceefc70090e752b"}, + {file = "black-24.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e08fb9a15c914b81dd734ddd7fb10513016e5ce7e6704bdd5e1251ceee51ac9"}, + {file = "black-24.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:810d445ae6069ce64030c78ff6127cd9cd178a9ac3361435708b907d8a04c693"}, + {file = "black-24.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ba15742a13de85e9b8f3239c8f807723991fbfae24bad92d34a2b12e81904982"}, + {file = "black-24.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7e53a8c630f71db01b28cd9602a1ada68c937cbf2c333e6ed041390d6968faf4"}, + {file = "black-24.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:93601c2deb321b4bad8f95df408e3fb3943d85012dddb6121336b8e24a0d1218"}, + {file = "black-24.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0057f800de6acc4407fe75bb147b0c2b5cbb7c3ed110d3e5999cd01184d53b0"}, + {file = "black-24.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:faf2ee02e6612577ba0181f4347bcbcf591eb122f7841ae5ba233d12c39dcb4d"}, + {file = "black-24.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:057c3dc602eaa6fdc451069bd027a1b2635028b575a6c3acfd63193ced20d9c8"}, + {file = "black-24.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:08654d0797e65f2423f850fc8e16a0ce50925f9337fb4a4a176a7aa4026e63f8"}, + {file = "black-24.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca610d29415ee1a30a3f30fab7a8f4144e9d34c89a235d81292a1edb2b55f540"}, + {file = "black-24.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:4dd76e9468d5536abd40ffbc7a247f83b2324f0c050556d9c371c2b9a9a95e31"}, + {file = "black-24.2.0-py3-none-any.whl", hash = "sha256:e8a6ae970537e67830776488bca52000eaa37fa63b9988e8c487458d9cd5ace6"}, + {file = "black-24.2.0.tar.gz", hash = "sha256:bce4f25c27c3435e4dace4815bcb2008b87e167e3bf4ee47ccdc5ce906eb4894"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "cachetools" +version = "5.5.0" +description = "Extensible memoizing collections and decorators" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292"}, + {file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"}, +] + +[[package]] +name = "certifi" +version = "2024.7.4" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "distlib" +version = "0.3.8" +description = "Distribution utilities" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, +] + +[[package]] +name = "docutils" +version = "0.21.2" +description = "Docutils -- Python Documentation Utilities" +optional = false +python-versions = ">=3.9" +files = [ + {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, + {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "filelock" +version = "3.15.4" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, + {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] +typing = ["typing-extensions (>=4.8)"] + +[[package]] +name = "future" +version = "1.0.0" +description = "Clean single-source support for Python 3 and 2" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216"}, + {file = "future-1.0.0.tar.gz", hash = "sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05"}, +] + +[[package]] +name = "identify" +version = "2.6.0" +description = "File identification library for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"}, + {file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"}, +] + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "idna" +version = "3.7" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "jinja2" +version = "3.1.4" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "networkx" +version = "3.3" +description = "Python package for creating and manipulating graphs and networks" +optional = false +python-versions = ">=3.10" +files = [ + {file = "networkx-3.3-py3-none-any.whl", hash = "sha256:28575580c6ebdaf4505b22c6256a2b9de86b316dc63ba9e93abde3d78dfdbcf2"}, + {file = "networkx-3.3.tar.gz", hash = "sha256:0c127d8b2f4865f59ae9cb8aafcd60b5c70f3241ebd66f7defad7c4ab90126c9"}, +] + +[package.extras] +default = ["matplotlib (>=3.6)", "numpy (>=1.23)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"] +developer = ["changelist (==0.5)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] +doc = ["myst-nb (>=1.0)", "numpydoc (>=1.7)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"] +extra = ["lxml (>=4.6)", "pydot (>=2.0)", "pygraphviz (>=1.12)", "sympy (>=1.10)"] +test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] + +[[package]] +name = "nodeenv" +version = "1.9.1" +description = "Node.js virtual environment builder" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, +] + +[[package]] +name = "numpy" +version = "1.26.4" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, + {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, + {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, + {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, + {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, + {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, + {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, + {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, + {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, + {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, +] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "pennylane" +version = "0.37.0" +description = "PennyLane is a cross-platform Python library for quantum computing, quantum machine learning, and quantum chemistry. Train a quantum computer the same way as a neural network." +optional = false +python-versions = "*" +files = [ + {file = "PennyLane-0.37.0-py3-none-any.whl", hash = "sha256:0ee5e468918c9e4c757ede70f9b87723020e457984779d2b06117f395669e9e4"}, +] + +[package.dependencies] +appdirs = "*" +autograd = "*" +autoray = ">=0.6.11" +cachetools = "*" +networkx = "*" +numpy = "<2.0" +packaging = "*" +pennylane-lightning = ">=0.37" +requests = "*" +rustworkx = "*" +scipy = "*" +semantic-version = ">=2.7" +toml = "*" +typing-extensions = "*" + +[package.extras] +kernels = ["cvxopt", "cvxpy"] + +[[package]] +name = "pennylane-lightning" +version = "0.37.0" +description = "PennyLane-Lightning plugin" +optional = false +python-versions = "*" +files = [ + {file = "PennyLane_Lightning-0.37.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a54eb4224be1c800d1817d66d0813732cd461294c37fe772d986a3d41a9d918c"}, + {file = "PennyLane_Lightning-0.37.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:3b3442cf2a2b01ddefcc325f33df7abd43bf13783a76af5d6685a29f0ac1a2e7"}, + {file = "PennyLane_Lightning-0.37.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:1df506b2be94eb813326cbd4c84167699924c60163abdfb407ade6937574dddf"}, + {file = "PennyLane_Lightning-0.37.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:b21f845f47a0f71b90d0ebcea3dae0bbbe84df26b3510938ac88672330561f35"}, + {file = "PennyLane_Lightning-0.37.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1b2456f8c20f77f298d9dd3778dc851a26a039d3749e7f81f2dce6c669b467e0"}, + {file = "PennyLane_Lightning-0.37.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c2490cbda5db9d2bf632cf8ecd18303aaeb8c34853e9b28a6b9e57dd16f52f4"}, + {file = "PennyLane_Lightning-0.37.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34d2fdcf4e949296e95a93cc09343a1d0038850264ddcf76cdec04b695eaf949"}, + {file = "PennyLane_Lightning-0.37.0-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:9ef977253a6933504392ba375bd657d66233d2192913fca842ec4d080c421147"}, + {file = "PennyLane_Lightning-0.37.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2b2f2f197a4403e3f724718bc4e32f64ca6f59d7e4b79e22cb48a3b5d9fdbb4c"}, + {file = "PennyLane_Lightning-0.37.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:6397d4b7c57bf5c2ae95dce21faef01266c223b3e53fa0c1eda570ccd5549c43"}, + {file = "PennyLane_Lightning-0.37.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:f54a15429107d0c3769db27ab02a1adcfa22fb04400c3b46930de2a43c609944"}, + {file = "PennyLane_Lightning-0.37.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf12bdd2380f709390fcee7959bd3c7878d754256528764902d0e6893b442bd2"}, + {file = "PennyLane_Lightning-0.37.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:56179672163cfeab504d7e5517086710f1d22e1f0411cc1d545b484e6bafc805"}, + {file = "PennyLane_Lightning-0.37.0-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:82582ed3351f19730adc25a00c19c50e4201e84fd8929ddd340ffa64b67b8d23"}, + {file = "PennyLane_Lightning-0.37.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:a2185aee111b3b27eb32e4dbbe980d9222a021c525ffb4f3f81eb50afc68509a"}, + {file = "PennyLane_Lightning-0.37.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:e05b7054d734ca4ad9ec6fec60b8d3cb338610717899616a62af3014d61e6c3c"}, + {file = "PennyLane_Lightning-0.37.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:0b34ec2802734805f10c66bd6a21ab6a1c3b2e4adc162dd295bd46e7d6c87074"}, + {file = "PennyLane_Lightning-0.37.0-cp312-cp312-win_amd64.whl", hash = "sha256:37c4f0153f43f7f32939c9ddcee79a7a60d55ffc72497524d65ffd5dea6ff8ae"}, + {file = "PennyLane_Lightning-0.37.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f14d127dc861d2efaa70dfafd6b46be298d3a9d42e75f7d72979f421a08cf574"}, + {file = "PennyLane_Lightning-0.37.0-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:3c3bffcf47d569723e7e0024f13ff65a321c40a59e830cd6fbbfe2d1df205434"}, + {file = "PennyLane_Lightning-0.37.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:58a7682566786de46808ef76c3cdfcf807edea6840a792f0d3250c3c185de0d8"}, + {file = "PennyLane_Lightning-0.37.0-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:c079c57aa9fb053bd9157752cff6eafccb00b9190fa94b8d3c991b21314131a7"}, + {file = "PennyLane_Lightning-0.37.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:bced963db081ed64df1487f3cbd9ddc0146d127c849d8502e7d0038c3237ec53"}, + {file = "PennyLane_Lightning-0.37.0-cp39-cp39-win_amd64.whl", hash = "sha256:348271c9249f0617e665e77591ec32ce74a9390dba428d81038797907db771b3"}, + {file = "PennyLane_Lightning-0.37.0-py3-none-any.whl", hash = "sha256:f2c594cd7d915ace6a48ca9f0475cf0b5221276bd61d029d2a8ef65f0c767e00"}, + {file = "pennylane_lightning-0.37.0.tar.gz", hash = "sha256:df426c5f3baf73fab5c82588abcf8ac083673995c8c71a2544cf29582ae86c6b"}, +] + +[package.dependencies] +pennylane = ">=0.36" + +[package.extras] +gpu = ["pennylane-lightning-gpu"] +kokkos = ["pennylane-lightning-kokkos"] + +[[package]] +name = "platformdirs" +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pre-commit" +version = "3.7.1" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.9" +files = [ + {file = "pre_commit-3.7.1-py2.py3-none-any.whl", hash = "sha256:fae36fd1d7ad7d6a5a1c0b0d5adb2ed1a3bda5a21bf6c3e5372073d7a11cd4c5"}, + {file = "pre_commit-3.7.1.tar.gz", hash = "sha256:8ca3ad567bc78a4972a3f1a477e94a79d4597e8140a6e0b651c5e33899c3654a"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + +[[package]] +name = "pygments" +version = "2.18.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pytest" +version = "8.2.0" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.2.0-py3-none-any.whl", hash = "sha256:1733f0620f6cda4095bbf0d9ff8022486e91892245bb9e7d5542c018f612f233"}, + {file = "pytest-8.2.0.tar.gz", hash = "sha256:d507d4482197eac0ba2bae2e9babf0672eb333017bcedaa5fb1a3d42c1174b3f"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2.0" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "ruff" +version = "0.5.7" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.5.7-py3-none-linux_armv6l.whl", hash = "sha256:548992d342fc404ee2e15a242cdbea4f8e39a52f2e7752d0e4cbe88d2d2f416a"}, + {file = "ruff-0.5.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:00cc8872331055ee017c4f1071a8a31ca0809ccc0657da1d154a1d2abac5c0be"}, + {file = "ruff-0.5.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eaf3d86a1fdac1aec8a3417a63587d93f906c678bb9ed0b796da7b59c1114a1e"}, + {file = "ruff-0.5.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a01c34400097b06cf8a6e61b35d6d456d5bd1ae6961542de18ec81eaf33b4cb8"}, + {file = "ruff-0.5.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcc8054f1a717e2213500edaddcf1dbb0abad40d98e1bd9d0ad364f75c763eea"}, + {file = "ruff-0.5.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f70284e73f36558ef51602254451e50dd6cc479f8b6f8413a95fcb5db4a55fc"}, + {file = "ruff-0.5.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:a78ad870ae3c460394fc95437d43deb5c04b5c29297815a2a1de028903f19692"}, + {file = "ruff-0.5.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ccd078c66a8e419475174bfe60a69adb36ce04f8d4e91b006f1329d5cd44bcf"}, + {file = "ruff-0.5.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e31c9bad4ebf8fdb77b59cae75814440731060a09a0e0077d559a556453acbb"}, + {file = "ruff-0.5.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d796327eed8e168164346b769dd9a27a70e0298d667b4ecee6877ce8095ec8e"}, + {file = "ruff-0.5.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4a09ea2c3f7778cc635e7f6edf57d566a8ee8f485f3c4454db7771efb692c499"}, + {file = "ruff-0.5.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a36d8dcf55b3a3bc353270d544fb170d75d2dff41eba5df57b4e0b67a95bb64e"}, + {file = "ruff-0.5.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9369c218f789eefbd1b8d82a8cf25017b523ac47d96b2f531eba73770971c9e5"}, + {file = "ruff-0.5.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b88ca3db7eb377eb24fb7c82840546fb7acef75af4a74bd36e9ceb37a890257e"}, + {file = "ruff-0.5.7-py3-none-win32.whl", hash = "sha256:33d61fc0e902198a3e55719f4be6b375b28f860b09c281e4bdbf783c0566576a"}, + {file = "ruff-0.5.7-py3-none-win_amd64.whl", hash = "sha256:083bbcbe6fadb93cd86709037acc510f86eed5a314203079df174c40bbbca6b3"}, + {file = "ruff-0.5.7-py3-none-win_arm64.whl", hash = "sha256:2dca26154ff9571995107221d0aeaad0e75a77b5a682d6236cf89a58c70b76f4"}, + {file = "ruff-0.5.7.tar.gz", hash = "sha256:8dfc0a458797f5d9fb622dd0efc52d796f23f0a1493a9527f4e49a550ae9a7e5"}, +] + +[[package]] +name = "rustworkx" +version = "0.15.1" +description = "A python graph library implemented in Rust" +optional = false +python-versions = ">=3.8" +files = [ + {file = "rustworkx-0.15.1-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6cd4496d3298cd3205c03545e48cc37d21e0455d57752af801d3fb250452d590"}, + {file = "rustworkx-0.15.1-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:cb518f5649e62d753e29ca1e57290c8f58adbebcd154dc3159f4a36ebfa1e2b7"}, + {file = "rustworkx-0.15.1-cp38-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac68ae2515ece22ba3ef56f3d16ad6bf707955f650d623190b2e7d706c6dc92"}, + {file = "rustworkx-0.15.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b903edec1d803704b499959f9d6f6119cdda63b9b64194a4b4307e506b112f0"}, + {file = "rustworkx-0.15.1-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2c97a56ff8a0f6c273a83e26e627c72207442b4252aa550acad0bff42caac40"}, + {file = "rustworkx-0.15.1-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:241c502532e348ba89200823326dba30de4df4b886cb2fd5a140b359ff124bb3"}, + {file = "rustworkx-0.15.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e5f4156f46fa03177c9b0580450eab87786063495d48b457762a5bdd20c55e2"}, + {file = "rustworkx-0.15.1-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7834ab34748db6214ec3b3836b996b23882dc83184234e6d346d6bb85fd58ae5"}, + {file = "rustworkx-0.15.1-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ce53f173fed16e1d51d9df9f23475a16c981b03bf1a412d991c75a70db6b1dc1"}, + {file = "rustworkx-0.15.1-cp38-abi3-win32.whl", hash = "sha256:308bc76a01bcae9af4602d8b9ed58021df37dd0bb5a7b2e3831ae53c5e234ff0"}, + {file = "rustworkx-0.15.1-cp38-abi3-win_amd64.whl", hash = "sha256:89077382633e918d2392772f53b9d6d30eee51eb536f8d38ee195c212b2f0427"}, + {file = "rustworkx-0.15.1.tar.gz", hash = "sha256:0e0cc86599f979285b2ab9c357276f3272f3fcb3b2df5651a6bf9704c570d4c1"}, +] + +[package.dependencies] +numpy = ">=1.16.0,<3" + +[package.extras] +all = ["matplotlib (>=3.0)", "pillow (>=5.4)"] +graphviz = ["pillow (>=5.4)"] +mpl = ["matplotlib (>=3.0)"] + +[[package]] +name = "scipy" +version = "1.14.1" +description = "Fundamental algorithms for scientific computing in Python" +optional = false +python-versions = ">=3.10" +files = [ + {file = "scipy-1.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3"}, + {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d"}, + {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69"}, + {file = "scipy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad"}, + {file = "scipy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8"}, + {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37"}, + {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2"}, + {file = "scipy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2"}, + {file = "scipy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc"}, + {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310"}, + {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066"}, + {file = "scipy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1"}, + {file = "scipy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e"}, + {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d"}, + {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e"}, + {file = "scipy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06"}, + {file = "scipy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84"}, + {file = "scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417"}, +] + +[package.dependencies] +numpy = ">=1.23.5,<2.3" + +[package.extras] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] +doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<=7.3.7)", "sphinx-design (>=0.4.0)"] +test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + +[[package]] +name = "semantic-version" +version = "2.10.0" +description = "A library implementing the 'SemVer' scheme." +optional = false +python-versions = ">=2.7" +files = [ + {file = "semantic_version-2.10.0-py2.py3-none-any.whl", hash = "sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177"}, + {file = "semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c"}, +] + +[package.extras] +dev = ["Django (>=1.11)", "check-manifest", "colorama (<=0.4.1)", "coverage", "flake8", "nose2", "readme-renderer (<25.0)", "tox", "wheel", "zest.releaser[recommended]"] +doc = ["Sphinx", "sphinx-rtd-theme"] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +optional = false +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "sphinx" +version = "8.0.2" +description = "Python documentation generator" +optional = false +python-versions = ">=3.10" +files = [ + {file = "sphinx-8.0.2-py3-none-any.whl", hash = "sha256:56173572ae6c1b9a38911786e206a110c9749116745873feae4f9ce88e59391d"}, + {file = "sphinx-8.0.2.tar.gz", hash = "sha256:0cce1ddcc4fd3532cf1dd283bc7d886758362c5c1de6598696579ce96d8ffa5b"}, +] + +[package.dependencies] +alabaster = ">=0.7.14" +babel = ">=2.13" +colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} +docutils = ">=0.20,<0.22" +imagesize = ">=1.3" +Jinja2 = ">=3.1" +packaging = ">=23.0" +Pygments = ">=2.17" +requests = ">=2.30.0" +snowballstemmer = ">=2.2" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.9" +tomli = {version = ">=2", markers = "python_version < \"3.11\""} + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["flake8 (>=6.0)", "mypy (==1.11.0)", "pytest (>=6.0)", "ruff (==0.5.5)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-Pillow (==10.2.0.20240520)", "types-Pygments (==2.18.0.20240506)", "types-colorama (==0.4.15.20240311)", "types-defusedxml (==0.7.0.20240218)", "types-docutils (==0.21.0.20240724)", "types-requests (>=2.30.0)"] +test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] + +[[package]] +name = "sphinx-rtd-theme" +version = "3.0.0rc1" +description = "Read the Docs theme for Sphinx" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sphinx_rtd_theme-3.0.0rc1-py2.py3-none-any.whl", hash = "sha256:5942c963c36525ae25386d22b565dc07daf0862e2757fe83ba679414fabba406"}, + {file = "sphinx_rtd_theme-3.0.0rc1.tar.gz", hash = "sha256:fa5b5f2fcfa067038d4e362295275d959529bedde94f929d899f88c97f9939fa"}, +] + +[package.dependencies] +docutils = ">0.18,<0.22" +sphinx = ">=6,<9" +sphinxcontrib-jquery = ">=4,<5" + +[package.extras] +dev = ["bump2version", "transifex-client", "twine", "wheel"] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, + {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, + {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, + {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +description = "Extension to include jQuery on newer Sphinx releases" +optional = false +python-versions = ">=2.7" +files = [ + {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, + {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, +] + +[package.dependencies] +Sphinx = ">=1.8" + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, + {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["defusedxml (>=0.7.1)", "pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, + {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "urllib3" +version = "2.2.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "virtualenv" +version = "20.26.3" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"}, + {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "b4bd2f675198e4f96dddbfe71aebcaad43f3293fb4bb3ebb82c924e8a10a9730" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..6701379 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,42 @@ +[tool.poetry] +name = "ionizer" +version = "0.3.0" +description = "PennyLane tools for compilation into trapped-ion native gates." +authors = ["UBC Quantum Software and Algorithms Research Lab"] +repository = "https://github.com/QSAR-UBC/ionizer/" +license = "MIT" +readme = "README.md" +keywords = ["quantum-computing", "quantum-software"] +include = [{ path = "ionizer/resources"}] + +[tool.poetry.dependencies] +python = "^3.10" +pennylane = "^0.37" + + +[tool.poetry.group.dev.dependencies] +pytest = "8.2" +black = "24.2" +pre-commit = "3.7.1" +ruff = "^0.5.0" + +[tool.poetry.group.docs.dependencies] +sphinx = "8.0.2" +sphinx-rtd-theme = "3.0.0rc1" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.ruff] +line-length = 100 +indent-width = 4 + +[tool.ruff.format] +docstring-code-format = true + +[tool.ruff.lint.per-file-ignores] +"__init__.py" = ["F401"] + +[tool.codespell] +skip = "poetry.lock" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 498c84c..0000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -pennylane>=0.33 diff --git a/setup.py b/setup.py deleted file mode 100644 index 67cbff2..0000000 --- a/setup.py +++ /dev/null @@ -1,20 +0,0 @@ -from setuptools import setup, find_packages, find_namespace_packages - -requirements = [ - "pennylane>=0.33", -] - -setup( - name="Ionizer", - version="0.2", - description="PennyLane tools for compilation into trapped-ion native gates.", - author="UBC Quantum Software and Algorithms Research Group", - url="https://github.com/QSAR-UBC/ionizer", - packages=["ionizer", "ionizer.resources"], - include_package_data=True, - package_data={ - "ionizer.resources": [ - "double_gate_identities.pkl", - "triple_gate_identities.pkl" - ]}, -) diff --git a/tests/test_decompositions.py b/tests/test_decompositions.py index 5a6d737..d88c8c4 100644 --- a/tests/test_decompositions.py +++ b/tests/test_decompositions.py @@ -17,6 +17,7 @@ """ Test the decompositions of standard operations in to the {GPI, GPI2, MS} gate set. """ + import pytest import pennylane as qml @@ -43,10 +44,18 @@ single_qubit_unitaries = [ (np.eye(2), []), - (np.array([[0.0, 0.54030231 - 0.84147098j], [0.54030231 + 0.84147098j, 0.0]]), ["GPI"]), + ( + np.array([[0.0, 0.54030231 - 0.84147098j], [0.54030231 + 0.84147098j, 0.0]]), + ["GPI"], + ), (qml.RZ.compute_matrix(0.2), ["GPI", "GPI"]), ( - np.array([[0.70710678, -0.59500984 - 0.38205142j], [0.59500984 - 0.38205142j, 0.70710678]]), + np.array( + [ + [0.70710678, -0.59500984 - 0.38205142j], + [0.59500984 - 0.38205142j, 0.70710678], + ] + ), ["GPI2"], ), (qml.RY.compute_matrix(1.0), ["GPI2", "GPI", "GPI2"]), @@ -79,7 +88,9 @@ class TestDecompositions: def test_non_parametric_decompositions(self, gate, decomp_function): """Test decompositions of non-parametric operations.""" expected_mat = gate.compute_matrix() - obtained_mat = qml.matrix(decomp_function)(wires=range(gate.num_wires)) + obtained_mat = qml.matrix(decomp_function, wire_order=range(gate.num_wires))( + wires=range(gate.num_wires) + ) mat_product = qml.math.dot(expected_mat, qml.math.conj(qml.math.T(obtained_mat))) mat_product = mat_product / mat_product[0, 0] assert qml.math.allclose(mat_product, qml.math.eye(mat_product.shape[0])) @@ -91,15 +102,17 @@ def test_non_parametric_decompositions(self, gate, decomp_function): def test_parametric_decompositions(self, gate, decomp_function, angle): """Test decompositions of parametric operations.""" expected_mat = gate.compute_matrix(angle) - obtained_mat = qml.matrix(decomp_function)(angle, wires=range(gate.num_wires)) + obtained_mat = qml.matrix(decomp_function, wire_order=range(gate.num_wires))( + angle, wires=range(gate.num_wires) + ) mat_product = qml.math.dot(expected_mat, qml.math.conj(qml.math.T(obtained_mat))) mat_product = mat_product / mat_product[0, 0] assert qml.math.allclose(mat_product, qml.math.eye(mat_product.shape[0])) - @pytest.mark.parametrize("U, decomp_list", single_qubit_unitaries) - def test_single_qubit_unitary_decomposition(self, U, decomp_list): + @pytest.mark.parametrize("unitary, decomp_list", single_qubit_unitaries) + def test_single_qubit_unitary_decomposition(self, unitary, decomp_list): """Test decompositions of single-qubit unitary matrices.""" - obtained_decomp_list = gpi_single_qubit_unitary(U, [0]) + obtained_decomp_list = gpi_single_qubit_unitary(unitary, [0]) assert all( op.name == expected_name for op, expected_name in zip(obtained_decomp_list, decomp_list) @@ -110,7 +123,7 @@ def test_single_qubit_unitary_decomposition(self, U, decomp_list): for op in obtained_decomp_list: qml.apply(op) - obtained_matrix = qml.matrix(tape) - mat_product = math.dot(obtained_matrix, math.conj(math.T(U))) + obtained_matrix = qml.matrix(tape, wire_order=tape.wires) + mat_product = math.dot(obtained_matrix, math.conj(math.T(unitary))) mat_product = mat_product / mat_product[0, 0] assert math.allclose(mat_product, math.eye(2)) diff --git a/tests/test_transform_utils.py b/tests/test_identity_hunter.py similarity index 93% rename from tests/test_transform_utils.py rename to tests/test_identity_hunter.py index 0457a1f..7a86404 100644 --- a/tests/test_transform_utils.py +++ b/tests/test_identity_hunter.py @@ -1,20 +1,20 @@ """ -Test the utility functions for the transpilation transforms. +Test the utility functions for the transpilation transforms. """ + import pytest -from functools import partial import pennylane as qml from pennylane import math import numpy as np from ionizer.utils import are_mats_equivalent -from ionizer.transform_utils import ( +from ionizer.identity_hunter import ( search_and_apply_two_gate_identities, search_and_apply_three_gate_identities, ) -from ionizer.ops import GPI, GPI2, MS +from ionizer.ops import GPI, GPI2 def _compare_op_lists(ops1, ops2): @@ -147,7 +147,10 @@ def test_valid_identity_different_wires(self): @pytest.mark.parametrize( "input_ops,expected_ops", [ - ([GPI2(np.pi, wires=0), GPI(0, wires=0), GPI2(-np.pi, wires=0)], []), # Simplifies to I + ( + [GPI2(np.pi, wires=0), GPI(0, wires=0), GPI2(-np.pi, wires=0)], + [], + ), # Simplifies to I ( [GPI2(0, wires=0), GPI(np.pi / 4, wires=0), GPI2(0, wires=0)], [GPI2(-np.pi / 2, wires=0)], @@ -161,7 +164,11 @@ def test_valid_identity_different_wires(self): [GPI2(0.3, wires=0)], ), # Second two gates only ( - [GPI2(-np.pi / 2, wires=0), GPI(-np.pi / 4, wires=0), GPI2(-np.pi / 2, wires=0)], + [ + GPI2(-np.pi / 2, wires=0), + GPI(-np.pi / 4, wires=0), + GPI2(-np.pi / 2, wires=0), + ], [GPI2(-np.pi, wires=0)], ), # 3-gate identity ], diff --git a/tests/test_transforms.py b/tests/test_transforms.py index fef7edb..3ea847d 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -1,9 +1,12 @@ """ -Test the suite of transpilation transforms. +Test the suite of transpilation transforms. """ -import pytest + +# pylint: disable=function-redefined from functools import partial +import pytest + import pennylane as qml from pennylane import math import numpy as np @@ -18,18 +21,7 @@ ) from ionizer.ops import GPI, GPI2, MS - -def _compare_tape_contents(tape1, tape2): - """Test if two tapes are equal.""" - assert len(tape1.operations) == len(tape2.operations) - - for op1, op2 in zip(tape1.operations, tape2.operations): - assert op1.name == op2.name - assert op1.num_params == op2.num_params - if op1.num_params > 0: - assert math.allclose(op1.data, op2.data) - - return True +from test_identity_hunter import _compare_op_lists # pylint: disable=wrong-import-order class TestCommuteThroughMSGates: @@ -48,7 +40,7 @@ def qfunc(): transformed_qfunc = commute_through_ms_gates(qfunc) transformed_tape = qml.tape.make_qscript(transformed_qfunc)() - assert _compare_tape_contents(tape, transformed_tape) + assert _compare_op_lists(tape.operations, transformed_tape.operations) def qfunc(): MS(wires=[0, 1]) @@ -60,7 +52,7 @@ def qfunc(): transformed_qfunc = commute_through_ms_gates(qfunc) transformed_tape = qml.tape.make_qscript(transformed_qfunc)() - assert _compare_tape_contents(tape, transformed_tape) + assert _compare_op_lists(tape.operations, transformed_tape.operations) def test_commutation_both_qubits(self): """Test that when no gates on either qubits commute, nothing happens.""" @@ -81,8 +73,11 @@ def expected_qfunc(): transformed_tape = qml.tape.make_qscript(transformed_qfunc)() expected_tape = qml.tape.make_qscript(expected_qfunc)() - assert _compare_tape_contents(transformed_tape, expected_tape) - assert are_mats_equivalent(qml.matrix(transformed_tape), qml.matrix(expected_tape)) + assert _compare_op_lists(transformed_tape.operations, expected_tape.operations) + assert are_mats_equivalent( + qml.matrix(transformed_tape, wire_order=[0, 1]), + qml.matrix(expected_tape, wire_order=[0, 1]), + ) def test_commutation_one_qubit(self): """Test case where gates on one of the qubits commutes.""" @@ -103,8 +98,11 @@ def expected_qfunc(): transformed_tape = qml.tape.make_qscript(transformed_qfunc)() expected_tape = qml.tape.make_qscript(expected_qfunc)() - assert _compare_tape_contents(transformed_tape, expected_tape) - assert are_mats_equivalent(qml.matrix(transformed_tape), qml.matrix(expected_tape)) + assert _compare_op_lists(transformed_tape.operations, expected_tape.operations) + assert are_mats_equivalent( + qml.matrix(transformed_tape, wire_order=[0, 1]), + qml.matrix(expected_tape, wire_order=[0, 1]), + ) def qfunc(): MS(wires=[0, 1]) @@ -122,8 +120,11 @@ def expected_qfunc(): transformed_tape = qml.tape.make_qscript(transformed_qfunc)() expected_tape = qml.tape.make_qscript(expected_qfunc)() - assert _compare_tape_contents(transformed_tape, expected_tape) - assert are_mats_equivalent(qml.matrix(transformed_tape), qml.matrix(expected_tape)) + assert _compare_op_lists(transformed_tape.operations, expected_tape.operations) + assert are_mats_equivalent( + qml.matrix(transformed_tape, wire_order=[0, 1]), + qml.matrix(expected_tape, wire_order=[0, 1]), + ) def test_commutation_one_qubit_multiple_ms(self): """Test case where gates on one of the qubits commutes through multiple MS gates.""" @@ -144,8 +145,11 @@ def expected_qfunc(): transformed_tape = qml.tape.make_qscript(transformed_qfunc)() expected_tape = qml.tape.make_qscript(expected_qfunc)() - assert _compare_tape_contents(transformed_tape, expected_tape) - assert are_mats_equivalent(qml.matrix(transformed_tape), qml.matrix(expected_tape)) + assert _compare_op_lists(transformed_tape.operations, expected_tape.operations) + assert are_mats_equivalent( + qml.matrix(transformed_tape, wire_order=range(3)), + qml.matrix(expected_tape, wire_order=range(3)), + ) def test_commutation_two_qubits_multiple_ms(self): """Test case where gates on one of the qubits commutes.""" @@ -168,10 +172,14 @@ def expected_qfunc(): transformed_tape = qml.tape.make_qscript(transformed_qfunc)() expected_tape = qml.tape.make_qscript(expected_qfunc)() - assert _compare_tape_contents(transformed_tape, expected_tape) - assert are_mats_equivalent(qml.matrix(transformed_tape), qml.matrix(expected_tape)) + assert _compare_op_lists(transformed_tape.operations, expected_tape.operations) + assert are_mats_equivalent( + qml.matrix(transformed_tape, wire_order=range(3)), + qml.matrix(expected_tape, wire_order=range(3)), + ) - def test_commutation_two_qubits_multiple_ms_left(self): + @pytest.mark.parametrize("verify_equivalence", [True, False]) + def test_commutation_two_qubits_multiple_ms_left(self, verify_equivalence): """Test case where gates on one of the qubits commutes in leftward direction.""" def qfunc(): @@ -188,11 +196,13 @@ def expected_qfunc(): GPI2(np.pi, wires=2) MS(wires=[0, 2]) - transformed_qfunc = partial(commute_through_ms_gates, direction="left")(qfunc) + transformed_qfunc = partial( + commute_through_ms_gates, direction="left", verify_equivalence=verify_equivalence + )(qfunc) transformed_tape = qml.tape.make_qscript(transformed_qfunc)() expected_tape = qml.tape.make_qscript(expected_qfunc)() - assert _compare_tape_contents(transformed_tape, expected_tape) + assert _compare_op_lists(transformed_tape, expected_tape) class TestVirtualizeRZGates: @@ -211,7 +221,10 @@ def qfunc(): transformed_tape = qml.tape.make_qscript(transformed_qfunc)() assert len(transformed_tape.operations) == 1 - assert are_mats_equivalent(qml.matrix(tape), qml.matrix(transformed_tape)) + assert are_mats_equivalent( + qml.matrix(tape, wire_order=[0]), + qml.matrix(transformed_tape, wire_order=[0]), + ) def test_rz_gpi2(self): """Test that RZ is correctly pushed through a GPI2 gate.""" @@ -229,7 +242,10 @@ def qfunc(): assert all( op.name == name for op, name in zip(transformed_tape.operations, ["GPI2", "GPI", "GPI"]) ) - assert are_mats_equivalent(qml.matrix(tape), qml.matrix(transformed_tape)) + assert are_mats_equivalent( + qml.matrix(tape, wire_order=[0]), + qml.matrix(transformed_tape, wire_order=[0]), + ) def test_rz_gpi_ms(self): """Test that non-GPI gates stop RZ from going through.""" @@ -249,9 +265,13 @@ def qfunc(): op.name == name for op, name in zip(transformed_tape.operations, ["GPI2", "GPI", "GPI", "MS"]) ) - assert are_mats_equivalent(qml.matrix(tape), qml.matrix(transformed_tape)) + assert are_mats_equivalent( + qml.matrix(tape, wire_order=[0, 1]), + qml.matrix(transformed_tape, wire_order=[0, 1]), + ) - def test_rz_gpi_multiqubit(self): + @pytest.mark.parametrize("verify_equivalence", [True, False]) + def test_rz_gpi_multiqubit(self, verify_equivalence): """Test that RZ gates are virtualized on multiple qubits.""" def qfunc(): @@ -262,7 +282,9 @@ def qfunc(): tape = qml.tape.make_qscript(qfunc)() - transformed_qfunc = virtualize_rz_gates(qfunc) + transformed_qfunc = partial(virtualize_rz_gates, verify_equivalence=verify_equivalence)( + qfunc + ) transformed_tape = qml.tape.make_qscript(transformed_qfunc)() assert len(transformed_tape.operations) == 4 @@ -270,9 +292,13 @@ def qfunc(): op.name == name for op, name in zip(transformed_tape.operations, ["GPI2", "GPI", "GPI", "GPI"]) ) - assert are_mats_equivalent(qml.matrix(tape), qml.matrix(transformed_tape)) + assert are_mats_equivalent( + qml.matrix(tape, wire_order=[0, 1]), + qml.matrix(transformed_tape, wire_order=[0, 1]), + ) - def test_rz_gpi_multiqubit_multims(self): + @pytest.mark.parametrize("verify_equivalence", [True, False]) + def test_rz_gpi_multiqubit_multims(self, verify_equivalence): """Test that RZ gates are virtualized on multiple qubits with gates in between.""" def qfunc(): @@ -285,7 +311,9 @@ def qfunc(): tape = qml.tape.make_qscript(qfunc)() - transformed_qfunc = virtualize_rz_gates(qfunc) + transformed_qfunc = partial(virtualize_rz_gates, verify_equivalence=verify_equivalence)( + qfunc + ) transformed_tape = qml.tape.make_qscript(transformed_qfunc)() assert len(transformed_tape.operations) == 6 @@ -295,7 +323,10 @@ def qfunc(): transformed_tape.operations, ["GPI2", "GPI", "GPI", "MS", "GPI", "MS"] ) ) - assert are_mats_equivalent(qml.matrix(tape), qml.matrix(transformed_tape)) + assert are_mats_equivalent( + qml.matrix(tape, wire_order=[0, 1]), + qml.matrix(transformed_tape, wire_order=[0, 1]), + ) class TestSingleQubitGPIFusion: @@ -318,8 +349,11 @@ def qfunc(): transformed_qfunc = single_qubit_fusion_gpi(qfunc) transformed_tape = qml.tape.make_qscript(transformed_qfunc)() - assert _compare_tape_contents(tape, transformed_tape) - assert are_mats_equivalent(qml.matrix(tape), qml.matrix(transformed_tape)) + assert _compare_op_lists(tape.operations, transformed_tape.operations) + assert are_mats_equivalent( + qml.matrix(tape, wire_order=range(3)), + qml.matrix(transformed_tape, wire_order=range(3)), + ) def test_no_fusion_multiple_gates(self): """Test that if there are no gates to fuse that nothing happens, even @@ -340,8 +374,11 @@ def qfunc(): transformed_qfunc = single_qubit_fusion_gpi(qfunc) transformed_tape = qml.tape.make_qscript(transformed_qfunc)() - assert _compare_tape_contents(tape, transformed_tape) - assert are_mats_equivalent(qml.matrix(tape), qml.matrix(transformed_tape)) + assert _compare_op_lists(tape.operations, transformed_tape.operations) + assert are_mats_equivalent( + qml.matrix(tape, wire_order=range(3)), + qml.matrix(transformed_tape, wire_order=range(3)), + ) def test_fusion_three_gates(self): """Test that if a GPI2/GPI/GPI2 sequence already exists that we don't fuse.""" @@ -362,10 +399,14 @@ def qfunc(): transformed_qfunc = single_qubit_fusion_gpi(qfunc) transformed_tape = qml.tape.make_qscript(transformed_qfunc)() - assert _compare_tape_contents(tape, transformed_tape) - assert are_mats_equivalent(qml.matrix(tape), qml.matrix(transformed_tape)) + assert _compare_op_lists(tape.operations, transformed_tape.operations) + assert are_mats_equivalent( + qml.matrix(tape, wire_order=range(3)), + qml.matrix(transformed_tape, wire_order=range(3)), + ) - def test_fusion_four_gates(self): + @pytest.mark.parametrize("verify_equivalence", [True, False]) + def test_fusion_four_gates(self, verify_equivalence): """Test that more than three gates get properly fused.""" def qfunc(): @@ -385,17 +426,23 @@ def qfunc(): tape = qml.tape.make_qscript(qfunc)() - transformed_qfunc = single_qubit_fusion_gpi(qfunc) + transformed_qfunc = partial(single_qubit_fusion_gpi, verify_equivalence=verify_equivalence)( + qfunc + ) transformed_tape = qml.tape.make_qscript(transformed_qfunc)() assert len(transformed_tape.operations) == len(tape.operations) - 2 - assert are_mats_equivalent(qml.matrix(tape), qml.matrix(transformed_tape)) + assert are_mats_equivalent( + qml.matrix(tape, wire_order=range(3)), + qml.matrix(transformed_tape, wire_order=range(3)), + ) class TestConvertToGPI: """Tests that operations on a tape are correctly converted to GPI gates.""" - def test_convert_tape_to_gpi_known_gates(self): + @pytest.mark.parametrize("verify_equivalence", [True, False]) + def test_convert_tape_to_gpi_known_gates(self, verify_equivalence): """Test that known gates are correctly converted to GPI gates.""" def qfunc(): @@ -406,13 +453,17 @@ def qfunc(): tape = qml.tape.make_qscript(qfunc)() - transformed_qfunc = convert_to_gpi(qfunc) + transformed_qfunc = partial(convert_to_gpi, verify_equivalence=verify_equivalence)(qfunc) transformed_tape = qml.tape.make_qscript(transformed_qfunc)() assert all(op.name in ["GPI", "GPI2", "MS"] for op in transformed_tape.operations) - assert are_mats_equivalent(qml.matrix(tape), qml.matrix(transformed_tape)) + assert are_mats_equivalent( + qml.matrix(tape, wire_order=range(3)), + qml.matrix(transformed_tape, wire_order=range(3)), + ) - def test_convert_tape_to_gpi_known_gates_exclusion(self): + @pytest.mark.parametrize("verify_equivalence", [True, False]) + def test_convert_tape_to_gpi_known_gates_exclusion(self, verify_equivalence): """Test that known gates are correctly converted to GPI gates and excluded gates are kept as-is.""" @@ -424,20 +475,24 @@ def qfunc(): tape = qml.tape.make_qscript(qfunc)() - transformed_qfunc = partial(convert_to_gpi, exclude_list=["RY"])(qfunc) + transformed_qfunc = partial( + convert_to_gpi, exclude_list=["RY"], verify_equivalence=verify_equivalence + )(qfunc) transformed_tape = qml.tape.make_qscript(transformed_qfunc)() assert all(op.name in ["GPI", "GPI2", "MS", "RY"] for op in transformed_tape.operations) assert transformed_tape.operations[3].name == "RY" assert are_mats_equivalent( - qml.matrix(tape, wire_order=range(3)), qml.matrix(transformed_tape, wire_order=range(3)) + qml.matrix(tape, wire_order=range(3)), + qml.matrix(transformed_tape, wire_order=range(3)), ) class TestIonize: """Integration test for full ionize transform.""" - def test_ionize_tape(self): + @pytest.mark.parametrize("verify_equivalence", [True, False]) + def test_ionize_tape(self, verify_equivalence): """Test ionize transform on a single tape.""" def qfunc(): @@ -452,14 +507,13 @@ def qfunc(): tape = qml.tape.make_qscript(qfunc)() - transformed_qfunc = ionize(qfunc) + transformed_qfunc = ionize(qfunc, verify_equivalence=verify_equivalence) transformed_tape = qml.tape.make_qscript(transformed_qfunc)() - print(transformed_tape.operations) - assert all(op.name in ["GPI", "GPI2", "MS"] for op in transformed_tape.operations) assert are_mats_equivalent( - qml.matrix(tape, wire_order=range(4)), qml.matrix(transformed_tape, wire_order=range(4)) + qml.matrix(tape, wire_order=range(4)), + qml.matrix(transformed_tape, wire_order=range(4)), ) def test_ionize_qnode(self): @@ -493,6 +547,7 @@ def ionized_qnode(): qml.matrix(ionized_qnode, wire_order=range(4))(), ) + @pytest.mark.parametrize("verify_equivalence", [True, False]) @pytest.mark.parametrize( "params", [ @@ -502,8 +557,9 @@ def ionized_qnode(): np.array([-0.54, 0.68, 0.11]), ], ) - def test_ionize_parametrized_qnode(self, params): - """Test ionize transform on a QNode.""" + def test_ionize_parametrized_qnode(self, params, verify_equivalence): + """Test ionize transform on a QNode including parametrized gates. + Verify also that equivalence is preserved.""" dev = qml.device("default.qubit", wires=5) def quantum_function(params): @@ -521,7 +577,7 @@ def normal_qnode(params): return qml.expval(qml.PauliZ(0) @ qml.PauliX(wires=2)) @qml.qnode(dev) - @ionize + @partial(ionize, verify_equivalence=verify_equivalence) def ionized_qnode(params): quantum_function(params) return qml.expval(qml.PauliZ(0) @ qml.PauliX(wires=2)) diff --git a/tests/test_utils.py b/tests/test_utils.py index a36a40e..173b3c7 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,8 +1,11 @@ """ Test utility functions. """ + import pytest +from functools import partial + import numpy as np import pennylane as qml from pennylane import math @@ -10,28 +13,148 @@ from ionizer.ops import GPI, GPI2, MS from ionizer.utils import ( are_mats_equivalent, + flag_non_equivalence, rescale_angles, extract_gpi2_gpi_gpi2_angles, tape_to_json, ) -from test_decompositions import single_qubit_unitaries +from test_decompositions import single_qubit_unitaries # pylint: disable=wrong-import-order + + +@qml.transform +def add_bad_gates(tape, verify_equivalence=False): + """A transform that behaves incorrectly. + + Used to test the equivalence checking mechanism. Since all our + implemented transforms preserve equivalence, we create this "bad" transform, + which has the same structure as the others, to validate that an error is + raised when the transformed circuit is not equivalent. + """ + + new_operations = [] + for op in tape.operations: + new_operations.append(op) + new_operations.append(op) + + new_tape = type(tape)(new_operations, tape.measurements, shots=tape.shots) + + if verify_equivalence: + flag_non_equivalence(tape, new_tape) + + def null_postprocessing(results): + return results[0] + return [new_tape], null_postprocessing -class TestMatrixEquivalence: - """Test utility function for matrix equivalence checking.""" + +class TestEquivalenceMechanism: + + def test_equivalence_tape(self): + """Test that non-equivalence is correctly detected when a transform is + applied to tapes.""" + + with qml.tape.QuantumTape() as tape: + qml.Hadamard(wires=0) + qml.CNOT(wires=[0, 1]) + + # Will pass without issue + add_bad_gates(tape) + + with pytest.raises(ValueError, match="not equivalent after transform"): + _ = partial(add_bad_gates, verify_equivalence=True)(tape) + + def test_equivalence_qfunc(self): + """Test that non-equivalence is correctly detected for quantum + function transforms.""" + + def qfunc(): + qml.Hadamard(wires=0) + qml.CNOT(wires=[0, 1]) + return qml.probs(wires=[0, 1]) + + transformed_qfunc = add_bad_gates(qfunc) + _ = qml.tape.make_qscript(transformed_qfunc)() + + with pytest.raises(ValueError, match="not equivalent after transform"): + transformed_qfunc = partial(add_bad_gates, verify_equivalence=True)(qfunc) + _ = qml.tape.make_qscript(transformed_qfunc)() + + def test_equivalence_qnode(self): + """Test that non-equivalence is correctly detected for transforms applied to QNodes.""" + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + def circuit(): + qml.Hadamard(wires=0) + qml.CNOT(wires=[0, 1]) + return qml.probs(wires=[0, 1]) + + # Will pass without issue; note that the QNode must be called for the + # transform to execute. + transformed_qnode = add_bad_gates(circuit) + transformed_qnode() + + with pytest.raises(ValueError, match="not equivalent after transform"): + transformed_qnode = partial(add_bad_gates, verify_equivalence=True)(circuit) + transformed_qnode() + + def test_equivalence_qnode_default(self): + """Test that non-equivalence is not detected if we do not add the flag + in the decorator.""" + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + @add_bad_gates + def circuit(): + qml.Hadamard(wires=0) + qml.CNOT(wires=[0, 1]) + return qml.probs(wires=[0, 1]) + + circuit() + + @pytest.mark.parametrize("verify_equivalence", [True, False]) + def test_equivalence_composition(self, verify_equivalence): + """Test that non-equivalence is correctly detected for a bad transform applied + before a good one in a QNode.""" + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + @partial(add_bad_gates, verify_equivalence=verify_equivalence) + @qml.transforms.cancel_inverses + def circuit(): + qml.Hadamard(wires=0) + qml.Hadamard(wires=0) + qml.CNOT(wires=[0, 1]) + return qml.probs(wires=[0, 1]) + + if verify_equivalence is True: + with pytest.raises(ValueError, match="not equivalent after transform"): + circuit() + else: + circuit() + + +class TestMatrixAngleUtilities: + """Test utility functions for matrix and angle manipulations.""" @pytest.mark.parametrize( "mat1, mat2", [ (np.eye(2), 1j * np.eye(2)), - (qml.PauliX.compute_matrix(), np.exp(1j * np.pi / 2) * qml.PauliX.compute_matrix()), - (qml.SX.compute_matrix(), np.exp(-1j * np.pi / 2) * qml.SX.compute_matrix()), + ( + qml.PauliX.compute_matrix(), + np.exp(1j * np.pi / 2) * qml.PauliX.compute_matrix(), + ), + ( + qml.SX.compute_matrix(), + np.exp(-1j * np.pi / 2) * qml.SX.compute_matrix(), + ), (qml.Hadamard.compute_matrix(), -qml.Hadamard.compute_matrix()), ], ) def test_equivalent_matrices(self, mat1, mat2): - """Test that we correcty identify matrices that are equivalent + """Test that we correctly identify matrices that are equivalent up to a global phase.""" assert are_mats_equivalent(mat1, mat2) @@ -44,14 +167,10 @@ def test_equivalent_matrices(self, mat1, mat2): ], ) def test_inequivalent_matrices(self, mat1, mat2): - """Test that we correcty identify matrices that are equivalent + """Test that we correctly identify matrices that are equivalent up to a global phase.""" assert not are_mats_equivalent(mat1, mat2) - -class TestRescaleAngles: - """Test utility function for rescaling of angles.""" - @pytest.mark.parametrize( "angles,rescaled_angles", [ @@ -59,7 +178,10 @@ class TestRescaleAngles: (np.pi, np.pi), (-np.pi / 2, -np.pi / 2), (3 * np.pi / 2, -np.pi / 2), - (np.array([np.pi / 2, 5 * np.pi / 4]), np.array([np.pi / 2, -3 * np.pi / 4])), + ( + np.array([np.pi / 2, 5 * np.pi / 4]), + np.array([np.pi / 2, -3 * np.pi / 4]), + ), ], ) def test_rescale_angles(self, angles, rescaled_angles): @@ -84,21 +206,17 @@ def test_rescale_angles_hardware(self, angles, rescaled_angles): print(obtained_angles) assert math.allclose(obtained_angles, rescaled_angles) - -class TestExtractGPIGPI2Angles: - """Test utility function for extraction of GPI/GPI2 angles.""" - - @pytest.mark.parametrize("U", [test_case[0] for test_case in single_qubit_unitaries]) - def test_extract_gpi_gpi2_angles(self, U): + @pytest.mark.parametrize("unitary", [test_case[0] for test_case in single_qubit_unitaries]) + def test_extract_gpi_gpi2_angles(self, unitary): """Test that extracting GPI/GPI2 angles yields the correct operation.""" - gamma, beta, alpha = extract_gpi2_gpi_gpi2_angles(U) + gamma, beta, alpha = extract_gpi2_gpi_gpi2_angles(unitary) with qml.tape.QuantumTape() as tape: GPI2(gamma, wires=0) GPI(beta, wires=0) GPI2(alpha, wires=0) - assert are_mats_equivalent(qml.matrix(tape), U) + assert are_mats_equivalent(qml.matrix(tape), unitary) class TestConvertToJSON: @@ -115,10 +233,10 @@ def test_single_gate_circuit(self): assert circuit_json["shots"] == 1000 assert circuit_json["target"] == "simulator" - assert circuit_json["body"]["gateset"] == "native" - assert circuit_json["body"]["qubits"] == 1 + assert circuit_json["input"]["gateset"] == "native" + assert circuit_json["input"]["qubits"] == 1 - circuit_contents = circuit_json["body"]["circuit"] + circuit_contents = circuit_json["input"]["circuit"] assert len(circuit_contents) == 1 assert circuit_contents[0]["phase"] == 0.3 / (2 * np.pi) assert circuit_contents[0]["target"] == 0 @@ -136,9 +254,9 @@ def test_multi_gate_circuit(self): assert circuit_json["shots"] == 100 assert circuit_json["target"] == "qpu" - assert circuit_json["body"]["qubits"] == 1 + assert circuit_json["input"]["qubits"] == 1 - circuit_contents = circuit_json["body"]["circuit"] + circuit_contents = circuit_json["input"]["circuit"] assert len(circuit_contents) == 3 assert circuit_contents[0]["phase"] == 0.3 / (2 * np.pi) @@ -161,9 +279,9 @@ def test_multi_gate_multi_wire_circuit(self): assert circuit_json["shots"] == 100 assert circuit_json["target"] == "qpu" - assert circuit_json["body"]["qubits"] == 3 + assert circuit_json["input"]["qubits"] == 3 - circuit_contents = circuit_json["body"]["circuit"] + circuit_contents = circuit_json["input"]["circuit"] assert len(circuit_contents) == 3 assert circuit_contents[0]["phase"] == 0.3 / (2 * np.pi) @@ -187,9 +305,9 @@ def test_multi_wire_with_ms_gates(self): assert circuit_json["shots"] == 100 assert circuit_json["target"] == "qpu" - assert circuit_json["body"]["qubits"] == 2 + assert circuit_json["input"]["qubits"] == 2 - circuit_contents = circuit_json["body"]["circuit"] + circuit_contents = circuit_json["input"]["circuit"] assert len(circuit_contents) == 4 assert circuit_contents[0]["phase"] == 0.3 / (2 * np.pi)