diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 9edfe4906..ab65dfca0 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -12,13 +12,13 @@ permissions: {} jobs: build-sdist: name: 🐍 Packaging - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-sdist.yml@ace7363a2a8e036e5efc6717be67f1eda29a120e # v2.4.2 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-sdist.yml@ae4c1005796fedc123c3c70cdba94ce643c9fcae # v2.3.1 permissions: contents: read build-wheel: name: 🐍 Packaging - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-wheel-build.yml@ace7363a2a8e036e5efc6717be67f1eda29a120e # v2.4.2 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-wheel-build.yml@ae4c1005796fedc123c3c70cdba94ce643c9fcae # v2.3.1 permissions: contents: read diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dfe0c3ad5..28f78ef1f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ permissions: {} jobs: change-detection: name: πŸ” Change - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-change-detection.yml@ace7363a2a8e036e5efc6717be67f1eda29a120e # v2.4.2 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-change-detection.yml@ae4c1005796fedc123c3c70cdba94ce643c9fcae # v2.3.1 permissions: contents: read @@ -29,10 +29,9 @@ jobs: fail-fast: false matrix: runs-on: [ubuntu-24.04, ubuntu-24.04-arm, macos-26, windows-2025] - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@ace7363a2a8e036e5efc6717be67f1eda29a120e # v2.4.2 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@ae4c1005796fedc123c3c70cdba94ce643c9fcae # v2.3.1 with: runs-on: ${{ matrix.runs-on }} - use-sccache-gha: false permissions: contents: read @@ -40,7 +39,7 @@ jobs: name: 🐍 Coverage needs: [change-detection, python-tests] if: fromJSON(needs.change-detection.outputs.run-python-tests) - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-coverage.yml@ace7363a2a8e036e5efc6717be67f1eda29a120e # v2.4.2 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-coverage.yml@ae4c1005796fedc123c3c70cdba94ce643c9fcae # v2.3.1 permissions: contents: read id-token: write @@ -49,7 +48,7 @@ jobs: name: 🐍 Lint needs: change-detection if: fromJSON(needs.change-detection.outputs.run-python-linter) - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-linter.yml@ace7363a2a8e036e5efc6717be67f1eda29a120e # v2.4.2 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-linter.yml@ae4c1005796fedc123c3c70cdba94ce643c9fcae # v2.3.1 with: enable-ty: true enable-mypy: false @@ -60,7 +59,7 @@ jobs: name: πŸš€ CD needs: change-detection if: fromJSON(needs.change-detection.outputs.run-cd) - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-sdist.yml@ace7363a2a8e036e5efc6717be67f1eda29a120e # v2.4.2 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-sdist.yml@ae4c1005796fedc123c3c70cdba94ce643c9fcae # v2.3.1 permissions: contents: read @@ -68,7 +67,7 @@ jobs: name: πŸš€ CD needs: change-detection if: fromJSON(needs.change-detection.outputs.run-cd) - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-wheel-build.yml@ace7363a2a8e036e5efc6717be67f1eda29a120e # v2.4.2 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-wheel-build.yml@ae4c1005796fedc123c3c70cdba94ce643c9fcae # v2.3.1 permissions: contents: read diff --git a/.github/workflows/upstream.yml b/.github/workflows/upstream.yml index e042d4ff7..db2635c07 100644 --- a/.github/workflows/upstream.yml +++ b/.github/workflows/upstream.yml @@ -21,7 +21,7 @@ jobs: fail-fast: false matrix: runs-on: [ubuntu-24.04, macos-26, windows-2025] - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-qiskit-upstream-tests.yml@ace7363a2a8e036e5efc6717be67f1eda29a120e # v2.4.2 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-qiskit-upstream-tests.yml@ae4c1005796fedc123c3c70cdba94ce643c9fcae # v2.3.1 with: runs-on: ${{ matrix.runs-on }} permissions: @@ -31,7 +31,7 @@ jobs: name: Create issue on failure needs: qiskit-upstream-tests if: ${{ always() }} - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-qiskit-upstream-issue.yml@ace7363a2a8e036e5efc6717be67f1eda29a120e # v2.4.2 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-qiskit-upstream-issue.yml@ae4c1005796fedc123c3c70cdba94ce643c9fcae # v2.3.1 with: tests-result: ${{ needs.qiskit-upstream-tests.result }} permissions: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b6366df71..df8787167 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -54,14 +54,14 @@ repos: ## Check pyproject.toml file - repo: https://github.com/henryiii/validate-pyproject-schema-store - rev: 2026.09.04 + rev: 2026.08.29 hooks: - id: validate-pyproject priority: 0 ## Ensure uv.lock is up to date - repo: https://github.com/astral-sh/uv-pre-commit - rev: 0.12.10 + rev: 0.12.7 hooks: - id: uv-lock priority: 0 @@ -78,7 +78,7 @@ repos: ## Check for typos - repo: https://github.com/adhtruong/mirrors-typos - rev: v1.50.1 + rev: v1.50.0 hooks: - id: typos priority: 3 @@ -127,16 +127,9 @@ repos: - id: rumdl-fmt priority: 6 - ## Format pyproject.toml with pyproject-fmt - - repo: https://github.com/tox-dev/pyproject-fmt - rev: v2.29.3 - hooks: - - id: pyproject-fmt - priority: 5 - ## Format and lint Python files with ruff - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.16.6 + rev: v0.16.5 hooks: - id: ruff-check require_serial: true @@ -147,7 +140,7 @@ repos: ## Check Python types with ty - repo: https://github.com/astral-sh/ty-pre-commit - rev: v0.0.78 + rev: v0.0.75 hooks: - id: ty args: [--isolated] diff --git a/CHANGELOG.md b/CHANGELOG.md index cf5980af6..c71e265af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,19 +12,17 @@ that minor releases may include breaking changes. ## [Unreleased] -## [2.3.0] - 2026-09-12 - -_If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md#230)._ - ### Added -- ✨ Add optional structured loops to Grover's algorithm and quantum walk - ([#997]) ([**@flowerthrower**]) +- ✨ Add Steane's and Shor's encoding for benchmarks ([#930]) + ([**@Felix-Gundlach**], [**@emilkanic0909**], [**@SalehAlsherif**]) ### Changed -- ⬆️ Raise the minimum Qiskit version from 2.0.0 to 2.1.0 ([#1024]) - ([**@denialhaag**]) +- ⬆️ Increase minimum Qiskit version to 2.4.0 ([#930]) ([**@Felix-Gundlach**], + [**@emilkanic0909**], [**@SalehAlsherif**]) +- ✨ Add optional structured loops to Grover's algorithm and quantum walk + ([#997]) ([**@flowerthrower**]) - πŸ”₯ Drop support for Python 3.10 ([#999]) ([**@denialhaag**]) - 🍎 Stop testing on x86 macOS systems ([#999]) ([**@denialhaag**]) @@ -178,8 +176,7 @@ _πŸ“š Refer to the [GitHub Release Notes] for previous changelogs._ -[unreleased]: https://github.com/munich-quantum-toolkit/bench/compare/v2.3.0...HEAD -[2.3.0]: https://github.com/munich-quantum-toolkit/bench/releases/tag/v2.3.0 +[unreleased]: https://github.com/munich-quantum-toolkit/bench/compare/v2.2.3...HEAD [2.2.3]: https://github.com/munich-quantum-toolkit/bench/releases/tag/v2.2.3 [2.2.2]: https://github.com/munich-quantum-toolkit/bench/releases/tag/v2.2.2 [2.2.1]: https://github.com/munich-quantum-toolkit/bench/releases/tag/v2.2.1 @@ -191,7 +188,6 @@ _πŸ“š Refer to the [GitHub Release Notes] for previous changelogs._ -[#1024]: https://github.com/munich-quantum-toolkit/bench/pull/1024 [#1011]: https://github.com/munich-quantum-toolkit/bench/pull/1011 [#999]: https://github.com/munich-quantum-toolkit/bench/pull/999 [#997]: https://github.com/munich-quantum-toolkit/bench/pull/997 @@ -253,6 +249,9 @@ _πŸ“š Refer to the [GitHub Release Notes] for previous changelogs._ [**@TomasVF**]: https://github.com/TomasVF [**@flowerthrower**]: https://github.com/flowerthrower [**@johanneswittmann9**]: https://github.com/johanneswittmann9 +[**@Felix-Gundlach**]: https://github.com/Felix-Gundlach +[**@emilkanic0909**]: https://github.com/emilkanic0909 +[**@SalehAlsherif**]: https://github.com/SalehAlsherif [**@cnYui**]: https://github.com/cnYui diff --git a/README.md b/README.md index b4f7c5e79..9897b5683 100644 --- a/README.md +++ b/README.md @@ -34,27 +34,27 @@ throughout the quantum computing software stack. MQT Bench is hosted at - **Comprehensive Quantum Benchmark Suite:** Provides a wide range of quantum circuit benchmarks, including algorithms such as GHZ, QAOA, QFT, Grover, Shor, and many more. - [List of benchmarks](https://mqt.readthedocs.io/projects/bench/en/stable/benchmark_selection.html) + [List of benchmarks](https://mqt.readthedocs.io/projects/bench/en/latest/benchmark_selection.html) - **Cross-Level Benchmark Generation:** Supports four abstraction levelsβ€”algorithmic, target-independent, target-dependent native gates, and target-dependent mappedβ€”enabling benchmarking across the entire quantum software stack. - [Abstraction levels](https://mqt.readthedocs.io/projects/bench/en/stable/abstraction_levels.html) + [Abstraction levels](https://mqt.readthedocs.io/projects/bench/en/latest/abstraction_levels.html) - **Flexible Target and Gateset Support:** Generate circuits for various hardware targets and native gatesets, including IBM, IonQ, Quantinuum, Rigetti, and more. - [Supported devices and gatesets](https://mqt.readthedocs.io/projects/bench/en/stable/parameter.html) + [Supported devices and gatesets](https://mqt.readthedocs.io/projects/bench/en/latest/parameter.html) - **Python API, CLI, and Web Interface:** Use MQT Bench programmatically via Python, from the command line, or through an interactive web interfaceβ€”whichever fits your workflow. - [Usage guide](https://mqt.readthedocs.io/projects/bench/en/stable/usage.html) + [Usage guide](https://mqt.readthedocs.io/projects/bench/en/latest/usage.html) - **Parameterized and Mirror Circuits:** Easily generate parameterized circuits (with random or symbolic parameters) and mirror circuits for robust benchmarking and error detection. - [Quickstart](https://mqt.readthedocs.io/projects/bench/en/stable/quickstart.html) + [Quickstart](https://mqt.readthedocs.io/projects/bench/en/latest/quickstart.html) - **Export to Standard Formats:** Save generated circuits in OpenQASM 2, OpenQASM 3, and QPY formats for compatibility with other quantum tools. - [Output formats](https://mqt.readthedocs.io/projects/bench/en/stable/quickstart.html#output-formats) + [Output formats](https://mqt.readthedocs.io/projects/bench/en/latest/quickstart.html#output-formats) - **Extensible and Open Source:** Actively maintained, fully open-source, and designed for easy integration and extension within the quantum computing community. @@ -101,7 +101,7 @@ To support this endeavor, please consider: requests - Citing the MQT in your publications (see [Cite This](#cite-this)) - Citing our research in your publications (see - [References](https://mqt.readthedocs.io/projects/bench/en/stable/references.html)) + [References](https://mqt.readthedocs.io/projects/bench/en/latest/references.html)) - Using the MQT in research and teaching, and sharing feedback and use cases - Sponsoring us on GitHub: diff --git a/UPGRADING.md b/UPGRADING.md index 7f74ab62d..63d0b9e45 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -6,12 +6,10 @@ of changes including minor and patch releases, please refer to the ## [Unreleased] -## [2.3.0] +### Platform note -### Qiskit 2.1 minimum - -The minimum Qiskit version increases from **2.0.0 to 2.1.0**, dropping support -for Qiskit 2.0. Upgrade Qiskit to 2.1.0 or newer. +Starting with this release, x86 macOS is no longer tested in CI; we cannot +guarantee that MQT Bench installs and runs correctly on those systems. ### End of support for Python 3.10 @@ -19,10 +17,9 @@ Starting with this release, MQT Bench no longer supports Python 3.10. As a result, MQT Bench is no longer tested under Python 3.10 and requires Python 3.11 or later. -### macOS support +### Changed -Starting with this release, x86 macOS is no longer tested in CI; we cannot -guarantee that MQT Bench installs and runs correctly on those systems. +- ⬆️ Increase minimum Qiskit version to 2.4.0 ([#930]) ([**@Felix-Gundlach**]), ## [2.2.0] @@ -132,7 +129,7 @@ other Qiskit components. The `get_benchmark` function has been redesigned to provide a more modular and extensible way to retrieve benchmarks. As a consequence, the function signature has changed. Please see the -[API documentation](https://mqt.readthedocs.io/projects/bench/en/stable/parameter.html) +[API documentation](https://mqt.readthedocs.io/projects/bench/en/latest/parameter.html) for the updated function signature and the examples provided below. ```python @@ -208,8 +205,7 @@ accordingly. -[unreleased]: https://github.com/munich-quantum-toolkit/bench/compare/v2.3.0...HEAD -[2.3.0]: https://github.com/munich-quantum-toolkit/bench/compare/v2.2.3...v2.3.0 +[unreleased]: https://github.com/munich-quantum-toolkit/bench/compare/v2.2.1...HEAD [2.2.0]: https://github.com/munich-quantum-toolkit/bench/compare/v2.1.0...v2.2.0 [2.1.0]: https://github.com/munich-quantum-toolkit/bench/compare/v2.0.1...v2.1.0 [2.0.1]: https://github.com/munich-quantum-toolkit/bench/compare/v2.0.0...v2.0.1 diff --git a/docs/conf.py b/docs/conf.py index 5ade5d7a2..8bda44566 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -67,7 +67,7 @@ "python": ("https://docs.python.org/3", None), "typing_extensions": ("https://typing-extensions.readthedocs.io/en/latest/", None), "qiskit": ("https://docs.quantum.ibm.com/api/qiskit", None), - "mqt": ("https://mqt.readthedocs.io/en/stable/", None), + "mqt": ("https://mqt.readthedocs.io/en/latest/", None), } myst_enable_extensions = [ diff --git a/docs/contributing.md b/docs/contributing.md index 1db7b4151..6fa61cd1d 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -316,31 +316,39 @@ The documentation is written in Markdown) and built using [Sphinx](https://www.sphinx-doc.org/en/master/). The documentation source files can be found in the {code}`docs/` directory. -Tutorials use [MyST-NB](https://myst-nb.readthedocs.io/) Markdown notebooks. -Only `{code-cell}` blocks in notebook pages execute; ordinary code fences are -illustrative. Keep required setup visible, show useful output, and assert the -behavior that each example demonstrates. Use local simulation for executable -examples; leave credentials and remote-device deployment as configuration -recipes. +On top of the API documentation, we provide a set of tutorials and examples that +demonstrate how to use the library. These are written in Markdown using +[myst-nb](https://myst-nb.readthedocs.io/en/latest/), which allows executing +Python code blocks in the documentation. The code blocks are executed during the +documentation build process, and the output is included in the documentation. +This allows us to provide up-to-date examples and tutorials that are guaranteed +to work with the latest version of the library. -Use the documentation session to install Python dependencies, build the package -and generated references, and render the examples: +You can build the documentation using the {code}`nox` session {code}`docs`. ```console -uvx nox --non-interactive -s docs +nox -s docs ``` -Install the project's native build requirements first. C++ API generation needs -Doxygen; DD visualizations also need the Graphviz `dot` executable. The session -manages Python packages, not these system tools. +This will install all dependencies for building the documentation in an isolated +environment, build the Python package, and then build the documentation. It will +then host the documentation on a local web server for you to view. -Omit `--non-interactive` to serve the documentation while editing. To check -external links, run: +:::{note} + +If you do not want to use {code}`nox`, you can also build the documentation +directly using {code}`sphinx-build`. This requires that you have the project and +its documentation dependencies installed in your virtual environment (e.g., by +running {code}`uv sync`). ```console -uvx nox --non-interactive -s docs -- -b linkcheck +sphinx-build -b html docs/ docs/_build ``` +The docs can then be found in the {code}`docs/_build` directory. + +::: + ## Tips for Development If something goes wrong, the CI pipeline will notify you. Here are some tips for diff --git a/docs/parameter.md b/docs/parameter.md index c9112eb70..fc4f89d3c 100644 --- a/docs/parameter.md +++ b/docs/parameter.md @@ -56,6 +56,17 @@ print(get_available_device_names()) they exist. - `generate_mirror_circuit`: Generate the mirror version (U @ U.inverse()) of the benchmark. +- `encoding`: Error correction code applied to the output. Encoding of + non-transversal gates uses placeholders. + +Possible values for `encoding`: + +```text +{code-cell} ipython3 +:tags: [hide-input] +from mqt.bench.error_correction import get_available_encoding_names + +print(get_available_encoding_names())``` ## Native Gate-Set Support diff --git a/noxfile.py b/noxfile.py index dfdd05a53..bbfb95385 100755 --- a/noxfile.py +++ b/noxfile.py @@ -114,7 +114,7 @@ def qiskit(session: nox.Session) -> None: session.run("uv", "pip", "show", "qiskit", env=env) -@nox.session(python="3.14", reuse_venv=True) +@nox.session(reuse_venv=True) def docs(session: nox.Session) -> None: """Build the docs. Use "--non-interactive" to avoid serving. Pass "-b linkcheck" to check links.""" parser = argparse.ArgumentParser() diff --git a/pyproject.toml b/pyproject.toml index 9b9484160..9f3973fa9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,55 +1,89 @@ [build-system] +requires = [ + "hatchling>=1.27.0", + "hatch-vcs>=0.4.0" +] build-backend = "hatchling.build" -requires = [ "hatch-vcs>=0.4", "hatchling>=1.27" ] - [project] -name = "mqt-bench" +name = "mqt.bench" description = "MQT Bench - A MQT tool for Benchmarking Quantum Software Tools" readme = "README.md" -keywords = [ "benchmarking", "MQT", "performance", "quantum computing", "testing" ] -license = "MIT" -license-files = [ "LICENSE" ] -requires-python = ">=3.11" -classifiers = [ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "Intended Audience :: Information Technology", - "Intended Audience :: Science/Research", - "Natural Language :: English", - "Operating System :: MacOS", - "Operating System :: Microsoft :: Windows", - "Operating System :: POSIX :: Linux", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: 3.14", - "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)", - "Topic :: Scientific/Engineering :: Quantum Computing", - "Typing :: Typed", +authors = [ + { name = "Nils Quetschlich", email = "nils.quetschlich@tum.de" }, + { name = "Lukas Burgholzer", email = "lukas.burgholzer@tum.de"}, ] -dynamic = [ "version" ] +keywords = ["MQT", "quantum computing", "benchmarking", "performance", "testing"] +license = "MIT" +license-files = ["LICENSE"] +requires-python = ">=3.11, !=3.14.1" +dynamic = ["version"] + dependencies = [ - "networkx>=2.8.8", - "numpy>=1.24", - "numpy>=1.26; python_version>='3.12'", - "numpy>=2.1; python_version>='3.13'", - "numpy>=2.3.2; python_version>='3.14'", - "qiskit[qasm3-import]>=2.1", - "scikit-learn>=1.5.2", - "scikit-learn>=1.7.2; python_version>='3.14'", + # Qiskit 1.3.2 contains some fixes for exporting OpenQASM 3 files + # Qiskit 2.0.0 fixes the SolovayKitaev transpiler for mid-circuit measurements + # Qiskit 2.4.0 fixes the transpilation to Clifford+T basis + "qiskit[qasm3-import]>=2.4.0", + "networkx>=2.8.8", + "numpy>=1.24", + "numpy>=1.26; python_version >= '3.12'", + "numpy>=2.1; python_version >= '3.13'", + "numpy>=2.3.2; python_version >= '3.14'", + "scikit-learn>=1.5.2", + "scikit-learn>=1.7.2; python_version >= '3.14'", ] -[[project.authors]] -name = "Nils Quetschlich" -email = "nils.quetschlich@tum.de" +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Intended Audience :: Information Technology", + "Intended Audience :: Science/Research", + "Natural Language :: English", + "Operating System :: MacOS", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)", + "Topic :: Scientific/Engineering :: Quantum Computing", + "Typing :: Typed", +] + +[dependency-groups] +test = [ + "pytest>=9.0.1", + "pytest-console-scripts>=1.4.1", + "pytest-cov>=7.1.0", + "pytest-sugar>=1.1.1", + "pytest-xdist>=3.8.0", +] +docs = [ + "furo>=2025.09.25", + "myst-nb>=1.3.0", + "openqasm-pygments>=0.2.0", + "sphinx>=8.2.3", + "sphinx-design>=0.6.1", + "sphinx-llm>=0.4.1", + "sphinx-autoapi>=3.6.0", + "sphinx-copybutton>=0.5.2", + "sphinxcontrib-bibtex>=2.6.5", + "sphinxext-opengraph>=0.13.0", + "qiskit[visualization]>=1.2.1", +] +dev = [ + {include-group = "test"}, + "nox>=2025.11.12", + "prek>=0.2.27", +] -[[project.authors]] -name = "Lukas Burgholzer" -email = "burgholzer@me.com" +[project.scripts] +"mqt.bench.cli" = "mqt.bench.cli:main" # Kept for backwards compatibility. To be removed in mqt-bench v3 +"mqt-bench" = "mqt.bench.cli:main" [project.urls] Homepage = "https://github.com/munich-quantum-toolkit/bench" @@ -57,38 +91,9 @@ Homepage = "https://github.com/munich-quantum-toolkit/bench" Discussions = "https://github.com/munich-quantum-toolkit/bench/discussions" Research = "https://www.cda.cit.tum.de/research/quantum/" -[project.scripts] -"mqt.bench.cli" = "mqt.bench.cli:main" # Kept for backwards compatibility. To be removed in mqt-bench v3 -mqt-bench = "mqt.bench.cli:main" - - -[dependency-groups] -dev = [ - { include-group = "test" }, - "nox>=2025.11.12", - "prek>=0.2.27", -] -test = [ - "pytest>=9.0.1", - "pytest-console-scripts>=1.4.1", - "pytest-cov>=7.1", - "pytest-sugar>=1.1.1", - "pytest-xdist>=3.8", -] -docs = [ - "furo>=2025.12.19", - "myst-nb>=1.4", - "openqasm-pygments>=0.2", - "qiskit[visualization]>=2.1", - "sphinx>=9", - "sphinx-autoapi>=3.6", - "sphinx-copybutton>=0.5.2", - "sphinx-design>=0.7", - "sphinx-llm>=1", - "sphinxcontrib-bibtex>=2.7", - "sphinxext-opengraph>=0.13", -] +[tool.hatch.build.targets.wheel] +packages = ["src/mqt"] [tool.hatch.version] source = "vcs" @@ -96,17 +101,56 @@ source = "vcs" [tool.hatch.build.hooks.vcs] version-file = "src/mqt/bench/_version.py" -[tool.hatch.build.targets.wheel] -packages = [ "src/mqt" ] +[tool.check-sdist] +sdist-only = ["src/mqt/bench/_version.py"] -[tool.uv.sources] -mqt-bench = { workspace = true } + +[tool.pytest] +minversion = "9.0" +testpaths = ["tests"] +strict = true +addopts = [ + "-ra", + "--showlocals", + "--numprocesses=auto", # Automatically use all available CPU cores for parallel testing +] +log_level = "INFO" +filterwarnings = [ + "error", + 'ignore:.*pkg_resources.*:DeprecationWarning:', + 'ignore:.*Values in x.*:RuntimeWarning:', + 'ignore:.*divide by zero encountered in det.*:RuntimeWarning:', + 'ignore:.*invalid value encountered in det.*:RuntimeWarning:', + 'ignore:.*invalid value encountered in divide.*:RuntimeWarning:', + 'ignore:.*The iteration is not making good progress.*:RuntimeWarning:', + # Qiskit-internal warning + 'ignore:.*Trying to add QuantumRegister to a QuantumCircuit having a layout.*:UserWarning:qiskit.*', + # Qiskit 2.3 deprecations + 'ignore:.*argument ``annotated`` is deprecated as of Qiskit 2.3.*:DeprecationWarning:', +] + +[tool.coverage] +run.source = ["mqt.bench"] +run.core = "sysmon" +run.parallel = true +run.disable_warnings = [ + "no-sysmon", +] +report.exclude_also = [ + '\.\.\.', + 'if TYPE_CHECKING:', + 'raise AssertionError', + 'raise NotImplementedError', +] +show_missing = true +skip_empty = true +precision = 1 [tool.ruff] line-length = 120 -namespace-packages = [ "mqt" ] +namespace-packages = ["mqt"] preview = true fix = true unsafe-fixes = true @@ -116,92 +160,84 @@ show-fixes = true preview = true docstring-code-format = true -[tool.ruff.lint.per-file-ignores] -"*.pyi" = [ "D" ] # pydocstyle -"*.ipynb" = [ - "D", # pydocstyle - "E402", # Allow imports to appear anywhere in Jupyter notebooks - "I002", # Allow missing `from __future__ import annotations` import -] - -[tool.ruff.lint.isort] -known-first-party = [ "mqt.bench" ] - -[tool.ruff.lint.pydocstyle] -convention = "google" - [tool.ruff.lint] extend-select = [ - "A", # flake8-builtins - "ANN", # flake8-annotations - "ARG", # flake8-unused-arguments - "ASYNC", # flake8-async - "B", # flake8-bugbear - "C4", # flake8-comprehensions - "D", # pydocstyle - "EM", # flake8-errmsg - "EXE", # flake8-executable - "FA", # flake8-future-annotations - "FLY", # flynt - "FURB", # refurb - "I", # isort - "ICN", # flake8-import-conventions - "ISC", # flake8-implicit-str-concat - "LOG", # flake8-logging-format - "N", # flake8-naming - "NPY", # numpy - "PERF", # perflint - "PGH", # pygrep-hooks - "PIE", # flake8-pie - "PL", # pylint - "PT", # flake8-pytest-style - "PTH", # flake8-use-pathlib - "PYI", # flake8-pyi - "Q", # flake8-quotes - "RET", # flake8-return - "RSE", # flake8-raise - "RUF", # Ruff-specific - "SIM", # flake8-simplify - "SLF", # flake8-self - "SLOT", # flake8-slots - "TC", # flake8-type-checking - "TID251", # flake8-tidy-imports - "TRY", # tryceratops - "UP", # pyupgrade - "YTT", # flake8-2020 + "A", # flake8-builtins + "ANN", # flake8-annotations + "ARG", # flake8-unused-arguments + "ASYNC", # flake8-async + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "D", # pydocstyle + "EM", # flake8-errmsg + "EXE", # flake8-executable + "FA", # flake8-future-annotations + "FLY", # flynt + "FURB", # refurb + "I", # isort + "ICN", # flake8-import-conventions + "ISC", # flake8-implicit-str-concat + "LOG", # flake8-logging-format + "N", # flake8-naming + "NPY", # numpy + "PERF", # perflint + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PL", # pylint + "PT", # flake8-pytest-style + "PTH", # flake8-use-pathlib + "PYI", # flake8-pyi + "Q", # flake8-quotes + "RET", # flake8-return + "RSE", # flake8-raise + "RUF", # Ruff-specific + "SLF", # flake8-self + "SLOT", # flake8-slots + "SIM", # flake8-simplify + "TC", # flake8-type-checking + "TID251", # flake8-tidy-imports + "TRY", # tryceratops + "UP", # pyupgrade + "YTT", # flake8-2020 ] ignore = [ - "E501", # Line too long (Black is enough) - "PLR", # Design related pylint codes - "S101", # Use of assert detected + "E501", # Line too long (Black is enough) + "PLR", # Design related pylint codes + "S101", # Use of assert detected ] flake8-unused-arguments.ignore-variadic-names = true future-annotations = true +[tool.ruff.lint.isort] +known-first-party = ["mqt.bench"] -[tool.repo-review.ignore] -GH200 = "We use Renovate instead of Dependabot" -PC160 = "We use a mirror of crate-ci/typos" -PC170 = "We do not use rST files anymore" +[tool.ruff.lint.per-file-ignores] +"*.pyi" = ["D"] # pydocstyle +"*.ipynb" = [ + "D", # pydocstyle + "E402", # Allow imports to appear anywhere in Jupyter notebooks + "I002", # Allow missing `from __future__ import annotations` import +] +[tool.ruff.lint.pydocstyle] +convention = "google" -[tool.check-sdist] -sdist-only = [ "src/mqt/bench/_version.py" ] +[tool.uv] +# symengine>=0.11,<0.14 (optional dependency of qiskit) is incompatible with Python 3.14 +override-dependencies = [ + "symengine>=0.11,<0.14", + "symengine>=0.14.1; python_version >= '3.14'", +] -[tool.pyproject-fmt] -generate_python_version_classifiers = false -table_format = "long" -sub_table_spacing = "\n" -separate_root_table = "\n\n" +[tool.uv.sources] +mqt-bench = { workspace = true } [tool.typos] - -[tool.typos.default] -extend-ignore-re = [ +default.extend-ignore-re = [ '"id": ".*",', - "(?Rm)^.*(#|//)\\s*spellchecker:disable-line$", # ignore line + "(?Rm)^.*(#|//)\\s*spellchecker:disable-line$", # ignore line "(?s)(#|//)\\s*spellchecker:off.*?\\n\\s*(#|//)\\s*spellchecker:on" # ignore block ] @@ -214,52 +250,17 @@ bench = "bench" benchs = "benchs" -[tool.ty.src] -exclude = [ - "docs/**", -] +[tool.repo-review.ignore] +GH200 = "We use Renovate instead of Dependabot" +PC160 = "We use a mirror of crate-ci/typos" +PC170 = "We do not use rST files anymore" + [tool.ty.terminal] # Error if ty emits any warning-level diagnostics. error-on-warning = true - -[tool.pytest] -addopts = [ - "-ra", - "--showlocals", - "--numprocesses=auto", # Automatically use all available CPU cores for parallel testing -] -filterwarnings = [ - "error", - # Qiskit-internal warning - "ignore:.*Trying to add QuantumRegister to a QuantumCircuit having a layout.*:UserWarning:qiskit.*", - # Qiskit 2.3 deprecations - "ignore:.*argument ``annotated`` is deprecated as of Qiskit 2.3.*:DeprecationWarning:", -] -log_level = "INFO" -minversion = "9.0" -strict = true -testpaths = [ "tests" ] - - -[tool.coverage] -precision = 1 -show_missing = true -skip_empty = true - -[tool.coverage.run] -source = [ "mqt.bench" ] -parallel = true -disable_warnings = [ - "no-sysmon", -] -core = "sysmon" - -[tool.coverage.report] -exclude_also = [ - '\.\.\.', - "if TYPE_CHECKING:", - "raise AssertionError", - "raise NotImplementedError", +[tool.ty.src] +exclude = [ + "docs/**", ] diff --git a/src/mqt/bench/benchmark_generation.py b/src/mqt/bench/benchmark_generation.py index d4be806c2..9ec48c1c0 100644 --- a/src/mqt/bench/benchmark_generation.py +++ b/src/mqt/bench/benchmark_generation.py @@ -10,6 +10,7 @@ from __future__ import annotations +import warnings from enum import Enum, auto from typing import TYPE_CHECKING, Unpack, assert_never, overload @@ -21,6 +22,7 @@ from qiskit.transpiler import Layout, Target from .benchmarks import create_circuit +from .error_correction import get_available_encoding_names, get_transpiler from .targets.gatesets import get_target_for_gateset, ionq, rigetti if TYPE_CHECKING: # pragma: no cover @@ -159,6 +161,7 @@ def _validate_opt_level(opt_level: int) -> None: def get_benchmark_alg( benchmark: str, circuit_size: int, + encoding: str = "", *, generate_mirror_circuit: bool = False, random_parameters: bool = True, @@ -170,6 +173,7 @@ def get_benchmark_alg( def get_benchmark_alg( benchmark: QuantumCircuit, circuit_size: None = None, + encoding: str = "", *, generate_mirror_circuit: bool = False, random_parameters: bool = True, @@ -181,6 +185,7 @@ def get_benchmark_alg( def get_benchmark_alg( benchmark: str | QuantumCircuit, circuit_size: int | None = None, + encoding: str = "", *, generate_mirror_circuit: bool = False, random_parameters: bool = True, @@ -191,6 +196,7 @@ def get_benchmark_alg( def get_benchmark_alg( benchmark: str | QuantumCircuit, circuit_size: int | None = None, + encoding: str = "", *, generate_mirror_circuit: bool = False, random_parameters: bool = True, @@ -201,6 +207,7 @@ def get_benchmark_alg( Arguments: benchmark: QuantumCircuit or name of the benchmark to be generated circuit_size: Input for the benchmark creation, in most cases this is equal to the qubit number + encoding: Error correction code to be used, can be any of {"", "steane", "shor"}. generate_mirror_circuit: If True, generates the mirror version (U @ U.inverse()) of the benchmark. random_parameters: If True, assigns random parameters to the circuit's parameters if they exist. kwargs: Additional keyword arguments passed to the circuit creation. @@ -208,9 +215,20 @@ def get_benchmark_alg( Returns: Qiskit::QuantumCircuit representing the raw benchmark circuit without any hardware-specific compilation or mapping. """ + valid_encodings = {"", *get_available_encoding_names()} + if encoding not in valid_encodings: + msg = f"Invalid `encoding` '{encoding}'. Must be one of {valid_encodings}." + raise ValueError(msg) + qc = _get_circuit(benchmark, circuit_size, random_parameters, **kwargs) + if generate_mirror_circuit: - return _create_mirror_circuit(qc, inplace=True) + qc = _create_mirror_circuit(qc, inplace=True) + + if encoding: + transpiler = get_transpiler(encoding)(qc) + qc = transpiler.transpile() + return qc @@ -471,6 +489,7 @@ def get_benchmark( circuit_size: int, target: Target | None = None, opt_level: int = 2, + encoding: str = "", *, generate_mirror_circuit: bool = False, random_parameters: bool = True, @@ -485,6 +504,7 @@ def get_benchmark( circuit_size: None, target: Target | None = None, opt_level: int = 2, + encoding: str = "", *, generate_mirror_circuit: bool = False, random_parameters: bool = True, @@ -499,6 +519,7 @@ def get_benchmark( circuit_size: int | None = None, target: Target | None = None, opt_level: int = 2, + encoding: str = "", *, generate_mirror_circuit: bool = False, random_parameters: bool = True, @@ -512,6 +533,7 @@ def get_benchmark( circuit_size: int | None = None, target: Target | None = None, opt_level: int = 2, + encoding: str = "", *, generate_mirror_circuit: bool = False, random_parameters: bool = True, @@ -526,6 +548,8 @@ def get_benchmark( target: `~qiskit.transpiler.target.Target` for the benchmark generation (only used for "nativegates" and "mapped" level) opt_level: Optimization level to be used by the transpiler. + encoding: Error correction code to be used, can be any of {"", "steane", "shor"}. + Only supported for `BenchmarkLevel.ALG`; ignored (with a warning) for other levels. generate_mirror_circuit: If True, generates the mirror version (U @ U.inverse()) of the benchmark. random_parameters: If True, assigns random parameters to the circuit's parameters if they exist. kwargs: Additional keyword arguments passed to the circuit creation. @@ -533,12 +557,20 @@ def get_benchmark( Returns: Qiskit::QuantumCircuit object representing the benchmark with the selected options """ + if level is not BenchmarkLevel.ALG and encoding: + warnings.warn( + f"`encoding` is only supported for BenchmarkLevel.ALG and will be ignored for level={level.name}.", + UserWarning, + stacklevel=2, + ) + if level is BenchmarkLevel.ALG: return get_benchmark_alg( benchmark=benchmark, circuit_size=circuit_size, generate_mirror_circuit=generate_mirror_circuit, random_parameters=random_parameters, + encoding=encoding, **kwargs, ) diff --git a/src/mqt/bench/benchmarks/seven_qubit_steane_code.py b/src/mqt/bench/benchmarks/seven_qubit_steane_code.py index b8d1912e4..3fdf86f10 100644 --- a/src/mqt/bench/benchmarks/seven_qubit_steane_code.py +++ b/src/mqt/bench/benchmarks/seven_qubit_steane_code.py @@ -13,127 +13,14 @@ from qiskit import ClassicalRegister from qiskit.circuit import AncillaRegister, QuantumCircuit, QuantumRegister -from ._registry import register_benchmark - - -def _get_seven_qubit_steane_code_encoding_circuit() -> QuantumCircuit: - """Create the 7-qubit Steane code encoding circuit. - - Encodes qubit 0 into the 7-qubit Steane code logical state: - - |0> -> (|0000000> + |1010101> + |0110011> + |1100110> + |0001111> + |1011010> + |0111100> + |1101001>) - - |1> -> (|1111111> + |0101010> + |1001100> + |0011001> + |1110000> + |0100101> + |1000011> + |0010110>). - - Returns: - QuantumCircuit: 7-qubit encoding circuit. - """ - out = QuantumCircuit(7) - # H - out.h(4) - out.h(5) - out.h(6) - # CNOT from 0 - out.cx(0, 1) - out.cx(0, 2) - # CNOT from 6 - out.cx(6, 3) - out.cx(6, 1) - out.cx(6, 0) - # CNOT from 5 - out.cx(5, 3) - out.cx(5, 2) - out.cx(5, 0) - # CNOT from 4 - out.cx(4, 3) - out.cx(4, 2) - out.cx(4, 1) - return out - - -def _get_seven_qubit_steane_code_decoding_circuit() -> QuantumCircuit: - """Create the 7-qubit Steane code decoding circuit. - - Reverses the encoding operation to extract the logical qubit back to qubit 0. - - Returns: - QuantumCircuit: 7-qubit decoding circuit (qubit 0 is the output qubit). - """ - return _get_seven_qubit_steane_code_encoding_circuit().inverse() - +from mqt.bench.components.steane_circuit_components import ( + apply_seven_qubit_steane_code_correction, + get_seven_qubit_steane_code_decoding_circuit, + get_seven_qubit_steane_code_encoding_circuit, + get_seven_qubit_steane_code_syndrome_extraction_circuit, +) -def _get_seven_qubit_steane_code_syndrome_extraction_circuit() -> QuantumCircuit: - """Create the syndrome extraction circuit for the 7-qubit Steane code. - - Extracts bit-flip and phase-flip syndromes using 6 ancilla qubits (3 for each type). - - Bit-flip syndrome extraction: - Syndrome bits measure the parity of specific qubit subsets corresponding to - the X-stabilizer generators. - - Phase-flip syndrome extraction: - Uses Hadamard gates to convert from Z to X basis, and control/target swapped - CNOTs to extract the phase-flip syndrome - - Syndrome mapping: The 3-bit syndrome value (1-7) directly identifies which - data qubit experienced an error. Syndrome 0 indicates no error. - - Returns: - QuantumCircuit: 13-qubit circuit (qubits 0-6 are data, 7-9 are bit-flip - syndrome ancillas, 10-12 are phase-flip syndrome ancillas). - """ - logical_qubit, bit_flip_syndrome, phase_flip_syndrome = QuantumRegister(7), AncillaRegister(3), AncillaRegister(3) - out = QuantumCircuit(logical_qubit, bit_flip_syndrome, phase_flip_syndrome) - # Bit-flip - for ctrl in (0, 2, 4, 6): - out.cx(logical_qubit[ctrl], bit_flip_syndrome[0]) - for ctrl in (1, 2, 5, 6): - out.cx(logical_qubit[ctrl], bit_flip_syndrome[1]) - for ctrl in (3, 4, 5, 6): - out.cx(logical_qubit[ctrl], bit_flip_syndrome[2]) - # Phase-flip - for i in range(3): - out.h(phase_flip_syndrome[i]) - for targ in (0, 2, 4, 6): - out.cx(phase_flip_syndrome[0], logical_qubit[targ]) - for targ in (1, 2, 5, 6): - out.cx(phase_flip_syndrome[1], logical_qubit[targ]) - for targ in (3, 4, 5, 6): - out.cx(phase_flip_syndrome[2], logical_qubit[targ]) - for i in range(3): - out.h(phase_flip_syndrome[i]) - return out - - -def _apply_seven_qubit_steane_code_correction( - qc: QuantumCircuit, - logical_qubit: QuantumRegister, - bit_flip_syndrome: AncillaRegister, - phase_flip_syndrome: AncillaRegister, - bit_flip_syndrome_measurement: ClassicalRegister, - phase_flip_syndrome_measurement: ClassicalRegister, -) -> None: - """Apply error correction based on syndrome measurements. - - Measures the 6 syndrome qubits and conditionally applies X/Z gates to correct - single-qubit errors on any of the 7 data qubits. - - Arguments: - qc: The quantum circuit to modify. - logical_qubit: Register containing the 7 data qubits. - bit_flip_syndrome: Register containing the 3 bit-flip syndrome qubits. - phase_flip_syndrome: Register containing the 3 phase-flip syndrome qubits. - bit_flip_syndrome_measurement: Classical register for bit-flip syndrome results. - phase_flip_syndrome_measurement: Classical register for phase-flip syndrome results. - """ - qc.measure(bit_flip_syndrome, bit_flip_syndrome_measurement) - qc.measure(phase_flip_syndrome, phase_flip_syndrome_measurement) - # Bit-flip correction: syndrome value directly indicates which qubit to correct - for i in range(7): - with qc.if_test((bit_flip_syndrome_measurement, i + 1)): - qc.x(logical_qubit[i]) - # Phase-flip correction: syndrome value directly indicates which qubit to correct - for i in range(7): - with qc.if_test((phase_flip_syndrome_measurement, i + 1)): - qc.z(logical_qubit[i]) +from ._registry import register_benchmark def _create_single_logical_qubit_circuit(index: int) -> QuantumCircuit: @@ -164,20 +51,20 @@ def _create_single_logical_qubit_circuit(index: int) -> QuantumCircuit: ) # == Encoding == qc.compose( - _get_seven_qubit_steane_code_encoding_circuit(), + get_seven_qubit_steane_code_encoding_circuit(), qubits=logical_qubit[:], inplace=True, ) qc.barrier() # == Syndrome extraction == qc.compose( - _get_seven_qubit_steane_code_syndrome_extraction_circuit(), + get_seven_qubit_steane_code_syndrome_extraction_circuit(), qubits=logical_qubit[:] + bit_flip_syndrome[:] + phase_flip_syndrome[:], inplace=True, ) qc.barrier() # == Error correction == - _apply_seven_qubit_steane_code_correction( + apply_seven_qubit_steane_code_correction( qc, logical_qubit, bit_flip_syndrome, @@ -188,7 +75,7 @@ def _create_single_logical_qubit_circuit(index: int) -> QuantumCircuit: qc.barrier() # == Decoding == qc.compose( - _get_seven_qubit_steane_code_decoding_circuit(), + get_seven_qubit_steane_code_decoding_circuit(), qubits=logical_qubit[:], inplace=True, ) diff --git a/src/mqt/bench/benchmarks/shors_nine_qubit_code.py b/src/mqt/bench/benchmarks/shors_nine_qubit_code.py index 5ea409ce7..ca728a28e 100644 --- a/src/mqt/bench/benchmarks/shors_nine_qubit_code.py +++ b/src/mqt/bench/benchmarks/shors_nine_qubit_code.py @@ -13,166 +13,17 @@ from qiskit import ClassicalRegister from qiskit.circuit import AncillaRegister, QuantumCircuit, QuantumRegister -from ._registry import register_benchmark - - -def _get_three_qubit_bit_flip_encoding_decoding_circuit() -> QuantumCircuit: - """Create 3-qubit bit-flip encoding/decoding circuit. - - Encodes |0> β†’ |000> and |1> β†’ |111>. Self-inverse, so used for both encoding and decoding. - - Returns: - QuantumCircuit: 3-qubit circuit (qubit 0 is the input/output qubit). - """ - out = QuantumCircuit(3) - out.cx(0, 1) - out.cx(0, 2) - return out - - -def _get_three_qubit_phase_flip_encoding_circuit() -> QuantumCircuit: - """Create 3-qubit phase-flip encoding circuit. - - Encodes |0> β†’ |+++> and |1> β†’ |---> - - Returns: - QuantumCircuit: 3-qubit encoding circuit (qubit 0 is the input qubit). - """ - out = QuantumCircuit(3) - out.cx(0, 1) - out.cx(0, 2) - out.h(0) - out.h(1) - out.h(2) - return out - - -def _get_three_qubit_phase_flip_decoding_circuit() -> QuantumCircuit: - """Create 3-qubit phase-flip decoding circuit. - - Reverses the phase-flip encoding. - - Returns: - QuantumCircuit: 3-qubit decoding circuit (qubit 0 is the output qubit). - """ - out = QuantumCircuit(3) - out.h(0) - out.h(1) - out.h(2) - out.cx(0, 1) - out.cx(0, 2) - return out - - -def _get_three_qubit_bit_flip_syndrome_extraction_circuit() -> QuantumCircuit: - """Create circuit to extract bit-flip syndrome from a 3-qubit block. - - Uses 2 ancilla qubits to measure parity and identify which qubit (if any) flipped. - Syndrome mapping: 01 β†’ qubit 0, 10 β†’ qubit 1, 11 β†’ qubit 2, 00 β†’ no error. +from mqt.bench.components.shor_circuit_components import ( + apply_nine_qubit_shors_code_bit_flip_correction, + apply_nine_qubit_shors_code_phase_flip_correction, + get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit, + get_three_qubit_bit_flip_encoding_decoding_circuit, + get_three_qubit_bit_flip_syndrome_extraction_circuit, + get_three_qubit_phase_flip_decoding_circuit, + get_three_qubit_phase_flip_encoding_circuit, +) - Returns: - QuantumCircuit: 5-qubit circuit (qubits 0-2 are data, qubits 3-4 are syndrome ancillas). - """ - out = QuantumCircuit(5) - out.cx(0, 3) - out.cx(1, 4) - out.cx(2, 3) - out.cx(2, 4) - return out - - -def _get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit() -> QuantumCircuit: - """Create circuit to extract phase-flip syndrome across the three 3-qubit blocks. - - Detects which block (if any) experienced a phase flip using 2 ancilla qubits. - Syndrome mapping: 01 β†’ block 1 (qubits 0-2), 10 β†’ block 2 (qubits 3-5), - 11 β†’ block 3 (qubits 6-8), 00 β†’ no error. - - Returns: - QuantumCircuit: 11-qubit circuit (qubits 0-8 are data, qubits 9-10 are syndrome ancillas). - """ - logical_qubit, phase_flip_syndrome = QuantumRegister(9), AncillaRegister(2) - out = QuantumCircuit(logical_qubit, phase_flip_syndrome) - # The order on the CNOT gates below is reversed when compared to what one might expect - # with the control being the ancilla, and the target being one of the component qubits of the logical qubit - # This is because we put Hadamards at the starts and ends of the ancilla bits, in order to check the phase - # of the logical qubits as opposed to the amplitude. - # But this also effectively swaps the order of the control and target, so we swap them back to normal - out.h(phase_flip_syndrome[0]) - out.h(phase_flip_syndrome[1]) - # Syndrome 01 (block 1) - for i in range(3): - out.cx(phase_flip_syndrome[0], logical_qubit[i]) - # Syndrome 10 (block 2) - for i in range(3, 6): - out.cx(phase_flip_syndrome[1], logical_qubit[i]) - # Syndrome 11 (block 3) - for i in range(6, 9): - out.cx(phase_flip_syndrome[0], logical_qubit[i]) - out.cx(phase_flip_syndrome[1], logical_qubit[i]) - out.h(phase_flip_syndrome[0]) - out.h(phase_flip_syndrome[1]) - return out - - -def _apply_nine_qubit_shors_code_bit_flip_correction( - qc: QuantumCircuit, - logical_qubit: QuantumRegister, - bit_flip_syndrome: AncillaRegister, - bit_flip_syndrome_measurement: ClassicalRegister, -) -> None: - """Apply bit-flip correction based on syndrome measurement. - - Measures the 6 syndrome qubits and conditionally applies X gates to correct - bit-flip errors on any of the 9 data qubits. - - Arguments: - qc: The quantum circuit to modify. - logical_qubit: Register containing the 9 data qubits. - bit_flip_syndrome: Ancilla register containing the 6 syndrome qubits. - bit_flip_syndrome_measurement: Classical register for syndrome measurement results. - """ - qc.measure(bit_flip_syndrome, bit_flip_syndrome_measurement) - # Note that Qiskit uses little-endian bit order - for index, syndrome in enumerate([ - 0b000001, - 0b000010, - 0b000011, - 0b000100, - 0b001000, - 0b001100, - 0b010000, - 0b100000, - 0b110000, - ]): - with qc.if_test((bit_flip_syndrome_measurement, syndrome)): - qc.x(logical_qubit[index]) - - -def _apply_nine_qubit_shors_code_phase_flip_correction( - qc: QuantumCircuit, - logical_qubit: QuantumRegister, - phase_flip_syndrome: AncillaRegister, - phase_flip_syndrome_measurement: ClassicalRegister, -) -> None: - """Apply phase-flip correction based on syndrome measurement. - - Measures the 2 syndrome qubits and conditionally applies Z gates to correct - phase-flip errors on the first qubit of the affected block. - - Arguments: - qc: The quantum circuit to modify. - logical_qubit: Register containing the 9 data qubits. - phase_flip_syndrome: Ancilla register containing the 2 syndrome qubits. - phase_flip_syndrome_measurement: Classical register for syndrome measurement results. - """ - qc.measure(phase_flip_syndrome, phase_flip_syndrome_measurement) - with qc.if_test((phase_flip_syndrome_measurement, 0b01)): - qc.z(logical_qubit[0]) - with qc.if_test((phase_flip_syndrome_measurement, 0b10)): - qc.z(logical_qubit[3]) - with qc.if_test((phase_flip_syndrome_measurement, 0b11)): - qc.z(logical_qubit[6]) +from ._registry import register_benchmark def _create_single_logical_qubit_circuit(index: int) -> QuantumCircuit: @@ -204,53 +55,51 @@ def _create_single_logical_qubit_circuit(index: int) -> QuantumCircuit: # == Encoding == # Apply phase flip encoding on the first qubit of each bit-flip block qc.compose( - _get_three_qubit_phase_flip_encoding_circuit(), + get_three_qubit_phase_flip_encoding_circuit(), qubits=[logical_qubit[0], logical_qubit[3], logical_qubit[6]], inplace=True, ) # Apply bit flip encoding on each block - qc.compose(_get_three_qubit_bit_flip_encoding_decoding_circuit(), qubits=logical_qubit[:3], inplace=True) - qc.compose(_get_three_qubit_bit_flip_encoding_decoding_circuit(), qubits=logical_qubit[3:6], inplace=True) - qc.compose(_get_three_qubit_bit_flip_encoding_decoding_circuit(), qubits=logical_qubit[6:9], inplace=True) + qc.compose(get_three_qubit_bit_flip_encoding_decoding_circuit(), qubits=logical_qubit[:3], inplace=True) + qc.compose(get_three_qubit_bit_flip_encoding_decoding_circuit(), qubits=logical_qubit[3:6], inplace=True) + qc.compose(get_three_qubit_bit_flip_encoding_decoding_circuit(), qubits=logical_qubit[6:9], inplace=True) qc.barrier() # == Syndrome extraction == qc.compose( - _get_three_qubit_bit_flip_syndrome_extraction_circuit(), + get_three_qubit_bit_flip_syndrome_extraction_circuit(), qubits=logical_qubit[:3] + bit_flip_syndrome[:2], inplace=True, ) qc.compose( - _get_three_qubit_bit_flip_syndrome_extraction_circuit(), + get_three_qubit_bit_flip_syndrome_extraction_circuit(), qubits=logical_qubit[3:6] + bit_flip_syndrome[2:4], inplace=True, ) qc.compose( - _get_three_qubit_bit_flip_syndrome_extraction_circuit(), + get_three_qubit_bit_flip_syndrome_extraction_circuit(), qubits=logical_qubit[6:9] + bit_flip_syndrome[4:6], inplace=True, ) qc.barrier() qc.compose( - _get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit(), + get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit(), qubits=logical_qubit[:] + phase_flip_syndrome[:], inplace=True, ) qc.barrier() # == Error correction == - _apply_nine_qubit_shors_code_bit_flip_correction( - qc, logical_qubit, bit_flip_syndrome, bit_flip_syndrome_measurement - ) + apply_nine_qubit_shors_code_bit_flip_correction(qc, logical_qubit, bit_flip_syndrome, bit_flip_syndrome_measurement) qc.barrier() - _apply_nine_qubit_shors_code_phase_flip_correction( + apply_nine_qubit_shors_code_phase_flip_correction( qc, logical_qubit, phase_flip_syndrome, phase_flip_syndrome_measurement ) qc.barrier() # == Decoding == - qc.compose(_get_three_qubit_bit_flip_encoding_decoding_circuit(), qubits=logical_qubit[:3], inplace=True) - qc.compose(_get_three_qubit_bit_flip_encoding_decoding_circuit(), qubits=logical_qubit[3:6], inplace=True) - qc.compose(_get_three_qubit_bit_flip_encoding_decoding_circuit(), qubits=logical_qubit[6:9], inplace=True) + qc.compose(get_three_qubit_bit_flip_encoding_decoding_circuit(), qubits=logical_qubit[:3], inplace=True) + qc.compose(get_three_qubit_bit_flip_encoding_decoding_circuit(), qubits=logical_qubit[3:6], inplace=True) + qc.compose(get_three_qubit_bit_flip_encoding_decoding_circuit(), qubits=logical_qubit[6:9], inplace=True) qc.compose( - _get_three_qubit_phase_flip_decoding_circuit(), + get_three_qubit_phase_flip_decoding_circuit(), qubits=[logical_qubit[0], logical_qubit[3], logical_qubit[6]], inplace=True, ) @@ -279,17 +128,17 @@ def create_circuit(num_qubits: int) -> QuantumCircuit: Syndrome Extraction: - Bit-flip syndrome: For each block, 2 ancilla qubits measure the parity of - qubit pairs to detect which qubit (if any) experienced a bit flip. - Syndrome 01 β†’ qubit 0, syndrome 10 β†’ qubit 1, syndrome 11 β†’ qubit 2. + qubit pairs to detect which qubit (if any) experienced a bit flip. + Syndrome 01 β†’ qubit 0, syndrome 10 β†’ qubit 1, syndrome 11 β†’ qubit 2. - Phase-flip syndrome: 2 ancilla qubits detect phase differences between - the three blocks. Syndrome 01 β†’ block 1 (qubits 0-2), syndrome 10 β†’ block 2 - (qubits 3-5), syndrome 11 β†’ block 3 (qubits 6-8). + the three blocks. Syndrome 01 β†’ block 1 (qubits 0-2), syndrome 10 β†’ block 2 + (qubits 3-5), syndrome 11 β†’ block 3 (qubits 6-8). Error Correction: - Bit-flip correction: Based on the 6-bit syndrome measurement, X gates are - conditionally applied to correct bit flips on any of the 9 data qubits. + conditionally applied to correct bit flips on any of the 9 data qubits. - Phase-flip correction: Based on the 2-bit syndrome measurement, Z gates are - conditionally applied to the first qubit of the affected block. + conditionally applied to the first qubit of the affected block. Circuit Structure (per logical qubit): - 17 qubits: diff --git a/src/mqt/bench/components/__init__.py b/src/mqt/bench/components/__init__.py new file mode 100644 index 000000000..1b6eef3a4 --- /dev/null +++ b/src/mqt/bench/components/__init__.py @@ -0,0 +1,11 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Reusable circuit components for quantum error-correcting codes.""" + +from __future__ import annotations diff --git a/src/mqt/bench/components/shor_circuit_components.py b/src/mqt/bench/components/shor_circuit_components.py new file mode 100644 index 000000000..f5263137b --- /dev/null +++ b/src/mqt/bench/components/shor_circuit_components.py @@ -0,0 +1,177 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Shor's 9-qubit code circuit components.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from qiskit.circuit import AncillaRegister, QuantumCircuit, QuantumRegister + +if TYPE_CHECKING: + from qiskit.circuit import ClassicalRegister + + +def get_three_qubit_phase_flip_decoding_circuit() -> QuantumCircuit: + """Create 3-qubit phase-flip decoding circuit. + + Reverses the phase-flip encoding. + + Returns: + QuantumCircuit: 3-qubit decoding circuit (qubit 0 is the output qubit). + """ + out = QuantumCircuit(3) + out.h(0) + out.h(1) + out.h(2) + out.cx(0, 1) + out.cx(0, 2) + return out + + +def get_three_qubit_bit_flip_encoding_decoding_circuit() -> QuantumCircuit: + """Create 3-qubit bit-flip encoding/decoding circuit. + + Encodes |0> β†’ |000> and |1> β†’ |111>. Self-inverse, so used for both encoding and decoding. + + Returns: + QuantumCircuit: 3-qubit circuit (qubit 0 is the input/output qubit). + """ + out = QuantumCircuit(3) + out.cx(0, 1) + out.cx(0, 2) + return out + + +def get_three_qubit_phase_flip_encoding_circuit() -> QuantumCircuit: + """Create 3-qubit phase-flip encoding circuit. + + Encodes |0> β†’ |+++> and |1> β†’ |---> + + Returns: + QuantumCircuit: 3-qubit encoding circuit (qubit 0 is the input qubit). + """ + out = QuantumCircuit(3) + out.cx(0, 1) + out.cx(0, 2) + out.h(0) + out.h(1) + out.h(2) + return out + + +def get_three_qubit_bit_flip_syndrome_extraction_circuit() -> QuantumCircuit: + """Create circuit to extract bit-flip syndrome from a 3-qubit block. + + Uses 2 ancilla qubits to measure parity and identify which qubit (if any) flipped. + Syndrome mapping: 01 β†’ qubit 0, 10 β†’ qubit 1, 11 β†’ qubit 2, 00 β†’ no error. + + Returns: + QuantumCircuit: 5-qubit circuit (qubits 0-2 are data, qubits 3-4 are syndrome ancillas). + """ + out = QuantumCircuit(5) + out.cx(0, 3) + out.cx(1, 4) + out.cx(2, 3) + out.cx(2, 4) + return out + + +def get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit() -> QuantumCircuit: + """Create circuit to extract phase-flip syndrome across the three 3-qubit blocks. + + Detects which block (if any) experienced a phase flip using 2 ancilla qubits. + Syndrome mapping: 01 β†’ block 1 (qubits 0-2), 10 β†’ block 2 (qubits 3-5), + 11 β†’ block 3 (qubits 6-8), 00 β†’ no error. + + Returns: + QuantumCircuit: 11-qubit circuit (qubits 0-8 are data, qubits 9-10 are syndrome ancillas). + """ + logical_qubit, phase_flip_syndrome = QuantumRegister(9), AncillaRegister(2) + out = QuantumCircuit(logical_qubit, phase_flip_syndrome) + # The order on the CNOT gates below is reversed when compared to what one might expect + # with the control being the ancilla, and the target being one of the component qubits of the logical qubit + # This is because we put Hadamards at the starts and ends of the ancilla bits, in order to check the phase + # of the logical qubits as opposed to the amplitude. + # But this also effectively swaps the order of the control and target, so we swap them back to normal + out.h(phase_flip_syndrome[0]) + out.h(phase_flip_syndrome[1]) + # Syndrome 01 (block 1) + for i in range(3): + out.cx(phase_flip_syndrome[0], logical_qubit[i]) + # Syndrome 10 (block 2) + for i in range(3, 6): + out.cx(phase_flip_syndrome[1], logical_qubit[i]) + # Syndrome 11 (block 3) + for i in range(6, 9): + out.cx(phase_flip_syndrome[0], logical_qubit[i]) + out.cx(phase_flip_syndrome[1], logical_qubit[i]) + out.h(phase_flip_syndrome[0]) + out.h(phase_flip_syndrome[1]) + return out + + +def apply_nine_qubit_shors_code_bit_flip_correction( + qc: QuantumCircuit, + logical_qubit: QuantumRegister, + bit_flip_syndrome: AncillaRegister, + bit_flip_syndrome_measurement: ClassicalRegister, +) -> None: + """Apply bit-flip correction based on syndrome measurement. + + Measures the 6 syndrome qubits and conditionally applies X gates to correct + bit-flip errors on any of the 9 data qubits. + + Args: + qc: The quantum circuit to modify. + logical_qubit: Register containing the 9 data qubits. + bit_flip_syndrome: Ancilla register containing the 6 syndrome qubits. + bit_flip_syndrome_measurement: Classical register for syndrome measurement results. + """ + qc.measure(bit_flip_syndrome, bit_flip_syndrome_measurement) + # Note that Qiskit uses little-endian bit order + for index, syndrome in enumerate([ + 0b000001, + 0b000010, + 0b000011, + 0b000100, + 0b001000, + 0b001100, + 0b010000, + 0b100000, + 0b110000, + ]): + with qc.if_test((bit_flip_syndrome_measurement, syndrome)): + qc.x(logical_qubit[index]) + + +def apply_nine_qubit_shors_code_phase_flip_correction( + qc: QuantumCircuit, + logical_qubit: QuantumRegister, + phase_flip_syndrome: AncillaRegister, + phase_flip_syndrome_measurement: ClassicalRegister, +) -> None: + """Apply phase-flip correction based on syndrome measurement. + + Measures the 2 syndrome qubits and conditionally applies Z gates to correct + phase-flip errors on the first qubit of the affected block. + + Arguments: + qc: The quantum circuit to modify. + logical_qubit: Register containing the 9 data qubits. + phase_flip_syndrome: Ancilla register containing the 2 syndrome qubits. + phase_flip_syndrome_measurement: Classical register for syndrome measurement results. + """ + qc.measure(phase_flip_syndrome, phase_flip_syndrome_measurement) + with qc.if_test((phase_flip_syndrome_measurement, 0b01)): + qc.z(logical_qubit[0]) + with qc.if_test((phase_flip_syndrome_measurement, 0b10)): + qc.z(logical_qubit[3]) + with qc.if_test((phase_flip_syndrome_measurement, 0b11)): + qc.z(logical_qubit[6]) diff --git a/src/mqt/bench/components/steane_circuit_components.py b/src/mqt/bench/components/steane_circuit_components.py new file mode 100644 index 000000000..58578ebe4 --- /dev/null +++ b/src/mqt/bench/components/steane_circuit_components.py @@ -0,0 +1,138 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Steane's 7-qubit code circuit components.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from qiskit.circuit import AncillaRegister, QuantumCircuit, QuantumRegister + +if TYPE_CHECKING: + from qiskit.circuit import ClassicalRegister + + +def get_seven_qubit_steane_code_encoding_circuit() -> QuantumCircuit: + """Create the 7-qubit Steane code encoding circuit. + + Encodes qubit 0 into the 7-qubit Steane code logical state: + - |0> -> (|0000000> + |1010101> + |0110011> + |1100110> + |0001111> + |1011010> + |0111100> + |1101001>) + - |1> -> (|1111111> + |0101010> + |1001100> + |0011001> + |1110000> + |0100101> + |1000011> + |0010110>). + + Returns: + QuantumCircuit: 7-qubit encoding circuit. + """ + out = QuantumCircuit(7) + # H + out.h(4) + out.h(5) + out.h(6) + # CNOT from 0 + out.cx(0, 1) + out.cx(0, 2) + # CNOT from 6 + out.cx(6, 3) + out.cx(6, 1) + out.cx(6, 0) + # CNOT from 5 + out.cx(5, 3) + out.cx(5, 2) + out.cx(5, 0) + # CNOT from 4 + out.cx(4, 3) + out.cx(4, 2) + out.cx(4, 1) + return out + + +def get_seven_qubit_steane_code_decoding_circuit() -> QuantumCircuit: + """Create the 7-qubit Steane code decoding circuit. + + Reverses the encoding operation to extract the logical qubit back to qubit 0. + + Returns: + QuantumCircuit: 7-qubit decoding circuit (qubit 0 is the output qubit). + """ + return get_seven_qubit_steane_code_encoding_circuit().inverse() + + +def get_seven_qubit_steane_code_syndrome_extraction_circuit() -> QuantumCircuit: + """Create the syndrome extraction circuit for the 7-qubit Steane code. + + Extracts bit-flip and phase-flip syndromes using 6 ancilla qubits (3 for each type). + + Bit-flip syndrome extraction: + Syndrome bits measure the parity of specific qubit subsets corresponding to + the X-stabilizer generators. + + Phase-flip syndrome extraction: + Uses Hadamard gates to convert from Z to X basis, and control/target swapped + CNOTs to extract the phase-flip syndrome + + Syndrome mapping: The 3-bit syndrome value (1-7) directly identifies which + data qubit experienced an error. Syndrome 0 indicates no error. + + Returns: + QuantumCircuit: 13-qubit circuit (qubits 0-6 are data, 7-9 are bit-flip + syndrome ancillas, 10-12 are phase-flip syndrome ancillas). + """ + logical_qubit, bit_flip_syndrome, phase_flip_syndrome = QuantumRegister(7), AncillaRegister(3), AncillaRegister(3) + out = QuantumCircuit(logical_qubit, bit_flip_syndrome, phase_flip_syndrome) + # Bit-flip + for ctrl in (0, 2, 4, 6): + out.cx(logical_qubit[ctrl], bit_flip_syndrome[0]) + for ctrl in (1, 2, 5, 6): + out.cx(logical_qubit[ctrl], bit_flip_syndrome[1]) + for ctrl in (3, 4, 5, 6): + out.cx(logical_qubit[ctrl], bit_flip_syndrome[2]) + # Phase-flip + for i in range(3): + out.h(phase_flip_syndrome[i]) + for targ in (0, 2, 4, 6): + out.cx(phase_flip_syndrome[0], logical_qubit[targ]) + for targ in (1, 2, 5, 6): + out.cx(phase_flip_syndrome[1], logical_qubit[targ]) + for targ in (3, 4, 5, 6): + out.cx(phase_flip_syndrome[2], logical_qubit[targ]) + for i in range(3): + out.h(phase_flip_syndrome[i]) + return out + + +def apply_seven_qubit_steane_code_correction( + qc: QuantumCircuit, + logical_qubit: QuantumRegister, + bit_flip_syndrome: AncillaRegister, + phase_flip_syndrome: AncillaRegister, + bit_flip_syndrome_measurement: ClassicalRegister, + phase_flip_syndrome_measurement: ClassicalRegister, +) -> None: + """Apply error correction based on syndrome measurements. + + Measures the 6 syndrome qubits and conditionally applies X/Z gates to correct + single-qubit errors on any of the 7 data qubits. + + Args: + qc: The quantum circuit to modify. + logical_qubit: Register containing the 7 data qubits. + bit_flip_syndrome: Register containing the 3 bit-flip syndrome qubits. + phase_flip_syndrome: Register containing the 3 phase-flip syndrome qubits. + bit_flip_syndrome_measurement: Classical register for bit-flip syndrome results. + phase_flip_syndrome_measurement: Classical register for phase-flip syndrome results. + """ + qc.measure(bit_flip_syndrome, bit_flip_syndrome_measurement) + qc.measure(phase_flip_syndrome, phase_flip_syndrome_measurement) + # Bit-flip correction: syndrome value directly indicates which qubit to correct + for i in range(7): + with qc.if_test((bit_flip_syndrome_measurement, i + 1)): + qc.x(logical_qubit[i]) + # Phase-flip correction: syndrome value directly indicates which qubit to correct + for i in range(7): + with qc.if_test((phase_flip_syndrome_measurement, i + 1)): + qc.z(logical_qubit[i]) diff --git a/src/mqt/bench/error_correction/__init__.py b/src/mqt/bench/error_correction/__init__.py new file mode 100644 index 000000000..1355b43fc --- /dev/null +++ b/src/mqt/bench/error_correction/__init__.py @@ -0,0 +1,51 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Initialization of the error correction module.""" + +# ruff: file-ignore[non-empty-init-module] + +from __future__ import annotations + +from .ec_transpiler import ECTranspiler, LogicalQubit +from .shor_transpiler import ShorTranspiler +from .steane_transpiler import SteaneTranspiler + +__all__ = [ + "ECTranspiler", + "LogicalQubit", + "ShorTranspiler", + "SteaneTranspiler", + "get_available_encoding_names", + "get_transpiler", +] + + +_TRANSPILERS: dict[str, type[ECTranspiler]] = {cls.CODE_NAME: cls for cls in (ShorTranspiler, SteaneTranspiler)} + + +def get_available_encoding_names() -> list[str]: + """Return the names of all supported error-correcting codes, i.e. the valid ``encoding`` values.""" + return sorted(_TRANSPILERS) + + +def get_transpiler(encoding: str) -> type[ECTranspiler]: + """Return the transpiler class for the given error-correcting code. + + Arguments: + encoding: Name of the error-correcting code (see :func:`get_available_encoding_names`). + + Raises: + ValueError: If ``encoding`` is not a supported error-correcting code. + """ + if encoding not in _TRANSPILERS: + msg = ( + f"'{encoding}' is not a supported error-correcting code. Available codes: {get_available_encoding_names()}" + ) + raise ValueError(msg) + return _TRANSPILERS[encoding] diff --git a/src/mqt/bench/error_correction/ec_transpiler.py b/src/mqt/bench/error_correction/ec_transpiler.py new file mode 100644 index 000000000..39f299d94 --- /dev/null +++ b/src/mqt/bench/error_correction/ec_transpiler.py @@ -0,0 +1,277 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Shared base transpiler for converting standard circuits into fault-tolerant circuits. + +Concrete error-correcting codes (e.g. Shor's 9-qubit code, the 7-qubit Steane code) subclass +:class:`ECTranspiler` and only need to declare their gate set and provide the code-specific +encoding/decoding/syndrome circuits plus handlers for gates whose logical realization is not a +plain transversal application of a single physical gate. Any gate that is part of a subclass's +target gate set but has no dedicated handler is automatically realized as an opaque, ideal logical +gadget (see :meth:`ECTranspiler._handle_unregistered_gate`). +""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from dataclasses import dataclass +from typing import ClassVar + +from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister, transpile +from qiskit.circuit import AncillaRegister, Gate, Instruction + + +@dataclass +class LogicalQubit: + """Encapsulates the physical registers representing a single encoded logical qubit.""" + + data: QuantumRegister + bit_flip_syndrome: AncillaRegister + phase_flip_syndrome: AncillaRegister + bit_flip_measure: ClassicalRegister + phase_flip_measure: ClassicalRegister + + def get_all_registers(self) -> list[QuantumRegister | ClassicalRegister]: + """Return all active registers for this logical qubit.""" + regs: list[QuantumRegister | ClassicalRegister] = [self.data] + if self.bit_flip_syndrome is not None: + regs.extend( + reg + for reg in [ + self.bit_flip_syndrome, + self.phase_flip_syndrome, + self.bit_flip_measure, + self.phase_flip_measure, + ] + if reg is not None + ) + return regs + + +class ECTranspiler(ABC): + """A high-level transpiler that encodes a QuantumCircuit using a quantum error-correcting code. + + Subclasses declare the code's parameters as class attributes and implement the abstract + methods below; :meth:`transpile`, :meth:`encode_qubits` and :meth:`replace_gates` are shared + by every code. + """ + + #: Short name of the code, used e.g. to name the encoded circuit and opaque logical gadgets. + CODE_NAME: ClassVar[str] + #: Number of physical qubits used to encode a single logical qubit. + BLOCK_SIZE: ClassVar[int] + #: Number of ancilla qubits (and classical bits) used per bit-flip syndrome extraction. + BIT_FLIP_SYNDROME_SIZE: ClassVar[int] + #: Number of ancilla qubits (and classical bits) used per phase-flip syndrome extraction. + PHASE_FLIP_SYNDROME_SIZE: ClassVar[int] + #: Basis gate set the original circuit is transpiled to before logical replacement. + TARGET_GATE_SET: ClassVar[list[str]] + #: Gates that are realized by applying a single physical gate transversally, i.e. once per + #: physical qubit position across all involved logical qubits' data registers. Gates that need + #: a different physical realization (e.g. because control/target are inverted, or because the + #: logical operator only acts on a subset of physical qubits) should instead be implemented as + #: a dedicated ``_logical_`` method on the subclass. + TRANSVERSAL_GATES: ClassVar[dict[str, Gate]] = {} + + def __init__(self, original_circuit: QuantumCircuit) -> None: + """Initialize the transpiler with the original QuantumCircuit. + + Args: + original_circuit: Original circuit to transpile using the error-correcting code. + """ + self.original_qc = original_circuit + self.num_logical_qubits = original_circuit.num_qubits + self.logical_qubits: list[LogicalQubit] = [] + self.transpiled_qc = QuantumCircuit() + self.encoded_qubits = [False for _ in range(self.num_logical_qubits)] + + def transpile(self) -> QuantumCircuit: + """Transpile the original circuit to a fault-tolerant circuit using the error-correcting code. + + High-level Qiskit instructions such as ``QFTGate`` are first decomposed into the code's + supported basis gates. This decomposition uses ``approximation_degree=0.95``, + so encoded circuits are approximate rather than exact. + + Returns: + The transpiled fault-tolerant circuit. + """ + for instruction in self.original_qc.data: + if instruction.is_control_flow(): + msg = f"Error-correction transpilation currently does not support control-flow operations such as {instruction.operation.name}." + raise ValueError(msg) + + self.original_qc = transpile( + self.original_qc, + basis_gates=self.TARGET_GATE_SET, + optimization_level=0, + approximation_degree=0.95, + seed_transpiler=10, + ) + + self.encode_qubits() + self.replace_gates() + return self.transpiled_qc + + def encode_qubits(self) -> None: + """Replace each logical qubit with a physical register and apply the code's encoding.""" + all_registers: list[QuantumRegister | ClassicalRegister] = [] + for i in range(self.num_logical_qubits): + data_reg = QuantumRegister(self.BLOCK_SIZE, f"q{i}") + logical_qubit = LogicalQubit( + data=data_reg, + bit_flip_syndrome=AncillaRegister(self.BIT_FLIP_SYNDROME_SIZE, f"bs{i}"), + phase_flip_syndrome=AncillaRegister(self.PHASE_FLIP_SYNDROME_SIZE, f"ps{i}"), + bit_flip_measure=ClassicalRegister(self.BIT_FLIP_SYNDROME_SIZE, f"bsm{i}"), + phase_flip_measure=ClassicalRegister(self.PHASE_FLIP_SYNDROME_SIZE, f"psm{i}"), + ) + + self.logical_qubits.append(logical_qubit) + all_registers.extend(logical_qubit.get_all_registers()) + + self.encoded_qubits[i] = True + + self.transpiled_qc = QuantumCircuit(*all_registers) + self.transpiled_qc.name = f"{self.original_qc.name}_{self.CODE_NAME}_encoded" + + for logical_qubit in self.logical_qubits: + self._apply_encoding(self.transpiled_qc, logical_qubit.data) + + def replace_gates(self) -> None: + """Scan the original circuit and dispatch each instruction to its logical equivalent. + + For every instruction, the original circuit's qubits/clbits are resolved to logical + indices and handed off to :meth:`_apply_gate`, which owns the actual dispatch logic. + + Control-Flow instructions conditions are unsupported. + """ + for instruction in self.original_qc.data: + gate_name = instruction.operation.name + logical_qubit_indices = [self.original_qc.qubits.index(q) for q in instruction.qubits] + logical_clbit_indices = [self.original_qc.clbits.index(c) for c in instruction.clbits] + + decoded_qubits = [q for q in logical_qubit_indices if self.encoded_qubits[q] is False] + if decoded_qubits: + msg = f"Instruction {gate_name} accesses qubits {decoded_qubits} after decoding" + raise ValueError(msg) + + self._apply_gate(gate_name, logical_qubit_indices, logical_clbit_indices) + + def _apply_gate(self, gate_name: str, logical_qubit_indices: list, logical_clbit_indices: list) -> None: + """Apply a single named gate to the given logical qubit/classical-bit indices. + + Dispatch order: + 1. ``barrier``/``measure`` are always handled generically. + 2. A dedicated ``_logical_`` method on the subclass, if present, is used. This + covers gates whose physical realization is not a plain transversal application of a + single gate (e.g. a code-specific CX). + 3. Otherwise, if the gate is listed in :attr:`TRANSVERSAL_GATES`, it is applied + transversally (see :meth:`_apply_transversal_gate`). + 4. Otherwise, the gate is realized as an opaque, ideal logical gadget (see + :meth:`_handle_unregistered_gate`). + """ + if gate_name == "barrier": + self._handle_barrier(logical_qubit_indices) + return + if gate_name == "measure": + self._handle_measure(logical_qubit_indices[0], logical_clbit_indices[0]) + return + + handler_name = f"_logical_{gate_name}" + if hasattr(self, handler_name): + getattr(self, handler_name)(*logical_qubit_indices) + elif gate_name in self.TRANSVERSAL_GATES: + self._apply_transversal_gate(self.TRANSVERSAL_GATES[gate_name], logical_qubit_indices) + else: + self._handle_unregistered_gate(gate_name, logical_qubit_indices) + + def _handle_barrier(self, logical_qubit_indices: list[int]) -> None: + """Apply a logical barrier across the specified physical qubits.""" + involved_physical_data_registers = [self.logical_qubits[idx].data for idx in logical_qubit_indices] + flattened_physical_qubits = [ + physical_qubit + for physical_data_register in involved_physical_data_registers + for physical_qubit in physical_data_register + ] + if flattened_physical_qubits: + self.transpiled_qc.barrier(flattened_physical_qubits) + else: + self.transpiled_qc.barrier() + + def _handle_measure(self, logical_qubit_index: int, logical_classical_bit_index: int) -> None: + """Decode a logical qubit and measure it into a fresh single-bit classical register.""" + self.encoded_qubits[logical_qubit_index] = False + physical_data_register = self.logical_qubits[logical_qubit_index].data + self._apply_decoding(self.transpiled_qc, physical_data_register) + + measurement_register_name = f"meas_{logical_qubit_index}_{logical_classical_bit_index}" + physical_measurement_register = ClassicalRegister(1, measurement_register_name) + self.transpiled_qc.add_register(physical_measurement_register) + + self.transpiled_qc.measure(physical_data_register[0], physical_measurement_register[0]) + + def _apply_transversal_gate(self, physical_gate: Gate, logical_qubit_indices: list[int]) -> None: + """Apply ``physical_gate`` once per physical qubit position across the involved blocks. + + This is the shared building block behind :attr:`TRANSVERSAL_GATES` and can also be reused + by subclasses to implement gates that decompose into a sequence of transversal gates (e.g. + SWAP or DCX built from transversal CX applications). + """ + physical_data_registers = [self.logical_qubits[idx].data for idx in logical_qubit_indices] + for physical_qubit_index in range(self.BLOCK_SIZE): + physical_qubits = [register[physical_qubit_index] for register in physical_data_registers] + self.transpiled_qc.append(physical_gate, physical_qubits) + + for logical_qubit_index in dict.fromkeys(logical_qubit_indices): + self.insert_syndromes(logical_qubit_index) + + def _handle_unregistered_gate(self, gate_name: str, logical_qubit_indices: list[int]) -> None: + """Realize a gate with no dedicated handler as an opaque, ideal logical gadget. + + Many logical gates (e.g. T, or a code's non-transversal Hadamard) require magic-state + injection or another resource-intensive fault-tolerant construction to implement exactly. + Since this benchmark suite is only concerned with the resulting circuit *structure*, such + gates are instead represented by a single opaque, ideal placeholder instruction spanning + the involved logical qubits' data registers, named + ``f"{self.CODE_NAME}_ideal_logical_{gate_name}"``. + """ + physical_data_registers = [self.logical_qubits[idx].data for idx in logical_qubit_indices] + physical_qubits = [qubit for register in physical_data_registers for qubit in register] + + gadget = Instruction( + name=f"{self.CODE_NAME}_ideal_logical_{gate_name}", + num_qubits=len(physical_qubits), + num_clbits=0, + params=[], + ) + self.transpiled_qc.append(gadget, physical_qubits) + + for logical_qubit_index in dict.fromkeys(logical_qubit_indices): + self.insert_syndromes(logical_qubit_index) + + def insert_syndromes(self, logical_qubit_index: int) -> None: + """Run the code's syndrome extraction and correction cycle for one logical qubit's block. + + This method is called automatically after every logical gate. + + Args: + logical_qubit_index: Index of the logical qubit whose data block should undergo the + correction cycle. + """ + self._run_syndrome_cycle(self.logical_qubits[logical_qubit_index]) + + @abstractmethod + def _apply_encoding(self, qc: QuantumCircuit, physical_data_register: QuantumRegister) -> None: + """Apply the code's encoding to a physical data register.""" + + @abstractmethod + def _apply_decoding(self, qc: QuantumCircuit, physical_data_register: QuantumRegister) -> None: + """Apply the code's decoding to a physical data register.""" + + @abstractmethod + def _run_syndrome_cycle(self, qubit: LogicalQubit) -> None: + """Reset ancillas, extract syndromes, and apply corrections for one logical qubit.""" diff --git a/src/mqt/bench/error_correction/shor_transpiler.py b/src/mqt/bench/error_correction/shor_transpiler.py new file mode 100644 index 000000000..7aed2e526 --- /dev/null +++ b/src/mqt/bench/error_correction/shor_transpiler.py @@ -0,0 +1,197 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Shor Transpiler for converting standard circuits into fault-tolerant circuits using the 9-qubit Shor code.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, ClassVar + +from qiskit.circuit.library import IGate + +from mqt.bench.components.shor_circuit_components import ( + apply_nine_qubit_shors_code_bit_flip_correction, + apply_nine_qubit_shors_code_phase_flip_correction, + get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit, + get_three_qubit_bit_flip_encoding_decoding_circuit, + get_three_qubit_bit_flip_syndrome_extraction_circuit, + get_three_qubit_phase_flip_encoding_circuit, +) + +from .ec_transpiler import ECTranspiler, LogicalQubit + +if TYPE_CHECKING: + from qiskit import QuantumCircuit, QuantumRegister + from qiskit.circuit import Gate + + +class ShorTranspiler(ECTranspiler): + """A high-level transpiler that encodes a QuantumCircuit using Shor's 9-qubit error correction code. + + Only gates whose logical realization is not a plain transversal application of a single + physical gate need a dedicated handler here (``id`` is the only gate transversal in the + strict sense used by :class:`ECTranspiler`). Every other unhandled gate in + :attr:`TARGET_GATE_SET` -- notably ``t``, ``tdg``, and ``h`` (whose textbook realization via + physical Hadamards and block-transposing SWAPs is not fault-tolerant for the plain 9-qubit + Shor code) -- is automatically realized as an opaque, ideal logical gadget. + """ + + # Constants for the Shor 9-qubit code structure + SHOR_BLOCK_SIZE = 3 + SHOR_NUM_BLOCKS = 3 + SHOR_PHASE_FLIP_TARGETS: ClassVar[list[int]] = [0, 3, 6] + + CODE_NAME = "shor" + BLOCK_SIZE = SHOR_BLOCK_SIZE * SHOR_NUM_BLOCKS + BIT_FLIP_SYNDROME_SIZE = 6 + PHASE_FLIP_SYNDROME_SIZE = 2 + TARGET_GATE_SET: ClassVar[list[str]] = ["id", "h", "x", "y", "z", "cx", "t", "tdg"] + TRANSVERSAL_GATES: ClassVar[dict[str, Gate]] = {"id": IGate()} + + def _apply_encoding(self, qc: QuantumCircuit, physical_data_register: QuantumRegister) -> None: + """Apply Shor 9-qubit encoding to a physical data register.""" + # Phase flip encoding on the first qubit of each block + qc.compose( + get_three_qubit_phase_flip_encoding_circuit(), + qubits=[physical_data_register[i] for i in self.SHOR_PHASE_FLIP_TARGETS], + inplace=True, + ) + + # Bit flip encoding on each block + for i in range(self.SHOR_NUM_BLOCKS): + qc.compose( + get_three_qubit_bit_flip_encoding_decoding_circuit(), + qubits=physical_data_register[i * self.SHOR_BLOCK_SIZE : (i + 1) * self.SHOR_BLOCK_SIZE], + inplace=True, + ) + + def _apply_decoding(self, qc: QuantumCircuit, physical_data_register: QuantumRegister) -> None: + """Apply Shor 9-qubit decoding to a physical data register.""" + for i in range(self.SHOR_NUM_BLOCKS): + qc.compose( + get_three_qubit_bit_flip_encoding_decoding_circuit().inverse(), + qubits=physical_data_register[i * self.SHOR_BLOCK_SIZE : (i + 1) * self.SHOR_BLOCK_SIZE], + inplace=True, + ) + qc.compose( + get_three_qubit_phase_flip_encoding_circuit().inverse(), + qubits=[physical_data_register[i] for i in self.SHOR_PHASE_FLIP_TARGETS], + inplace=True, + ) + + def _logical_x(self, logical_qubit_index: int) -> None: + """Apply Transversal logical X. + + In Shor's code, a logical X acts like a global physical Z across the three + blocks. Since Z on one qubit of a block flips the entire block's phase, + applying one Z per block (Z_0 Z_3 Z_6) transversally achieves logical X. + """ + physical_data_register = self.logical_qubits[logical_qubit_index].data + for q in (physical_data_register[i] for i in self.SHOR_PHASE_FLIP_TARGETS): + self.transpiled_qc.z(q) + self.insert_syndromes(logical_qubit_index) + + def _logical_y(self, logical_qubit_index: int) -> None: + """Apply transversal logical Y. + + Since Y = iXZ (up to global phase), logical Y is realized by applying the + physical operations for logical Z and logical X. Logical Z acts as X on the + three qubits of the first block (X_0 X_1 X_2), and logical X acts as Z on the + first qubit of each block (Z_0 Z_3 Z_6). The global phase i is unobservable. + """ + physical_data_register = self.logical_qubits[logical_qubit_index].data + for q in (physical_data_register[0], physical_data_register[1], physical_data_register[2]): + self.transpiled_qc.x(q) + for q in (physical_data_register[i] for i in self.SHOR_PHASE_FLIP_TARGETS): + self.transpiled_qc.z(q) + self.insert_syndromes(logical_qubit_index) + + def _logical_z(self, logical_qubit_index: int) -> None: + """Apply Transversal logical Z. + + Applying X to the three qubits of a single block (e.g. X_0 X_1 X_2) maps + |000> to |111>, effectively giving diag(+1,-1) on the logical subspace. + """ + physical_data_register = self.logical_qubits[logical_qubit_index].data + for q in (physical_data_register[0], physical_data_register[1], physical_data_register[2]): + self.transpiled_qc.x(q) + self.insert_syndromes(logical_qubit_index) + + def _logical_cx(self, control_logical_qubit_index: int, target_logical_qubit_index: int) -> None: + """Apply transversal logical CX. + + Because the Shor logical operators X_L and Z_L have interchanged physical basis mapping + compared to typical codes, the physical CX role is inverted: control and target are + swapped at the physical level to construct a logical CX. + """ + control_physical_data_register = self.logical_qubits[control_logical_qubit_index].data + target_physical_data_register = self.logical_qubits[target_logical_qubit_index].data + for physical_qubit_index in range(self.BLOCK_SIZE): + # Physical control/target are swapped relative to logical + control_qubit = control_physical_data_register[physical_qubit_index] + target_qubit = target_physical_data_register[physical_qubit_index] + self.transpiled_qc.cx(target_qubit, control_qubit) + + self.insert_syndromes(control_logical_qubit_index) + self.insert_syndromes(target_logical_qubit_index) + + def _run_syndrome_cycle(self, qubit: LogicalQubit) -> None: + """Run the Shor code's bit-flip and phase-flip syndrome extraction and correction cycle. + + Performs three stages of error correction on the Shor block belonging to ``qubit``: + + 1. **Bit-flip syndrome extraction**: Each of the three 3-qubit blocks is checked independently. + Per block, two ancillas record the stabilizer parities (Z_i Z_j checks), + localizing an X error to one of the block's three qubits. + 2. **Phase-flip syndrome extraction**: The parities between the three blocks + (X-type stabilizers spanning six qubits each) are extracted, + localizing a single Z error to one of the three blocks. + 3. **Correction**: The ancillas are measured into the corresponding classical syndrome registers, + and X and Z corrections are applied to the identified data qubit. + + Ancilla registers are reset to |0> at the start of each cycle. + """ + self._extract_bit_flip_syndromes(qubit) + self._extract_phase_flip_syndromes(qubit) + self._apply_error_corrections(qubit) + + def _extract_bit_flip_syndromes(self, qubit: LogicalQubit) -> None: + """Extract bit-flip syndromes for the three blocks.""" + self.transpiled_qc.reset(qubit.bit_flip_syndrome) + for i in range(self.SHOR_NUM_BLOCKS): + self.transpiled_qc.compose( + get_three_qubit_bit_flip_syndrome_extraction_circuit(), + qubits=qubit.data[i * self.SHOR_BLOCK_SIZE : (i + 1) * self.SHOR_BLOCK_SIZE] + + qubit.bit_flip_syndrome[i * 2 : (i + 1) * 2], + inplace=True, + ) + + def _extract_phase_flip_syndromes(self, qubit: LogicalQubit) -> None: + """Extract phase-flip syndromes across the blocks.""" + self.transpiled_qc.reset(qubit.phase_flip_syndrome) + self.transpiled_qc.compose( + get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit(), + qubits=qubit.data[:] + qubit.phase_flip_syndrome[:], + inplace=True, + ) + + def _apply_error_corrections(self, qubit: LogicalQubit) -> None: + """Apply bit-flip and phase-flip error corrections based on syndromes.""" + apply_nine_qubit_shors_code_bit_flip_correction( + self.transpiled_qc, + qubit.data, + qubit.bit_flip_syndrome, + qubit.bit_flip_measure, + ) + + apply_nine_qubit_shors_code_phase_flip_correction( + self.transpiled_qc, + qubit.data, + qubit.phase_flip_syndrome, + qubit.phase_flip_measure, + ) diff --git a/src/mqt/bench/error_correction/steane_transpiler.py b/src/mqt/bench/error_correction/steane_transpiler.py new file mode 100644 index 000000000..b66d09f7c --- /dev/null +++ b/src/mqt/bench/error_correction/steane_transpiler.py @@ -0,0 +1,121 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Steane Transpiler for converting standard circuits into fault-tolerant circuits using the 7-qubit Steane code.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, ClassVar + +from qiskit.circuit.library import ( + CXGate, + CZGate, + HGate, + IGate, + SdgGate, + SGate, + XGate, + YGate, + ZGate, +) + +from mqt.bench.components.steane_circuit_components import ( + apply_seven_qubit_steane_code_correction, + get_seven_qubit_steane_code_decoding_circuit, + get_seven_qubit_steane_code_encoding_circuit, + get_seven_qubit_steane_code_syndrome_extraction_circuit, +) + +from .ec_transpiler import ECTranspiler, LogicalQubit + +if TYPE_CHECKING: + from qiskit import QuantumCircuit, QuantumRegister + from qiskit.circuit import Gate + + +class SteaneTranspiler(ECTranspiler): + """A high-level transpiler that encodes a QuantumCircuit using Steane's 7-qubit error correction code. + + Every gate in :attr:`TARGET_GATE_SET` except SWAP, DCX and CY is transversal in the strict + sense used by :class:`ECTranspiler`, i.e. realized by applying a single physical gate once per + physical qubit position. T and Tdg have no dedicated handler and are therefore automatically realized as opaque, + ideal logical gadgets by the base class. + """ + + CODE_NAME = "steane" + BLOCK_SIZE = 7 + BIT_FLIP_SYNDROME_SIZE = 3 + PHASE_FLIP_SYNDROME_SIZE = 3 + TARGET_GATE_SET: ClassVar[list[str]] = [ + "id", + "x", + "y", + "z", + "h", + "s", + "sdg", + "cx", + "cz", + "t", + "tdg", + ] + # Our Steane convention: logical S = physical Sdg on every qubit, logical Sdg = physical S on every qubit. + TRANSVERSAL_GATES: ClassVar[dict[str, Gate]] = { + "id": IGate(), + "x": XGate(), + "y": YGate(), + "z": ZGate(), + "h": HGate(), + "s": SdgGate(), + "sdg": SGate(), + "cx": CXGate(), + "cz": CZGate(), + } + + def _apply_encoding(self, qc: QuantumCircuit, physical_data_register: QuantumRegister) -> None: + """Apply Steane 7-qubit encoding to a physical data register.""" + qc.compose( + get_seven_qubit_steane_code_encoding_circuit(), + qubits=physical_data_register[:], + inplace=True, + ) + + def _apply_decoding(self, qc: QuantumCircuit, physical_data_register: QuantumRegister) -> None: + """Apply Steane 7-qubit decoding to a physical data register.""" + qc.compose( + get_seven_qubit_steane_code_decoding_circuit(), + qubits=physical_data_register[:], + inplace=True, + ) + + def _run_syndrome_cycle(self, qubit: LogicalQubit) -> None: + """Run the Steane code's syndrome extraction and correction cycle. + + 1. **Ancilla preparation**: The syndrome ancilla registers are reset to |0>. + 2. **Syndrome extraction**: The bit-flip ancillas measure the Z-type stabilizers + and the phase-flip ancillas measure the X-type stabilizers, mapping each error onto a 3-bit syndrome. + 3. **Correction**: The ancillas are measured into the corresponding classical syndrome registers, + and X and Z corrections are applied to the identified data qubit. + """ + self.transpiled_qc.reset(qubit.bit_flip_syndrome) + self.transpiled_qc.reset(qubit.phase_flip_syndrome) + + self.transpiled_qc.compose( + get_seven_qubit_steane_code_syndrome_extraction_circuit(), + qubits=qubit.data[:] + qubit.bit_flip_syndrome[:] + qubit.phase_flip_syndrome[:], + inplace=True, + ) + + apply_seven_qubit_steane_code_correction( + self.transpiled_qc, + qubit.data, + qubit.bit_flip_syndrome, + qubit.phase_flip_syndrome, + qubit.bit_flip_measure, + qubit.phase_flip_measure, + ) diff --git a/tests/test_bench.py b/tests/test_bench.py index 657456929..b95990151 100644 --- a/tests/test_bench.py +++ b/tests/test_bench.py @@ -15,14 +15,15 @@ import functools import io import re +import warnings from enum import StrEnum from importlib import metadata from pathlib import Path from typing import TYPE_CHECKING, NoReturn, cast import pytest -from qiskit import QuantumCircuit, qpy -from qiskit.circuit import ForLoopOp, IfElseOp, Parameter +from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister, qpy +from qiskit.circuit import Barrier, ForLoopOp, IfElseOp, Parameter from qiskit.circuit.library import CXGate, HGate, RXGate, RZGate, XGate from qiskit.compiler import transpile from qiskit.transpiler import ( @@ -33,6 +34,11 @@ ) from qiskit.transpiler.passes import GatesInBasis, RemoveBarriers +from mqt.bench import benchmark_generation +from mqt.bench.error_correction import get_available_encoding_names, get_transpiler +from mqt.bench.error_correction.shor_transpiler import ShorTranspiler +from mqt.bench.error_correction.steane_transpiler import SteaneTranspiler + if TYPE_CHECKING: # pragma: no cover from collections import OrderedDict from collections.abc import Callable @@ -422,6 +428,266 @@ def test_seven_qubit_steane_code_circuit_structure(num_qubits: int) -> None: ) +SHOR_BV = { + "cx": 265, + "if_else": 108, + "measure": 74, + "reset": 72, + "h": 51, + "shor_ideal_logical_h": 6, + "z": 3, +} +SHOR_GHZ = { + "cx": 186, + "if_else": 60, + "h": 38, + "measure": 43, + "reset": 40, + "shor_ideal_logical_h": 1, + "barrier": 1, +} +SHOR_GRAPHSTATE = { + "cx": 435, + "if_else": 180, + "h": 78, + "measure": 123, + "reset": 120, + "shor_ideal_logical_h": 9, + "barrier": 1, +} +SHOR_QFT = { + "cx": 849, + "if_else": 360, + "measure": 243, + "reset": 240, + "h": 138, + "shor_ideal_logical_t": 6, + "shor_ideal_logical_h": 3, + "shor_ideal_logical_tdg": 3, + "barrier": 1, +} +SHOR_Z = {"cx": 32, "if_else": 12, "measure": 8, "reset": 8, "h": 7, "x": 3} +SHOR_Y = { + "cx": 32, + "if_else": 12, + "h": 7, + "measure": 8, + "reset": 8, + "x": 3, + "z": 3, +} +STEANE_BV = { + "cx": 223, + "if_else": 98, + "h": 85, + "measure": 44, + "reset": 42, + "x": 7, + "cz": 7, +} +STEANE_GHZ = { + "cx": 200, + "if_else": 70, + "h": 55, + "measure": 33, + "reset": 30, + "barrier": 1, +} +STEANE_GRAPHSTATE = { + "cx": 282, + "if_else": 126, + "h": 93, + "measure": 57, + "reset": 54, + "cz": 21, + "barrier": 1, +} +STEANE_QFT = { + "cx": 849, + "if_else": 420, + "h": 219, + "measure": 183, + "reset": 180, + "steane_ideal_logical_t": 6, + "steane_ideal_logical_tdg": 3, + "barrier": 1, +} +STEANE_Z = {"cx": 35, "if_else": 14, "h": 9, "measure": 6, "reset": 6, "z": 7} +STEANE_Y = {"cx": 35, "if_else": 14, "h": 9, "measure": 6, "reset": 6, "y": 7} + + +@pytest.mark.parametrize( + ("logical_qubits", "code", "alg", "expected_gates"), + [ + (3, "shor", "ghz", SHOR_GHZ), + (3, "shor", "bv", SHOR_BV), + (3, "shor", "graphstate", SHOR_GRAPHSTATE), + (3, "shor", "qft", SHOR_QFT), + (3, "steane", "ghz", STEANE_GHZ), + (3, "steane", "bv", STEANE_BV), + (3, "steane", "graphstate", STEANE_GRAPHSTATE), + (3, "steane", "qft", STEANE_QFT), + (1, "shor", "Z", SHOR_Z), + (1, "shor", "Y", SHOR_Y), + (1, "steane", "Z", STEANE_Z), + (1, "steane", "Y", STEANE_Y), + ], +) +def test_error_correction_transpiler_circuit_structure( + logical_qubits: int, code: str, alg: str, expected_gates: dict +) -> None: + """Verify the physical circuit structure produced by the error-correction encoder. + + Checks that the encoded circuit has the correct number of physical qubits, + classical bits, and register sizes for the given code and algorithm. Exact + gate counts are checked for all algorithms except QFT, whose decomposition + can differ between supported Qiskit versions. + + The expected qubit and classical-bit counts are code-dependent: + + * **Steane code**: 13 physical qubits per logical qubit (7 data + 3 bit-flip + ancilla + 3 phase-flip ancilla) and 6 classical bits per logical qubit (3 + bit-flip syndrome + 3 phase-flip syndrome), plus one bit per original clbit. + * **Shor code**: 17 physical qubits per logical qubit (9 data + 6 Z-stabiliser + ancilla + 2 X-stabiliser ancilla) and 8 classical bits per logical qubit (6 + bit-flip syndrome + 2 phase-flip syndrome), plus one bit per original clbit. + + QFT circuits are excluded from the qubit-count checks because their ancilla + qubit count scales with the number of T gates rather than the logical qubit count. + + Args: + logical_qubits: Number of logical qubits in the benchmark circuit. + code: The error-correction code to use; either ``"steane"`` or ``"shor"``. + alg: Name of the benchmark algorithm (e.g. ``"ghz"``, ``"bv"``, + ``"graphstate"``, ``"qft"``). + expected_gates: Expected occurrences of gates in the benchmark circuit (based on qc.count_ops()). + """ + test_id = f"{logical_qubits} qubit {alg} on {code}" + + log_qc = QuantumCircuit(1) + if alg == "Z": + log_qc.z(0) + elif alg == "Y": + log_qc.y(0) + else: + log_qc = benchmark_generation.get_benchmark( + benchmark=alg, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=logical_qubits, encoding="" + ) + + # add error correction to the logical circuit + transpiler = get_transpiler(code)(log_qc.copy()) + qc = transpiler.transpile() + qc = transpiler.transpiled_qc + + qubit_code_factor = -1 + classical_code_factor = -1 + expected_qreg_sizes = [] + expected_creg_sizes = [] + + if code == "steane": + # Each logical qubit is split in 7 physical qubits + # Additionally, 6 ancillary registers are added + qubit_code_factor = 13 + classical_code_factor = 6 + + # Check quantum register sizes: 7n (data) + 3n (bit-flip syndrome) + 3n (phase-flip syndrome) + expected_qreg_sizes = sorted([7] * logical_qubits + [3] * logical_qubits + [3] * logical_qubits) + # Check classical register sizes: 3n (bit-flip) + 3n (phase-flip) + 1 for each original clbit + expected_creg_sizes = sorted([3] * logical_qubits + [3] * logical_qubits + [1] * log_qc.num_clbits) + elif code == "shor": + # Each logical qubit is split in 9 physical qubits + # Additionally, 8 ancilla qubits are added as stabilisers (6Z + 2X) + # => 1 logical qubit = 17 physical qubits + qubit_code_factor = 17 + # Each ancilla requires 1 clbit for syndrome extraction => 6*2 = 8 + classical_code_factor = 8 + + # Check quantum register sizes: 9n (data) + 6n (bit-flip syndrome) + 2n (phase-flip syndrome) + expected_qreg_sizes = sorted([9] * logical_qubits + [6] * logical_qubits + [2] * logical_qubits) + # Check classical register sizes: 6n (bit-flip) + 2n (phase-flip) + 1 for each original clbit + expected_creg_sizes = sorted([6] * logical_qubits + [2] * logical_qubits + [1] * log_qc.num_clbits) + + # QFT creates qubits scaling with the number of t-gates -> non-trivial scaling not covered by these simple tests + if alg != "qft": + expected_qubits = qubit_code_factor * log_qc.num_qubits + found_qubits = qc.num_qubits + assert found_qubits == expected_qubits, f"Expected {expected_qubits} qubits, found {found_qubits} for {test_id}" + + expected_clbits = classical_code_factor * log_qc.num_qubits + log_qc.num_clbits + found_clbits = qc.num_clbits + assert found_clbits == expected_clbits, ( + f"Expected {expected_clbits} classical bits, found {found_clbits} for {test_id}" + ) + + qreg_sizes = sorted(qreg.size for qreg in qc.qregs) + assert qreg_sizes == expected_qreg_sizes, ( + f"Expected qreg sizes {expected_qreg_sizes}, found {qreg_sizes} for {test_id}" + ) + + creg_sizes = sorted(creg.size for creg in qc.cregs) + assert creg_sizes == expected_creg_sizes, ( + f"Expected creg sizes {expected_creg_sizes}, found {creg_sizes} for {test_id}" + ) + + # Counts the occurrence of every gate in the created circuit + created_gates = qc.count_ops() + if alg == "qft": + # Checks only correct gate existence due to different synthesis in different qiskit versions + missing_gates = expected_gates.keys() - created_gates.keys() + assert not missing_gates, f"Created circuit is missing expected gates {missing_gates} for {test_id}" + else: + assert expected_gates == created_gates, f"Created circuit does not contain the expected gates for {test_id}" + + +def test_error_correction_transpiler_edge_cases() -> None: + """This Test is supposed to check that various incorrect edge cases are handled correctly. + + Generally, this means the right error message is raised + """ + # decoded qubits must not be accessed again + qc = QuantumCircuit(1, 1) + qc.x(0) + qc.measure(0, 0) + qc.x(0) + for transpiler in [get_transpiler(name)(qc.copy()) for name in get_available_encoding_names()]: + with pytest.raises(ValueError, match=r"accesses qubits \[[\d, ]+\] after decoding"): + transpiler.transpile() + + qr = QuantumRegister(2, "q") + cr = ClassicalRegister(2, "c") + qc = QuantumCircuit(qr, cr) + qc.h(qr[0]) + qc.measure(qr[0], cr[0]) + with qc.if_test((cr[0], 1)): + qc.x(qr[1]) + + for transpiler in [get_transpiler(name)(qc.copy()) for name in get_available_encoding_names()]: + with pytest.raises(ValueError, match=r"does not support control-flow operations such as .*"): + transpiler.transpile() + + # barrier on single qubit + qc = QuantumCircuit(1) + qc.x(0) + qc.append(Barrier(0), []) + for transpiler in [get_transpiler(name)(qc.copy()) for name in get_available_encoding_names()]: + qc = transpiler.transpile() + assert "barrier" in qc.count_ops() + + +def test_available_encodings() -> None: + """The available encoding names should map to their transpilers and be safe to mutate.""" + names = get_available_encoding_names() + assert set(names) == {"shor", "steane"} + + names.append("dummy_encoding") + assert "dummy_encoding" not in get_available_encoding_names() + + assert get_transpiler("shor") is ShorTranspiler + assert get_transpiler("steane") is SteaneTranspiler + with pytest.raises(ValueError, match="not a supported error-correcting code"): + get_transpiler("dummy_encoding") + + @pytest.mark.parametrize( ( "benchmark_name", @@ -501,6 +767,41 @@ def test_get_benchmark_alg_with_quantum_circuit() -> None: assert qc == qc_bench +def test_get_benchmark_alg_encoding_parameters() -> None: + """Test get_benchmark method with different encoding values.""" + match = re.escape("Invalid `encoding` 'invalid encoding'. Must be one of ") + "[\\s\\S]*" + with pytest.raises(ValueError, match=match): + get_benchmark_alg(benchmark="bv", circuit_size=1, encoding="invalid encoding") + + assert get_benchmark_alg(benchmark="bv", circuit_size=1, encoding="shor") + assert get_benchmark_alg(benchmark="bv", circuit_size=1, encoding="steane") + + +@pytest.mark.parametrize( + ("level", "target"), + [ + (BenchmarkLevel.INDEP, None), + (BenchmarkLevel.NATIVEGATES, get_device("rigetti_ankaa_84")), + (BenchmarkLevel.MAPPED, get_device("rigetti_ankaa_84")), + ], +) +def test_get_benchmark_encoding_ignored_for_non_alg_level(level: BenchmarkLevel, target: Target | None) -> None: + """Test that `encoding` is ignored (with a warning) for non-ALG levels.""" + match = re.escape( + f"`encoding` is only supported for BenchmarkLevel.ALG and will be ignored for level={level.name}." + ) + with pytest.warns(UserWarning, match=match): + qc = get_benchmark(benchmark="bv", level=level, circuit_size=3, target=target, encoding="shor") + assert qc.depth() > 0 + + +def test_get_benchmark_encoding_no_warning_for_alg_level() -> None: + """Test that `encoding` does not trigger a warning for the ALG level.""" + with warnings.catch_warnings(): + warnings.simplefilter("error") + assert get_benchmark(benchmark="bv", level=BenchmarkLevel.ALG, circuit_size=3, encoding="shor") + + def test_get_benchmark_faulty_parameters() -> None: """Test the get_benchmark method with faulty parameters.""" match = re.escape( diff --git a/uv.lock b/uv.lock index f8d4a17c3..12b077ca3 100644 --- a/uv.lock +++ b/uv.lock @@ -19,6 +19,12 @@ resolution-markers = [ "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] +[manifest] +overrides = [ + { name = "symengine", specifier = ">=0.11,<0.14" }, + { name = "symengine", marker = "python_full_version >= '3.14'", specifier = ">=0.14.1" }, +] + [[package]] name = "accessible-pygments" version = "0.0.5" @@ -722,76 +728,76 @@ wheels = [ [[package]] name = "filelock" -version = "3.32.6" +version = "3.32.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/38/46/126b1831dca12060d4a8296bf9c4fe5c93c4f22197fa239cb0cc82042bba/filelock-3.32.6.tar.gz", hash = "sha256:a3f55a18af3652a94d8f47d6055df434f254ca1d02ef2524850c6d249ca2512c", size = 225172, upload-time = "2026-09-08T22:57:11.528Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/a0/50c2c0ce5e74d7721bbb1b19a26ebd339aac5878553a6e35308c2f31f935/filelock-3.32.5.tar.gz", hash = "sha256:f6a6a28f743f9b95ce19db5abe0f376f75eb56517dff21e1a4751e2657d3e83d", size = 222838, upload-time = "2026-08-31T18:56:34.729Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/06/4f138f618dbea66803291274f228f01daf29f306fe8b96bc30dab765df75/filelock-3.32.6-py3-none-any.whl", hash = "sha256:3f16ecd0117feae0dfc147e8c62eb5daeccd8bd800378c3ddf416de9b4feb6b1", size = 100189, upload-time = "2026-09-08T22:57:10.182Z" }, + { url = "https://files.pythonhosted.org/packages/36/d2/b70a31e13d04456d28493f31d2aa087e99eeb2767ef0293b2625727ccb8c/filelock-3.32.5-py3-none-any.whl", hash = "sha256:142cd9fa77a872c5e78c62329a0d15278fadc686eb89e760017968961a4fd6b2", size = 100003, upload-time = "2026-08-31T18:56:33.078Z" }, ] [[package]] name = "fonttools" -version = "4.65.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/77/51/d63c7e52163ac14393a35bd14bd7c0da95f8f74be5d7cc988092f9965129/fonttools-4.65.0.tar.gz", hash = "sha256:762ba5431358d0dbd4a01982484a1d494fb267e91f974cdcf20b80eab8560f6f", size = 3674467, upload-time = "2026-09-10T15:35:54.955Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/9e/58250cdc54d96fcfacb544e12997a6390fa4e6b71ae2241cfcfe5b341803/fonttools-4.65.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:06273c71e692caf5989c0437ca50875a5e49e216ddf653228fe9bb35bdc82c0f", size = 3089141, upload-time = "2026-09-10T15:33:24.509Z" }, - { url = "https://files.pythonhosted.org/packages/3e/67/0f0416069e38da0a1327a847a2e8dd1edb425d0043d8a3e63eb940070209/fonttools-4.65.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ca2b02d74e9ad7e21a1d11e4701425800a4b0c63cf90486e60258262feccbcbf", size = 2584298, upload-time = "2026-09-10T15:33:26.598Z" }, - { url = "https://files.pythonhosted.org/packages/99/0d/7e40e9957359afc0bab081131c215370a6d2d203361bb6f945ab595e924c/fonttools-4.65.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7830e9fa3bebc44dbc27ff44d8201def30ea5c48a773696d58e69e6bcd9cd5d4", size = 5498163, upload-time = "2026-09-10T15:33:29.071Z" }, - { url = "https://files.pythonhosted.org/packages/a1/e6/e48cf0a272a5d4d17a09d44f92727e67f975ddfa94acc8464763d19a654d/fonttools-4.65.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a3991732c87b3f054a2a8cf86dd0d602833fa8cb37c911503173771646e1013d", size = 5457456, upload-time = "2026-09-10T15:33:31.918Z" }, - { url = "https://files.pythonhosted.org/packages/e4/8a/a5c67ddeda82ee5e4ec3bc52ac1cbb685f0bb7a0516badc55643b454ab0d/fonttools-4.65.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6031e77b3fb8c765055ba2b8bd8dcb17030f3bf2484c448b472fdedf4460ba80", size = 5464334, upload-time = "2026-09-10T15:33:34.605Z" }, - { url = "https://files.pythonhosted.org/packages/d7/16/294e77383b2d39c9f8f25144a7ba23fe1cbbc05227cc72545097785ff07c/fonttools-4.65.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6813cc1e2e883bd6c15b3e04f72c78dc65fdc4ca861063adf5f341fbaec2ca62", size = 5595597, upload-time = "2026-09-10T15:33:37.591Z" }, - { url = "https://files.pythonhosted.org/packages/80/01/8e74ce8626c734959c782f2d89af8e9f14d078fd3d4ddf8b5a51401ae475/fonttools-4.65.0-cp311-cp311-win32.whl", hash = "sha256:4a5db8442453da4b6f43ad325879381b726bf2238a2253efd9584be21a2cefc2", size = 2441114, upload-time = "2026-09-10T15:33:40.592Z" }, - { url = "https://files.pythonhosted.org/packages/37/3e/835dc6c658426e2670b7f38c38295492fcbaeb06080e9dce89ce8105993c/fonttools-4.65.0-cp311-cp311-win_amd64.whl", hash = "sha256:9f201796c8e24e657be77c16fa664e798a46122144217f90838982937a964f0a", size = 2498823, upload-time = "2026-09-10T15:33:43.402Z" }, - { url = "https://files.pythonhosted.org/packages/58/db/242fa4fce7f632c5f7ab15585343393b25792510c0c32bd218ad24d59f1c/fonttools-4.65.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e844a45c9e5ced6536f184cf1a65b5d65e8f7e711993b413e10500a8223622e5", size = 3097120, upload-time = "2026-09-10T15:33:46Z" }, - { url = "https://files.pythonhosted.org/packages/a0/b6/42fa4d373416675f74446421cf0b2badb82a4245c60745f05f424f75c649/fonttools-4.65.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b30e953de049bf43fc0a63c7d0c44d205c923e4bbf24716aae1518c0e65f977c", size = 2584658, upload-time = "2026-09-10T15:33:48.473Z" }, - { url = "https://files.pythonhosted.org/packages/75/6f/d589b9d62280a846c77a2c383d852c6dcb79ae8aa02bf0fa46c8577af145/fonttools-4.65.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09c34bdeed8915bfb53bee0c8ed2254dbd8ec69c0014b7f3702f347c049bf358", size = 5425737, upload-time = "2026-09-10T15:33:51.473Z" }, - { url = "https://files.pythonhosted.org/packages/b5/09/de2c0c20a42c18e565a2617932beb08c06697bbdd0d3f62b108262e11583/fonttools-4.65.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:05595385ae99f4b9626cebb973bf171b8fe38a8f40708e6e42abba0ed7537778", size = 5402463, upload-time = "2026-09-10T15:33:54.907Z" }, - { url = "https://files.pythonhosted.org/packages/79/2e/bc0f5c9dce21821454bb5812d3b23410bca33c8bbd5468386d0327aa0cff/fonttools-4.65.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d95b34dd68fbfc0e4a1740c421597656117f979ed8dc85de66e08f9f9981806e", size = 5362168, upload-time = "2026-09-10T15:33:57.481Z" }, - { url = "https://files.pythonhosted.org/packages/1f/0d/2116763ade7e71e0e5d421babe1785d745be9b3d605bf914792ce1c97f79/fonttools-4.65.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:924d06e6130429168318db71c40174a765ad016fc4b56ca811287e3d7373b3a6", size = 5524117, upload-time = "2026-09-10T15:34:00.021Z" }, - { url = "https://files.pythonhosted.org/packages/e2/5e/f9600553b9f645e3068553831685ff1dab6259536a23b38d2e048de38f17/fonttools-4.65.0-cp312-cp312-win32.whl", hash = "sha256:04f73dd01005752a6e75cf4a8dc6b70dc724d1d4bc34cc89522153f4a2f07680", size = 2432089, upload-time = "2026-09-10T15:34:02.803Z" }, - { url = "https://files.pythonhosted.org/packages/3a/02/e436a6a1863b9862bab9f82d6da33055dd7aa3738017edd902a163525dc2/fonttools-4.65.0-cp312-cp312-win_amd64.whl", hash = "sha256:3b5d9ba89edf778b376e669b879ae33a198bf45cf5a23c3f6514f935cf9d0d9d", size = 2483475, upload-time = "2026-09-10T15:34:05.09Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5c/343a4225e83eb06f82c1d8bf41fc5e5f71eab2f62bc7ca215c764722c0a9/fonttools-4.65.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8b7bb52817a24731d2e4f4df0e71fdde05e6c806c8f8f1517b015d142fdacfa5", size = 3094663, upload-time = "2026-09-10T15:34:07.235Z" }, - { url = "https://files.pythonhosted.org/packages/9b/c9/49b2401be932741d9218181c08948c96db32eab21ecaaf79283b752e13e7/fonttools-4.65.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e2c21772fcf70325189707b19f346812690bb1b0bd7e207e6ac205244806b303", size = 2584521, upload-time = "2026-09-10T15:34:09.648Z" }, - { url = "https://files.pythonhosted.org/packages/51/c9/48b07e6c5cf44fa56f758a04c3772a66c0e9ba82bbe22077e4076746a62b/fonttools-4.65.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:64c9b26816415b5e3d899e9077109d327b22140fe3c4066644d8cdbad5bb1569", size = 5395399, upload-time = "2026-09-10T15:34:12.38Z" }, - { url = "https://files.pythonhosted.org/packages/fa/2d/5cc5a10c8ed56079d6c2e9e3e5920622529a2ae045c9f47a6055ccb1a319/fonttools-4.65.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6dd6243f60e2d6160c2966e1e14020dc261ffd741b69a2e4ca8bfd051592e4b7", size = 5376659, upload-time = "2026-09-10T15:34:15.239Z" }, - { url = "https://files.pythonhosted.org/packages/ab/6f/ccf33739d936bb3afa1655a225be7ee5d63d6d3c8b7d0e570bc5a2a7b899/fonttools-4.65.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:834962fd7cf21c58e81ac50a59e6ed2306f9df5e3dd481dad1cd7d2c4c60b773", size = 5336795, upload-time = "2026-09-10T15:34:17.817Z" }, - { url = "https://files.pythonhosted.org/packages/7e/c1/ffd17483f2094f0f4118974295b514b5b82afd4f6e3c80c23f01684e97a6/fonttools-4.65.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:580eb68ff7bd6954a7a76afddd864bfc66eaaf5f5c20dd6ead9186d0055a4ffe", size = 5496530, upload-time = "2026-09-10T15:34:20.241Z" }, - { url = "https://files.pythonhosted.org/packages/b1/19/9aca7712d0676ba5f8d1530ce20478a9bb09cccd0153d56693337379cdcf/fonttools-4.65.0-cp313-cp313-win32.whl", hash = "sha256:7a18b2ffd44249fe84289253197aa65ad4f2de554c0d381f18b1f5939bc6bc60", size = 2430392, upload-time = "2026-09-10T15:34:22.906Z" }, - { url = "https://files.pythonhosted.org/packages/1f/6f/f015dea0f4354e0798751b657cea2dee482914737f88bf1a078349ce92cf/fonttools-4.65.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ae1846b0f192fd485d26a455af19b8f5cf05aff08f9836f533913d8fcea133c", size = 2481620, upload-time = "2026-09-10T15:34:25.816Z" }, - { url = "https://files.pythonhosted.org/packages/64/29/606365ef601668bfebed14cfe3dc72bb7fcd1e23011bbb2833f17fea3065/fonttools-4.65.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:dc87a9f846bec83c3795804f62b4632716d46e3522869a3dd9cd44a5d245b006", size = 3098393, upload-time = "2026-09-10T15:34:28.472Z" }, - { url = "https://files.pythonhosted.org/packages/c7/61/11412939d6b7abf5ac7ce0d61d7f94a0a4fbabc9f1ab0a04fa622e0fc11c/fonttools-4.65.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa50dd7b9baf75e2bbd43401fc0d237f7a94a8ad2e0c57ea97160fc631af5eb0", size = 2585766, upload-time = "2026-09-10T15:34:31.207Z" }, - { url = "https://files.pythonhosted.org/packages/db/17/734921d8aee8309801da42590375d32d4d46f771b73373ec9520d5d4220b/fonttools-4.65.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0d2a9892fdb3b7e2d0f4174e3b907d226ff83698249762eeefce08ec5b2de1dd", size = 5380679, upload-time = "2026-09-10T15:34:33.933Z" }, - { url = "https://files.pythonhosted.org/packages/cf/eb/2a4d78d60d978e694cfa04c98e4d8ddbf7f028fd768ddef470bb9da5d69e/fonttools-4.65.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6d815734e7fede0ad1f233f23f0f191cbe8fc64762ff041e589bc0f78e0b2397", size = 5321833, upload-time = "2026-09-10T15:34:36.295Z" }, - { url = "https://files.pythonhosted.org/packages/d9/ca/1cd48b5c11ef9658732787bf2362e1bf3871dad5945d2f6cc8f675ca769c/fonttools-4.65.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:71e4c67b6196a2f447f46476fd2302604721617f5e0a21b0988bdd87b6bb9687", size = 5320938, upload-time = "2026-09-10T15:34:38.992Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0d/90e6051bded926cccabe9dd0bce3b6ca012f4d5779d61167afbf4989ceb6/fonttools-4.65.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b11d8a4a0c3ca74bbd4c105b7ef82501945c939e6096d9934ec7d288cdf5aaa9", size = 5451747, upload-time = "2026-09-10T15:34:41.387Z" }, - { url = "https://files.pythonhosted.org/packages/18/74/23e0268e48029ff0752083f69312c49b738163d5af919add9dc4ac81e907/fonttools-4.65.0-cp314-cp314-win32.whl", hash = "sha256:8e44a34d91b3c793879767eb115867ced74d2eb94974e64e72fe9e2eea71cf1a", size = 2434246, upload-time = "2026-09-10T15:34:44.385Z" }, - { url = "https://files.pythonhosted.org/packages/a1/2d/ee69affecd4bc81cb932a213438d4199fb48bf8ca6d664438ccc7f623c2a/fonttools-4.65.0-cp314-cp314-win_amd64.whl", hash = "sha256:0aa8901db22875c831d6a91796549590d7e747da37438f38b69d771b668be445", size = 2486706, upload-time = "2026-09-10T15:34:46.842Z" }, - { url = "https://files.pythonhosted.org/packages/8e/9c/edee5f785198ce3327e1ddeace91c773122d47f086a67e0a84b800f4a940/fonttools-4.65.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:2e4a380ca40d3a5372e31b340f0da0d53b4583aadbb8e41f6a516afa69c509a4", size = 3172027, upload-time = "2026-09-10T15:34:49.269Z" }, - { url = "https://files.pythonhosted.org/packages/9c/c6/252ec9884381089bc30da75978b072593920249de60219817f16cbc9145f/fonttools-4.65.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:661bd91c4be13721408b2d4b67a9b3fa7736713adc9a6c9780c9c60fc7959f90", size = 2619020, upload-time = "2026-09-10T15:34:51.453Z" }, - { url = "https://files.pythonhosted.org/packages/42/79/f71b0d202b8473bb45b07876c08a474de9fe560ed2c0cde642812a81e22a/fonttools-4.65.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62c5e42c79449def957adf8a9a65a43018efa7e2a6bc6baa3afe955e0d5fb2ab", size = 5545942, upload-time = "2026-09-10T15:34:54.804Z" }, - { url = "https://files.pythonhosted.org/packages/4a/bd/52e1bf33e0aebfe22ecc9a85c634db707c1dd9f6b1b438efeed98c55b959/fonttools-4.65.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:36fca8efc46b5adfca327c666e739fc05b7a7a6ef17840230f81b22f53230f61", size = 5351477, upload-time = "2026-09-10T15:34:57.514Z" }, - { url = "https://files.pythonhosted.org/packages/5c/76/8c6b2ad20beec95cd446f3a8bdc753c7e4a4fd69ef3e66704c0f7c8cb0b5/fonttools-4.65.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8aa1291e4c767abf1b0b79ca2d6895f7c0b661d9d95d03b5791c883a9d1e1f08", size = 5412271, upload-time = "2026-09-10T15:35:00.895Z" }, - { url = "https://files.pythonhosted.org/packages/79/49/fadbf11bbbd2d699d88a5498a0634280206e01e3bd5da9a4e0c504953ce9/fonttools-4.65.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fcf39949f56911348514b466714efa9118bec3d2be249e1c487263f7cda6edab", size = 5446450, upload-time = "2026-09-10T15:35:03.369Z" }, - { url = "https://files.pythonhosted.org/packages/df/77/5fda646d3a6d5ee26465865c00319cc0925cf484a3b42dd8232d5b39f973/fonttools-4.65.0-cp314-cp314t-win32.whl", hash = "sha256:ffc918702661f1d74d2fbb2f5551036b64f6d2d743139e105289b694bcd16f54", size = 2467858, upload-time = "2026-09-10T15:35:05.744Z" }, - { url = "https://files.pythonhosted.org/packages/ae/0f/afa0f3de70ebe02bba46b32cccb30b1de52624472b14ac2e7cd403d08db9/fonttools-4.65.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5a977e3645dbffaee924209828aa702a215f7ff68bc08010740c10c723787e62", size = 2518248, upload-time = "2026-09-10T15:35:07.846Z" }, - { url = "https://files.pythonhosted.org/packages/86/54/b273cf5712b36a381c13284fbf244e811e1e1d7081aed20f200f0a191ffa/fonttools-4.65.0-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:7aa0518b45ff5286ad56f063938db3add3816e899aab58d782b3f9a252523caa", size = 3092770, upload-time = "2026-09-10T15:35:10.868Z" }, - { url = "https://files.pythonhosted.org/packages/36/1d/d3e4511475954d4ec4f3c254e81f0fcc1ade504cb7381eeca48b8c44b8a6/fonttools-4.65.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:673e2b3ac4ac8e4f3607d390ecc5a606e5db5c4e88fb4cb2999593efb65afea2", size = 2584364, upload-time = "2026-09-10T15:35:13.54Z" }, - { url = "https://files.pythonhosted.org/packages/e5/3f/7cfaba467bdd1d3d04be21ba7b5bd75c6ca58ba280e0519707ca96a43c4d/fonttools-4.65.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a03cff943b204a90bf3d1c04c97b9509a8aa0ee99e2e544084ca43ad995975b", size = 5377819, upload-time = "2026-09-10T15:35:15.839Z" }, - { url = "https://files.pythonhosted.org/packages/25/17/a68d9b19a97bb2ee37e8098fea50657073df97c7e5392afbe1b9d7c0c581/fonttools-4.65.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:41f684ee6212e411196ab054f8308faf6605f154950e6f4686fb8f2103d624b0", size = 5341296, upload-time = "2026-09-10T15:35:18.527Z" }, - { url = "https://files.pythonhosted.org/packages/ad/8d/d744653ed607a241339015d4af6743ca3d85a74a2045a02cc06ea0383c71/fonttools-4.65.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:52ea9d2a8385075770db74d5e5718fa80b2222bb4fc62856a377dd2865ca8848", size = 5315310, upload-time = "2026-09-10T15:35:20.853Z" }, - { url = "https://files.pythonhosted.org/packages/f0/c8/1ca6dc69cbaa0e394ff70d9e266777124d3ce3c6433026a6b4de50b890ae/fonttools-4.65.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:d0d25027ade65ec46b13c0436e51bcb7c5171a4ea255a5e7a8d0d1d3ab4cffd7", size = 5463840, upload-time = "2026-09-10T15:35:24.022Z" }, - { url = "https://files.pythonhosted.org/packages/07/97/d374df38a14f2ac04ba9ed96bca89d4988e7d63aa5ed85c54fd8f2badd7d/fonttools-4.65.0-cp315-cp315-win32.whl", hash = "sha256:22cb846d35d278235ef3b7e947c6040b2057d72e8305a314f21d5342eca49040", size = 2433149, upload-time = "2026-09-10T15:35:26.59Z" }, - { url = "https://files.pythonhosted.org/packages/62/c5/eb8f7506faf6a70c5a8f2eccbeea3cd826c748a6fd78c7b4b3effb5a3a11/fonttools-4.65.0-cp315-cp315-win_amd64.whl", hash = "sha256:aecc899fdbf9ecbf728f8977977e2e1043ee4d70c257124c8fa4cbcf796fcd83", size = 2485725, upload-time = "2026-09-10T15:35:28.945Z" }, - { url = "https://files.pythonhosted.org/packages/f1/a4/2df0d97514feb8d857d5de854cd8d660d9a7ee1fd081bc98497124f515a8/fonttools-4.65.0-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:6275863dad195ee34b6e0ca3fc61c74096bc37e5d6fb8e049f4d68d65865a2b7", size = 3164065, upload-time = "2026-09-10T15:35:31.197Z" }, - { url = "https://files.pythonhosted.org/packages/15/33/e09661c09e6c8a3b0bd50da0e72918df7576dede31f39c948cc245011434/fonttools-4.65.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:d8ffd2f62b402180b0edae8f86a071f583970e2177143117db5cf4c52da60079", size = 2615195, upload-time = "2026-09-10T15:35:33.569Z" }, - { url = "https://files.pythonhosted.org/packages/64/d7/114b05f4193679d0935272220083ec43de7f918b5a777018c5ac5c1ae39e/fonttools-4.65.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:830f91327ca83bfc1278e7060068a498938f84d05dc4869675486f84f55d4fe1", size = 5521239, upload-time = "2026-09-10T15:35:36.291Z" }, - { url = "https://files.pythonhosted.org/packages/ba/10/67d615939f859ffe75663a67bca3593966febbcd0109ec72f20f73cbb4cc/fonttools-4.65.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9db2cb95847c18eef74a4ef0fe257a893ae3f4b0395f4866e2f426ab07f3d804", size = 5343802, upload-time = "2026-09-10T15:35:38.82Z" }, - { url = "https://files.pythonhosted.org/packages/d1/06/faa793a806da03acce862fbed353f76026b28103d43860d69be9a6a1f0fd/fonttools-4.65.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:be9b9a95ed0af03375e99020e921c4bc6b41fad10e053dea7acad370521a3c46", size = 5388482, upload-time = "2026-09-10T15:35:41.616Z" }, - { url = "https://files.pythonhosted.org/packages/53/d2/eb7258df60e634db60c9a8cd72bc9eaf8dea409d1b1ceec3b9fb49531ad2/fonttools-4.65.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:bbd9faf777a9deb6790df4f2b0be611857c45fe86605e840d7154a028d828af7", size = 5435088, upload-time = "2026-09-10T15:35:44.639Z" }, - { url = "https://files.pythonhosted.org/packages/00/6a/58597f16e1265fe9205de3069e338a339e3eef0b1daf049ee08269458091/fonttools-4.65.0-cp315-cp315t-win32.whl", hash = "sha256:c779d838815b91889c95ed64c9be5950ad5a683279f91aeb23384cb757ddc6a3", size = 2464925, upload-time = "2026-09-10T15:35:47.224Z" }, - { url = "https://files.pythonhosted.org/packages/4b/95/122fc172006db747f4968e08c710f52a94f55eb007173b859b3f80b5b810/fonttools-4.65.0-cp315-cp315t-win_amd64.whl", hash = "sha256:d9484b7ee1b49b6b8a0231c849f3983723dec29e3a7366d9b1b02f4036f71944", size = 2513945, upload-time = "2026-09-10T15:35:49.895Z" }, - { url = "https://files.pythonhosted.org/packages/e6/35/f894ceb867118c0261d0f69a9bd516b045a3754238f76c88a49513ac7a83/fonttools-4.65.0-py3-none-any.whl", hash = "sha256:3060b8c1fc2329fa20265b7c138614143ea7c1624e26c5c180c76aeb74deae6f", size = 1196441, upload-time = "2026-09-10T15:35:52.347Z" }, +version = "4.64.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/41/0f072a712dc74496e03710e462a18a4cfd8a258ad055a4e22d28b43a7abd/fonttools-4.64.0.tar.gz", hash = "sha256:ecb2e59a7bc692fee64dda6010deb66222335693b30046f15cccf81233aa715f", size = 3664266, upload-time = "2026-08-31T15:44:33.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/f7/a222d1e20d460a09d08fe0b612a6f373235168c6c3228ff6a913cc8ff9ea/fonttools-4.64.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dac25768be4c03a990c359f408cb7e8958ed0e93061e495b3642ce7909761205", size = 3087538, upload-time = "2026-08-31T15:42:26.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/fd/571331ac2b9ea43403ba43f27f5b45427a13b5c559d4379c99f3f9437b59/fonttools-4.64.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d652592c71683941b768306fa1c7c6ce1bb9b072505043feafe86305d71030b7", size = 2582847, upload-time = "2026-08-31T15:42:28.664Z" }, + { url = "https://files.pythonhosted.org/packages/dd/bc/583ad5e4d6fbc600b1d9eb3c2e8b4eac5ca1c204fa29e4ef0ff344d8aa60/fonttools-4.64.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:801fd04899d72eab34f02ab78d0451525621b3bd589da9d2d480dfffe951b643", size = 5493826, upload-time = "2026-08-31T15:42:30.991Z" }, + { url = "https://files.pythonhosted.org/packages/f8/3d/4edf079bdb01791abe87753b7c8fdca4e8ec709276e7b030a1aa84a88a51/fonttools-4.64.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ff7aff4637fbf71394df139c63ccfe08a47aa4252d2f91224ddb3335c716c925", size = 5455609, upload-time = "2026-08-31T15:42:33.149Z" }, + { url = "https://files.pythonhosted.org/packages/7e/b6/e36ea109c8cfdb0f174c09d8464b4ddf6b446f97313118d69b6a28cd7189/fonttools-4.64.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f521d79d6acda4923b264805541696f452079db0952a5bb96f9ff742f50629ec", size = 5460840, upload-time = "2026-08-31T15:42:35.423Z" }, + { url = "https://files.pythonhosted.org/packages/6c/bf/0d8bb1fbb96c621c1da5f93e288bec8424802df10265e790dddc66626b1f/fonttools-4.64.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a0afa8bac675445dc0e2ba2891ecbedd9be89cb437afa94c823e0290cc2c4bc5", size = 5591341, upload-time = "2026-08-31T15:42:37.874Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e1/6f8a1a01e5ed4408fdffb934c77ce65d978a2df6fd6eaaec5e6b864b10f5/fonttools-4.64.0-cp311-cp311-win32.whl", hash = "sha256:c3c1fb656063a2f762db5378ea8d38ad5f7836b4f3fb8c4652270ded43df2935", size = 2439647, upload-time = "2026-08-31T15:42:40.196Z" }, + { url = "https://files.pythonhosted.org/packages/21/41/b575f14a653911f33f17ef62bbeaa818c94bc7e694579cc9ddec6935d5b2/fonttools-4.64.0-cp311-cp311-win_amd64.whl", hash = "sha256:e63b63b8b5fdb8e29318dff2b15c5f852be46e972775b466f75b848f6eed4502", size = 2497436, upload-time = "2026-08-31T15:42:42.36Z" }, + { url = "https://files.pythonhosted.org/packages/82/23/4ea251977fef70ed14193785e1b2949355f1f5927dc0ef1dada675c0bbb2/fonttools-4.64.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9ecb2b206b5b2386f6968721a0770226b66bdd54adc4279bfff3ddf62873eed8", size = 3095501, upload-time = "2026-08-31T15:42:44.299Z" }, + { url = "https://files.pythonhosted.org/packages/c4/32/943e9034f49797e1a25dbbd60c8047ce0c38c3585c5d1b2db38ce64059c5/fonttools-4.64.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8c631303bb1fd7be3067c47536a30ff1fcb4846d6008c112bc52a03f7cd6965", size = 2583091, upload-time = "2026-08-31T15:42:46.57Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4b/332cb3105d5d550cb5728e669151601c4f7698cbedafd134154eb806ef83/fonttools-4.64.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2763e452b025ee8e990f0462e76052de9bb094ebc21d296f62c6dfe958886b4", size = 5423449, upload-time = "2026-08-31T15:42:48.615Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8f/e5f2906ea833d362916c64a2f6b1922a07b19442744fd3cd89563f429bff/fonttools-4.64.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:66a83f93579fb3493e458c4449d1d566a7b2a1c7b19915cd0fa3c9b8b5a8540b", size = 5400981, upload-time = "2026-08-31T15:42:50.862Z" }, + { url = "https://files.pythonhosted.org/packages/fe/46/1b325ebb20aef8bb05f803052ea65931d73638ae3e0d02a6658e3d14e0f8/fonttools-4.64.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cf67f96dc0bfe9607f5f2b734cedfbe2f6f995231adee4ccefa12872044d452d", size = 5359588, upload-time = "2026-08-31T15:42:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d3/ac56fa880e01339aa6ad0bcb457cc30bb4d6c5ca79922e1bd650e4a3a396/fonttools-4.64.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6786bed88581e19bc4f28ea7a64ad531e8f54acf50327fddca942688824a60bd", size = 5520698, upload-time = "2026-08-31T15:42:55.919Z" }, + { url = "https://files.pythonhosted.org/packages/d7/40/1251fef04c308836a3a7db523703a7ca8ab010dc1e3b9a9bf08e0861c7a8/fonttools-4.64.0-cp312-cp312-win32.whl", hash = "sha256:da4c9bdeaf6b06c12d13d0addfc8ef15aa9695d26574a6dc10751258bef72f30", size = 2430689, upload-time = "2026-08-31T15:42:58.48Z" }, + { url = "https://files.pythonhosted.org/packages/6f/1c/ce0e89183c0235ff6cfbf0603d593023a5afa5333efb35ff569cc0be9ce2/fonttools-4.64.0-cp312-cp312-win_amd64.whl", hash = "sha256:06b6409b868494556a831ae33b2d9a090476c37516b38d70f45a9720b460d423", size = 2482071, upload-time = "2026-08-31T15:43:00.598Z" }, + { url = "https://files.pythonhosted.org/packages/44/7b/49b0054a79b9ed918c018e70e09b56eb5678ee8b44e59e126c79de4d8d73/fonttools-4.64.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9443eefff58aad558608f352092e1be6d278980e8c3b4e8621fcbfda97818500", size = 3092750, upload-time = "2026-08-31T15:43:02.705Z" }, + { url = "https://files.pythonhosted.org/packages/83/e2/08e73bd2f6e6248f071d3e9debe4c2b4cecda3a7dc057c56b44e255421c3/fonttools-4.64.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:09657817b75575822bcd6098ef0ebf0386f34430839ee53109e70fd40a7f6539", size = 2583001, upload-time = "2026-08-31T15:43:04.923Z" }, + { url = "https://files.pythonhosted.org/packages/2f/58/6dc0ff0963fc1e12f53bac59175617ec5927342ee7a4ffb871b44d56d81b/fonttools-4.64.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d7995b906666037d7114c20a5566a372902747452af7d5bd4cd6bca8f1a2550", size = 5392765, upload-time = "2026-08-31T15:43:07.096Z" }, + { url = "https://files.pythonhosted.org/packages/7a/dd/4d3911049680da7b3aecf97cb5094e3a05554f5d94829358fc7664640456/fonttools-4.64.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2c42237b7e8c6813643e57d3efed3be094d4c06339dc2166b626e2cc5c12ee93", size = 5373969, upload-time = "2026-08-31T15:43:09.617Z" }, + { url = "https://files.pythonhosted.org/packages/48/0a/a2cf94121fd3ca9166bdb4863f71d6db32a5606b223c81e2ba99832a0612/fonttools-4.64.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:498f02ea92c9ca18c0f9c581ea93184a9d56c25b0af14189b0767adaf34235d8", size = 5333854, upload-time = "2026-08-31T15:43:11.985Z" }, + { url = "https://files.pythonhosted.org/packages/44/e2/632f4a8b94e6d7eea41e6d82f0ed337f64f5fb96f9910e2ac6b1689910e2/fonttools-4.64.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8252f20108e557532f91d7d6dd9af87c16ed6fa930f65516aa480fa2cfed3363", size = 5492828, upload-time = "2026-08-31T15:43:14.288Z" }, + { url = "https://files.pythonhosted.org/packages/ff/25/7fee1978fdedc1a2d978b76dddbea88785416f5774e7222327b867712d71/fonttools-4.64.0-cp313-cp313-win32.whl", hash = "sha256:45e3ecc3888f1637094fd75cd8fc727f3a4b06d1ddf89181126c071e244fd2a5", size = 2429011, upload-time = "2026-08-31T15:43:16.463Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ed/bb1c217c9e1fbfa59f42b266af57df937a703882a73e59a309773752228f/fonttools-4.64.0-cp313-cp313-win_amd64.whl", hash = "sha256:e4812f71c39d77ec5041348dafa400532adf7bf8f1fffa9aa6495fce5876d7b8", size = 2480199, upload-time = "2026-08-31T15:43:18.774Z" }, + { url = "https://files.pythonhosted.org/packages/a0/0a/59b9074b8ab165cc28d3e08bcf7a8eff8e1dc5932a5a41c87b15952fa3af/fonttools-4.64.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6f1ce9ef9a1b13098efdc2e43a2ed96d9851bbde7b31c652a87552c4efe9b422", size = 3096790, upload-time = "2026-08-31T15:43:21.125Z" }, + { url = "https://files.pythonhosted.org/packages/17/39/af1077a36feefe79f36d67d75aee637a96673c0a74eeb2c6f24266f5a20b/fonttools-4.64.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:83cc48d1411d2ff388dab99973dca81172cc9ceae9c9799da9548d494cfb38cb", size = 2584288, upload-time = "2026-08-31T15:43:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/00/c4/1ea58af0eb78264d28e5871bba0810bf0943cd93d641fdeae92553ad3410/fonttools-4.64.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e412767d1c9765cf1b82f7b00f1686c6ca5809ebb77af363b3f9f2325a465c01", size = 5378061, upload-time = "2026-08-31T15:43:25.193Z" }, + { url = "https://files.pythonhosted.org/packages/93/99/5c7e36d770407b66f7fc8378d2bb47f1530d083d8b9247ff011ec9b4dd70/fonttools-4.64.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b4a7af455ffed980925bc0ebf5b8d6239e6c3e797d9d755b6db192fb3080d614", size = 5319458, upload-time = "2026-08-31T15:43:27.696Z" }, + { url = "https://files.pythonhosted.org/packages/a8/09/89e8d600e92723309d4ddd3944c4ca565197a5df0236ae4115ccbe797375/fonttools-4.64.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:398b14f89ca950b288bd290875f07e4e10685644fa4ac668546fb107b1ada4d4", size = 5316656, upload-time = "2026-08-31T15:43:29.842Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b3/56719ab37e1592ceac89724574146622bedec036a207be80f3c7b1c14cbe/fonttools-4.64.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dc96150f99e05a317cb1f042b92c4cf8bc93cdb1f9f85717322e202ecdf2e505", size = 5448380, upload-time = "2026-08-31T15:43:32.311Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b0/15951b7e3006073f3260da4f8983732d18903905cfe26daa5138a0bbf682/fonttools-4.64.0-cp314-cp314-win32.whl", hash = "sha256:1c3661324f3f0fa4539a32288a3e0711a5f3ccf020036e760bb558ae9811a16f", size = 2432778, upload-time = "2026-08-31T15:43:34.421Z" }, + { url = "https://files.pythonhosted.org/packages/8e/f3/0c97402b29411f4f6d31b32f2af5b4bcd5babe5a93c64558a8fd3654f140/fonttools-4.64.0-cp314-cp314-win_amd64.whl", hash = "sha256:043f6c572bf236f2a76e762c25f841daea11e8fc03e78088d7be66e0c5b4e4c0", size = 2485394, upload-time = "2026-08-31T15:43:36.506Z" }, + { url = "https://files.pythonhosted.org/packages/00/da/82192c7bee5314f04129a068dddae11082a63295d4e3b5ce08657de96608/fonttools-4.64.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4691a122b8c1d0d82d6e7510ce59d5c42146518240274b53e912e255573924f7", size = 3170118, upload-time = "2026-08-31T15:43:38.579Z" }, + { url = "https://files.pythonhosted.org/packages/42/2d/0b4f608d754f625fecc7d94b3b26af6f65f6ab4527b386bb0c6141cdd9ea/fonttools-4.64.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:3200180abc69639483cf54a17cca2e13c31ede5f665979ea0a9c829d093f372f", size = 2617333, upload-time = "2026-08-31T15:43:40.934Z" }, + { url = "https://files.pythonhosted.org/packages/5e/df/9f7448c38dea05458acfee81c443b14ef97d6d66c636af053d41cf8a32dd/fonttools-4.64.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53eee22af5b5a305c1ee2652955ed46b148e881456fcec1e7f0eb27f642f6bb4", size = 5542345, upload-time = "2026-08-31T15:43:43.114Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e8/cbf4a81e8be322bc4bee4ba31cd33a7b6336ef8030fa13b53b3e9c09fbf9/fonttools-4.64.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08f172961e11f4eb4f80f2f20049e09b0ea8e044fa6d456fed8346eb8588f360", size = 5349981, upload-time = "2026-08-31T15:43:45.363Z" }, + { url = "https://files.pythonhosted.org/packages/d8/dc/106fd9e93e962dbb60482d30c0913af3d108f5762429dfe93265a850bdc5/fonttools-4.64.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6eae4376adb104c2acfa76fd9ea0cb12b572ca1d70eceac709871f638ff76e93", size = 5409249, upload-time = "2026-08-31T15:43:47.817Z" }, + { url = "https://files.pythonhosted.org/packages/b0/2d/c7be990abf74c9c5c5367ae3dc65953ffce49fe00f62abd129cf725a2397/fonttools-4.64.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2730946ca8f12c356bd98eb9b2b095c8e761ed05bed5afb0d5b380cebe4f6370", size = 5443781, upload-time = "2026-08-31T15:43:49.909Z" }, + { url = "https://files.pythonhosted.org/packages/9b/cc/bffec3dccb9e3f4f6097a2baedaef5a8dec2cb816522ee163e4bb7f54b44/fonttools-4.64.0-cp314-cp314t-win32.whl", hash = "sha256:d30c966bea2deffa19c738c81776f7182da5ccabd97e666bae4f3d6ba87341d9", size = 2466542, upload-time = "2026-08-31T15:43:52.088Z" }, + { url = "https://files.pythonhosted.org/packages/96/49/e31f97dfc94e0648999f04bfccb37a97062d978f87df60654f496942c89f/fonttools-4.64.0-cp314-cp314t-win_amd64.whl", hash = "sha256:917fd520bb60809d83c14d43cfe48d5ad2516abaf2c073d65a431800dade2d29", size = 2516703, upload-time = "2026-08-31T15:43:53.954Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ec/e39b9e56db7dcc859983caa24cf2a36209dd71f6cc8ddbbf00d7342d7988/fonttools-4.64.0-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:8dd18fdff0ac9759b8d67a714730abee07b2312e3656c20ba5affb0107094762", size = 3091150, upload-time = "2026-08-31T15:43:56.309Z" }, + { url = "https://files.pythonhosted.org/packages/a8/e1/3e6a409d6f99efb66c2f6d0a71986432ff9cc9aca0df9ff4a8bed1d850f6/fonttools-4.64.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:5af87d1a6d247d7467ee082ae977a5443b2c45f8cd4d59375b6daa38d523c2de", size = 2582833, upload-time = "2026-08-31T15:43:58.348Z" }, + { url = "https://files.pythonhosted.org/packages/91/7e/e8aec0e6eaf93267450535c1be9c9ae63e1d9c49659c2ffdb52c19982114/fonttools-4.64.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:769fb64412ca237547ca73f111a64252d9e32c9d938bed51ed537bc9146a8f54", size = 5375327, upload-time = "2026-08-31T15:44:00.417Z" }, + { url = "https://files.pythonhosted.org/packages/61/d1/fa9ce5c1c3c0ef858a2cbe5632e9b6b137deca010b28d5d9a677e4aa89a7/fonttools-4.64.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e662f874ab2c7da9861584db44a13573e0936df087215f63013138f6e5eba083", size = 5338647, upload-time = "2026-08-31T15:44:02.62Z" }, + { url = "https://files.pythonhosted.org/packages/0d/13/e4d4fa3c166f7f5a8f9b6403ad9dcb3887cd05f8b9d2638bd2b43d2889ef/fonttools-4.64.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:2524a26f8fdb9051b0d778d052f5d238285ca9f91a7dc004514c7d6cf38d35f4", size = 5313557, upload-time = "2026-08-31T15:44:05.037Z" }, + { url = "https://files.pythonhosted.org/packages/91/c8/5a352d69608ea7606f677080ede971090fc7340b9f143c0b4fa00cfbf63e/fonttools-4.64.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:1e4e84b47839d35be24dbf476845a34f2ccf99707b66df125c1c414d3e86d25d", size = 5462363, upload-time = "2026-08-31T15:44:07.13Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f1/b15b845f66b559a24a9c40859391bbd3048af3ac0fd04e45b57dca11c4c7/fonttools-4.64.0-cp315-cp315-win32.whl", hash = "sha256:be084d19a3ac0c8b2aba696680642d703118d3b1f18cf83f5b7dbaf0ffc62ab6", size = 2431682, upload-time = "2026-08-31T15:44:09.293Z" }, + { url = "https://files.pythonhosted.org/packages/c6/b2/909beff0d2e2e1adac896e36cff7f9d76fd154bcd57e303d8f09aee8f72b/fonttools-4.64.0-cp315-cp315-win_amd64.whl", hash = "sha256:de8acaa5f4160f537a3cf41b031171d51004b9f4aebfa6c194f18dffa9533d03", size = 2484393, upload-time = "2026-08-31T15:44:11.117Z" }, + { url = "https://files.pythonhosted.org/packages/40/47/06a51becf651cc071daa88e18ecb9f45ace9e3a570d8191ed8b3cae353fa/fonttools-4.64.0-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:5b90ad6637237b636d15c9ae8b7c4a7a1c194f33def378677e468c13fd4542f8", size = 3162235, upload-time = "2026-08-31T15:44:13.182Z" }, + { url = "https://files.pythonhosted.org/packages/dd/96/8b3faf58fd7ec3bb11943126658ad15b3f385068b23248deffccf2327ac1/fonttools-4.64.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:fa75c7970bc6bca340cc6e20f20f069201bfcb50094c31a536fd99724d1d01ca", size = 2613507, upload-time = "2026-08-31T15:44:15.123Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3a/223d1437e72d79f1549be7bafb3cc08f397e1d9c354a241a8ec8573b7d33/fonttools-4.64.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:236e59bc7e2a63557a4d7b013f9cb9e28d9aebc45bc09f85e545e6bf091db626", size = 5518005, upload-time = "2026-08-31T15:44:17.359Z" }, + { url = "https://files.pythonhosted.org/packages/9d/9f/6f1a4b40ae533b9e907b7e627e397be632d0d9fd6f6be7756cf458870279/fonttools-4.64.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a515f664cad988f2295056833a59f62220bc3e46afdaffe389a29060f6712355", size = 5341654, upload-time = "2026-08-31T15:44:19.869Z" }, + { url = "https://files.pythonhosted.org/packages/52/f6/9ca30ba98730a22b527bcc1e02034f0b13b3be2ede764bb0fe8b8e0408d6/fonttools-4.64.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:5bfdaada437e7730c17d366bd7bb8c4a16639963ddbfc1b2f302a68a17a290e7", size = 5385994, upload-time = "2026-08-31T15:44:21.979Z" }, + { url = "https://files.pythonhosted.org/packages/09/27/baf1b61bff983bfec72cbb7b32162c4b68d76dc824aaaa48a1c26fa10b6b/fonttools-4.64.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:c60be0aed97a32c6ba8cee21f0d0477136e495451bd97910f589ac892db120d4", size = 5432407, upload-time = "2026-08-31T15:44:24.469Z" }, + { url = "https://files.pythonhosted.org/packages/21/ff/2eab37d43f2e2ccc0993959d8f2605d9a68470082c592684ac98611ee70a/fonttools-4.64.0-cp315-cp315t-win32.whl", hash = "sha256:f8669ce37851b597d3435b91fefa51139e58d506ca449ca0e5bb68c63b8b6d2b", size = 2463614, upload-time = "2026-08-31T15:44:27.198Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8d/8bdff3ea656592197c8ef5062774221885468ffa01d8b0dc782dae23a83a/fonttools-4.64.0-cp315-cp315t-win_amd64.whl", hash = "sha256:89356c0793b474af7e49ec90d39fb2363e2341516a90460e38231df5ebe8acd5", size = 2512422, upload-time = "2026-08-31T15:44:29.478Z" }, + { url = "https://files.pythonhosted.org/packages/82/f8/7188153c4b265c899cd035de6a062677d51f67118a4ba640902bd9683e90/fonttools-4.64.0-py3-none-any.whl", hash = "sha256:4a05783ff54ce4c7a28f18e5772efdf63c219374bd9ffc55452182e1cef8be60", size = 1195327, upload-time = "2026-08-31T15:44:31.741Z" }, ] [[package]] @@ -1474,7 +1480,7 @@ requires-dist = [ { name = "numpy", marker = "python_full_version >= '3.12'", specifier = ">=1.26" }, { name = "numpy", marker = "python_full_version >= '3.13'", specifier = ">=2.1" }, { name = "numpy", marker = "python_full_version >= '3.14'", specifier = ">=2.3.2" }, - { name = "qiskit", extras = ["qasm3-import"], specifier = ">=2.1" }, + { name = "qiskit", extras = ["qasm3-import"], specifier = ">=2.4.0" }, { name = "scikit-learn", specifier = ">=1.5.2" }, { name = "scikit-learn", marker = "python_full_version >= '3.14'", specifier = ">=1.7.2" }, ] @@ -1485,29 +1491,29 @@ dev = [ { name = "prek", specifier = ">=0.2.27" }, { name = "pytest", specifier = ">=9.0.1" }, { name = "pytest-console-scripts", specifier = ">=1.4.1" }, - { name = "pytest-cov", specifier = ">=7.1" }, + { name = "pytest-cov", specifier = ">=7.1.0" }, { name = "pytest-sugar", specifier = ">=1.1.1" }, - { name = "pytest-xdist", specifier = ">=3.8" }, + { name = "pytest-xdist", specifier = ">=3.8.0" }, ] docs = [ - { name = "furo", specifier = ">=2025.12.19" }, - { name = "myst-nb", specifier = ">=1.4" }, - { name = "openqasm-pygments", specifier = ">=0.2" }, - { name = "qiskit", extras = ["visualization"], specifier = ">=2.1" }, - { name = "sphinx", specifier = ">=9" }, - { name = "sphinx-autoapi", specifier = ">=3.6" }, + { name = "furo", specifier = ">=2025.9.25" }, + { name = "myst-nb", specifier = ">=1.3.0" }, + { name = "openqasm-pygments", specifier = ">=0.2.0" }, + { name = "qiskit", extras = ["visualization"], specifier = ">=1.2.1" }, + { name = "sphinx", specifier = ">=8.2.3" }, + { name = "sphinx-autoapi", specifier = ">=3.6.0" }, { name = "sphinx-copybutton", specifier = ">=0.5.2" }, - { name = "sphinx-design", specifier = ">=0.7" }, - { name = "sphinx-llm", specifier = ">=1" }, - { name = "sphinxcontrib-bibtex", specifier = ">=2.7" }, - { name = "sphinxext-opengraph", specifier = ">=0.13" }, + { name = "sphinx-design", specifier = ">=0.6.1" }, + { name = "sphinx-llm", specifier = ">=0.4.1" }, + { name = "sphinxcontrib-bibtex", specifier = ">=2.6.5" }, + { name = "sphinxext-opengraph", specifier = ">=0.13.0" }, ] test = [ { name = "pytest", specifier = ">=9.0.1" }, { name = "pytest-console-scripts", specifier = ">=1.4.1" }, - { name = "pytest-cov", specifier = ">=7.1" }, + { name = "pytest-cov", specifier = ">=7.1.0" }, { name = "pytest-sugar", specifier = ">=1.1.1" }, - { name = "pytest-xdist", specifier = ">=3.8" }, + { name = "pytest-xdist", specifier = ">=3.8.0" }, ] [[package]] @@ -1552,11 +1558,11 @@ wheels = [ [[package]] name = "narwhals" -version = "2.26.0" +version = "2.25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/35/68/5351e34623d253423240ea7de3f8fc74fa8ab14b1ab3c0ec4ac8997413c9/narwhals-2.26.0.tar.gz", hash = "sha256:6b9cadca82f375c7e4cf584fdc86ca25da54827307a9c58f94547ee6104b82dd", size = 686970, upload-time = "2026-09-08T13:32:08.964Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/7b/6248dada39781db1ab3ebf08943080df0796098515a87f6f8696d14ec744/narwhals-2.25.0.tar.gz", hash = "sha256:62c036c810662bf7820b7737077176313bc59350eeeefb808510f388c743e4b2", size = 677076, upload-time = "2026-08-20T18:10:15.454Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/b5/1b84b2c784db76d69442334bc8b8748c840f13ca53be086f4f250ad4a0bc/narwhals-2.26.0-py3-none-any.whl", hash = "sha256:29326d74f107c347fd1009bd58e38d9f7c7c5b51e6de97bc93dbc325d9038b54", size = 474034, upload-time = "2026-09-08T13:32:07.159Z" }, + { url = "https://files.pythonhosted.org/packages/eb/dc/55481808fd70ef1567cf13540ffd4702af3f74b112e35427564b03f79c2d/narwhals-2.25.0-py3-none-any.whl", hash = "sha256:1f0f403e8c7e4463cde9bfe78b12fdd809e3ae3dda6d9b2f802934fb9c7a6a8f", size = 467373, upload-time = "2026-08-20T18:10:13.834Z" }, ] [[package]] @@ -1996,11 +2002,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.11.8" +version = "4.11.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/18/f3bb8ef0d3b930692343da8aa4d3cbcd6749477c053959395ac81965a6e9/platformdirs-4.11.8.tar.gz", hash = "sha256:f23abafea7dd4276d1f29104b83598d7dcc567cafd07c9c951e66665645437fc", size = 37182, upload-time = "2026-09-08T22:20:42.866Z" } +sdist = { url = "https://files.pythonhosted.org/packages/69/b7/802a56eca9f2fac455b8bab5375a2647b0f0e14a2cd63ef077de3c4a7658/platformdirs-4.11.7.tar.gz", hash = "sha256:4f41487eeeeeb07f3a6625e61d9bc0ae6809f92d3386dbd74392fbb76108104d", size = 35127, upload-time = "2026-09-01T13:35:10.502Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/e1/5b7b8bbb55084d1425bcb9bc823ff519e1b2be05f6ebb0089e2eacc38413/platformdirs-4.11.8-py3-none-any.whl", hash = "sha256:52f2f181bbfde907966932cc8312d967d02976422d66d537ea16092b8e291081", size = 24027, upload-time = "2026-09-08T22:20:41.537Z" }, + { url = "https://files.pythonhosted.org/packages/27/6e/80993e10a0482f630cef528635789233224f36b1ffd11592aa15d13ff9ce/platformdirs-4.11.7-py3-none-any.whl", hash = "sha256:8a02cb259042c79d1cd0450facc2fe6dc9d303ae7901afbe33bf8ea0b188cef6", size = 23938, upload-time = "2026-09-01T13:35:09.02Z" }, ] [[package]] @@ -2079,11 +2085,11 @@ wheels = [ [[package]] name = "pure-eval" -version = "0.2.4" +version = "0.2.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/da/9f/abfd2959e9261dd5217ca8551d4de211ca6ab26fe9b72cf44731ff6c4442/pure_eval-0.2.4.tar.gz", hash = "sha256:260c2774686e651b79f8b8e7fc9d80b3599ea6a66334b47d5f4abb69fc2c0ea1", size = 20563, upload-time = "2026-09-10T21:41:22.836Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/18/83376915176eb058cb86470eb7396388a13350b09a8f233a79303bbcbc5b/pure_eval-0.2.4-py3-none-any.whl", hash = "sha256:96cae060a313cfaad51bb761278bfb0e62dc0248d9315a81173752dc546cd37a", size = 11893, upload-time = "2026-09-10T21:41:21.532Z" }, + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, ] [[package]] @@ -2615,7 +2621,7 @@ wheels = [ [[package]] name = "scikit-learn" -version = "1.9.1" +version = "1.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "joblib" }, @@ -2626,50 +2632,38 @@ dependencies = [ { name = "scipy", version = "1.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "threadpoolctl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d2/eb/eaf5e07fcc0da7149b0e084f24e54edd7441b9a89ce7e034032ae97fe3a0/scikit_learn-1.9.1.tar.gz", hash = "sha256:629cada3e33e2b9bf376cdc7614a47a4140b8aedc1d836579e359736fbd82977", size = 7786908, upload-time = "2026-09-10T18:34:04.679Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/67/1904bf57ab9ea77b5ba3899178bcb35ff8d814fc38ef6d1283bcddeaf04a/scikit_learn-1.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:326c188f92084bf58664229f4578eeab6176313b37cd5dfc85abd92b94130c58", size = 8823771, upload-time = "2026-09-10T18:32:29.891Z" }, - { url = "https://files.pythonhosted.org/packages/23/31/dc50d8780ac647bed9417f10a8b15ae3cf990ad7edb4f4b468f35030f8c5/scikit_learn-1.9.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b48b2b5b41d9c5fbafef5f37b042f61110df3318ad2a45baf57287ea5b9ba5a2", size = 8290892, upload-time = "2026-09-10T18:32:32.441Z" }, - { url = "https://files.pythonhosted.org/packages/a1/aa/a7a61517eaaa8acbf6061d255a625034e4afa277caff47e829574cc21d1c/scikit_learn-1.9.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4298fcc01b3d8fa9768d36894e99cce0747b3b2dd73bfd80779e393769d0afab", size = 9026402, upload-time = "2026-09-10T18:32:34.482Z" }, - { url = "https://files.pythonhosted.org/packages/69/46/f8b95a7a16fd13b458401d5e85a63d88ecb3679f3649812d5fdbea4c8357/scikit_learn-1.9.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:52a0703bbc07ad27f560fa63fa68e4c54dd735bfbbf65b4dd3c225dc7547b6df", size = 9315422, upload-time = "2026-09-10T18:32:36.581Z" }, - { url = "https://files.pythonhosted.org/packages/29/8a/8d1e888fe3d0dd4e2a1f3356dad703eed569aaf673e222b6f647005ad30f/scikit_learn-1.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:220fa18152852a5ce29c49e1eaba9d44ec44631cd2e5cf65f5a40eafa5ab3412", size = 8329877, upload-time = "2026-09-10T18:32:38.729Z" }, - { url = "https://files.pythonhosted.org/packages/97/44/8f43f8de3e025c505efbd88b9264008f39490fbaf15a5ca8dbd8dae0cb74/scikit_learn-1.9.1-cp311-cp311-win_arm64.whl", hash = "sha256:8218cb8938d3031e0d23842838425490c229ecf3e99f666776e09d12ed902352", size = 7954229, upload-time = "2026-09-10T18:32:40.661Z" }, - { url = "https://files.pythonhosted.org/packages/df/a7/25f0a43d2fde306e8ef45f45121192f687b79beaf4bae8c21607c46c5e63/scikit_learn-1.9.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0c0f8b5d09b44101cea2767f300680bada1ea27f976fe4b48b83950a4f55a49a", size = 8775209, upload-time = "2026-09-10T18:32:42.804Z" }, - { url = "https://files.pythonhosted.org/packages/60/ea/57e57539ce175d774fc291ed091b0a6d756854b92cd92554c6bb4d0ae498/scikit_learn-1.9.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:8c14ce41d561f7749f990b41d6703fe02c4669fbc485e598e069e0a1967b488e", size = 8295541, upload-time = "2026-09-10T18:32:45.069Z" }, - { url = "https://files.pythonhosted.org/packages/78/2b/5721a174406bfba49bce20ae997b3b64cf355c3f623a2638284ab6a82156/scikit_learn-1.9.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4c20a6c017d820faa7ac8c783e3d0c6a9a2e297bf9f55332ca17cdf7fd4d04d", size = 8871592, upload-time = "2026-09-10T18:32:46.999Z" }, - { url = "https://files.pythonhosted.org/packages/8e/57/a50162f3d29feb979ab6347c6debda506dfb525bcff3c50dd17606651c7e/scikit_learn-1.9.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e5d7b18a5b9dca241a74695f3275fa4c895a9dadc72b3d8df5fa9d1083c9b83e", size = 9166043, upload-time = "2026-09-10T18:32:49.343Z" }, - { url = "https://files.pythonhosted.org/packages/72/8d/27c054166bac671770d1ea0ef7716134fe8119c0df224a58c89fd735a61c/scikit_learn-1.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:4b59abb30618121cc46b45972d6bf53a7128b4df4cd346c6ca6f4d5f9031e49c", size = 8262238, upload-time = "2026-09-10T18:32:51.679Z" }, - { url = "https://files.pythonhosted.org/packages/9d/d6/493086006ea0c68ad62c40a8dece1961b61bf503f45400f133d47f56e5be/scikit_learn-1.9.1-cp312-cp312-win_arm64.whl", hash = "sha256:d5945a2908be62350e2978344e62b56c1552c2ca4f844ebf6277c94944d647dd", size = 7902102, upload-time = "2026-09-10T18:32:53.67Z" }, - { url = "https://files.pythonhosted.org/packages/bb/8d/b60d5e7354ff0ff5cc9400e60273696589d87a30b8b2235886a76d80d062/scikit_learn-1.9.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c2b312fd8c02951a364fa120ea08c1cec10d863466bf1701b013152d7537835", size = 8739300, upload-time = "2026-09-10T18:32:55.483Z" }, - { url = "https://files.pythonhosted.org/packages/2f/81/3c6392c03665d2899457a76e535a9a6f597dddddf3220fd2e1d790da88c5/scikit_learn-1.9.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:61cd968ab831a76d0ecbaf0347ab2270268716da28f94fd022497e3d6f205f13", size = 8262364, upload-time = "2026-09-10T18:32:57.966Z" }, - { url = "https://files.pythonhosted.org/packages/0f/35/a15b8653499692879821301d48059376d6e68e8b65cd0f22d19b6ee83cd9/scikit_learn-1.9.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5990f9c69e431bfaddcde1a6d7c5355243e026bc9b9e560c13893b90dab53fb4", size = 8823698, upload-time = "2026-09-10T18:33:00.632Z" }, - { url = "https://files.pythonhosted.org/packages/23/e5/688703d357e5393f708d98eb189fd415ae69e39f6de03c6bd4005aef6118/scikit_learn-1.9.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:55e79d6e9b0923f1a978179822bd43d7f5543f45e970a00fe861f43486380aba", size = 9121732, upload-time = "2026-09-10T18:33:02.825Z" }, - { url = "https://files.pythonhosted.org/packages/96/45/a10add34c08184d373be9384660c75758128ca881ed27b503b6f6a742478/scikit_learn-1.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:2070f271e5375dc42c6bb93b461ab1c0aa5841d4009267e0cfd95a39dca94a43", size = 8237244, upload-time = "2026-09-10T18:33:05.26Z" }, - { url = "https://files.pythonhosted.org/packages/9e/08/7a89bcdadd1fff0d464d01056417b646c9abcbc54f7297a0a1203bba5ebb/scikit_learn-1.9.1-cp313-cp313-win_arm64.whl", hash = "sha256:613f0a783ca05aa844a4e1ac42d48425058f2c52be73f40f8cd98b7cd111acd6", size = 7875398, upload-time = "2026-09-10T18:33:07.506Z" }, - { url = "https://files.pythonhosted.org/packages/64/e3/b58e45082dcf3dcf0eb1192ee03545ec43d8c98441dfe20e88afca8442ce/scikit_learn-1.9.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d5d117952769b563067656784e03c75a2d8235a7a05cf7fffa78a311e75aac08", size = 8747414, upload-time = "2026-09-10T18:33:10.698Z" }, - { url = "https://files.pythonhosted.org/packages/9b/ed/d68115577c8b42b0442ebd8180945d4008880a33176094640e00b585128e/scikit_learn-1.9.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:8893bc6331f60f18d4ac75e12ed356e2dcf6a564bf767918b5b7ca54c8c8be49", size = 8279175, upload-time = "2026-09-10T18:33:12.846Z" }, - { url = "https://files.pythonhosted.org/packages/c4/6c/06c7eb61a438e389cbf3f7210897069bec5a883dbe03c189ae792781e11a/scikit_learn-1.9.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5492cf2df5226691c32611de8734bcf42148c6547ae53c7f4e6b847793addc0", size = 8881706, upload-time = "2026-09-10T18:33:14.741Z" }, - { url = "https://files.pythonhosted.org/packages/86/4e/0bab75490ca4b85fad8388739c7ebc71d9db553f8c69e39943ee8db0aaae/scikit_learn-1.9.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993d332ff80e62efae9e39603b7e872297c418d780f01a01855269a3489c950f", size = 9152030, upload-time = "2026-09-10T18:33:17.436Z" }, - { url = "https://files.pythonhosted.org/packages/9a/13/31c6f8ba1b7eecef9dd9558576c752d2ec5785fd0e456bb9fc59305be23f/scikit_learn-1.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:ca9051447455dae341d4d591eece7deb2d8e3d1020298fc87a81fc51e4da8f53", size = 8377024, upload-time = "2026-09-10T18:33:19.941Z" }, - { url = "https://files.pythonhosted.org/packages/62/e6/6d3cb8a45f5228f915acd66b819dd6b8232ccbe24532f51d278e3991df31/scikit_learn-1.9.1-cp314-cp314-win_arm64.whl", hash = "sha256:90de6573f733a9fb79476ff1371af52a397d41c8b35f9146e20923db010d67b6", size = 8014196, upload-time = "2026-09-10T18:33:22.126Z" }, - { url = "https://files.pythonhosted.org/packages/66/6e/6befb2d5961490d18d9dbc16a5df37aa121d08bc9893316a6a363977a903/scikit_learn-1.9.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7b5cad1624de8b75e5b9ccb7b0ce1ff1d01306340a3efc56d5529c5ba92392eb", size = 9066066, upload-time = "2026-09-10T18:33:24.396Z" }, - { url = "https://files.pythonhosted.org/packages/cb/18/11271f2f7db337db01f598e358721b1e83989407131272e5dd64214c28a8/scikit_learn-1.9.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:d137ce8a6142029fb5c35bd82f470c40cd9e760e5e2f7694b362c497c4ab3fa2", size = 8647654, upload-time = "2026-09-10T18:33:26.649Z" }, - { url = "https://files.pythonhosted.org/packages/e7/04/9c15d201e1b6a2e81b8215865df7646c5a360f560831769c8dc92ac1ab9a/scikit_learn-1.9.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:66f852f7325b5070bc28329005aca76055a2def78faac039548ae889aeaa45a6", size = 8889070, upload-time = "2026-09-10T18:33:28.95Z" }, - { url = "https://files.pythonhosted.org/packages/1a/5a/4cb6c85160af4a639e87a3b7bf8b1c25cfc3b504c5af710ca416a6dcfc5f/scikit_learn-1.9.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:748bcb0a4cc04aec470652c9e5ec68450948e867387e7dfade647107ade68d25", size = 9131227, upload-time = "2026-09-10T18:33:31.008Z" }, - { url = "https://files.pythonhosted.org/packages/00/0e/361440972ae3d19b90ea88a84791138a51de0e8432a770ba741b2c8d9ced/scikit_learn-1.9.1-cp314-cp314t-win_amd64.whl", hash = "sha256:38cd925e893e5539be704d5edc64dbe081aacdab6b89d8c2977c1f6a7a453ce5", size = 8683001, upload-time = "2026-09-10T18:33:33.188Z" }, - { url = "https://files.pythonhosted.org/packages/e2/8f/a9f405c5c0e2df6f343a871b40c97fb32969e3ccc38e3033dd118f3c261e/scikit_learn-1.9.1-cp314-cp314t-win_arm64.whl", hash = "sha256:b01e5b01735d38474127ca3f49319b592506225a87793b27559816b5c75cea39", size = 8257996, upload-time = "2026-09-10T18:33:35.343Z" }, - { url = "https://files.pythonhosted.org/packages/e5/c5/74a83ea39cef7cd07f53e06cc1cf51e79f35df74f81db956835d59ec34b1/scikit_learn-1.9.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:8dec64f31a6e0ec826aca6c1b39a51e16d946e400d4c0904316f3ca72ccfb825", size = 8747991, upload-time = "2026-09-10T18:33:37.4Z" }, - { url = "https://files.pythonhosted.org/packages/64/c9/cc93e8a7fe204e43d70e96e5eb89871643be20025eea05eb4fdaf19afe39/scikit_learn-1.9.1-cp315-cp315-macosx_12_0_arm64.whl", hash = "sha256:e1b468241f4a7a9a7a0d6479ad3cc47681cc151a4046c530f2777c3d68f08942", size = 8279340, upload-time = "2026-09-10T18:33:39.705Z" }, - { url = "https://files.pythonhosted.org/packages/a2/61/0c6080f0d356fb966053009e7f25e9bff6cf74b0b16195ccf0c3757d1ae8/scikit_learn-1.9.1-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ca869d0080a5723cde2d5a8b54a2da1ff7e68735a9e9adb3da1243183a0fa01", size = 8881917, upload-time = "2026-09-10T18:33:41.912Z" }, - { url = "https://files.pythonhosted.org/packages/47/bb/98a31f10fffbd39edcc2f8bf4119b29652248bb110b7d45c84e68aa293ab/scikit_learn-1.9.1-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6754b7cabfc3df0b1f7b38f7a344f559bbae9d82f0ac5e3d48cccbd19fdcefdf", size = 9152805, upload-time = "2026-09-10T18:33:44.23Z" }, - { url = "https://files.pythonhosted.org/packages/f6/48/858ceff52213cfd97c0a069362071756bcb70b9fac9771b38d87c4cf7f17/scikit_learn-1.9.1-cp315-cp315-win_amd64.whl", hash = "sha256:52cfdb1fed3a34362dbc0bd96f2e761a66fd5724d6901629f5a558f1f3bd9849", size = 8376719, upload-time = "2026-09-10T18:33:46.492Z" }, - { url = "https://files.pythonhosted.org/packages/2a/1e/5337a871bdea53effbd154b61429df048f2665653251de74a3bd8a6dea9e/scikit_learn-1.9.1-cp315-cp315-win_arm64.whl", hash = "sha256:ae6571a4828c6f5019bcd2b4125e5b18c0af3dbc9c99726c891f45f41335ec8e", size = 8014341, upload-time = "2026-09-10T18:33:48.762Z" }, - { url = "https://files.pythonhosted.org/packages/0e/35/150383a42d83ec4c7b39f9c50bd68408ecf04c19fc30ea5198fa42e67d9c/scikit_learn-1.9.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:48fefd8eb42bd4eec3e2d348149368ccd6d71987e20c30706a56a24eb86a6e73", size = 9059953, upload-time = "2026-09-10T18:33:50.951Z" }, - { url = "https://files.pythonhosted.org/packages/9f/dd/aa0d738808540f7eaacfab93e01982db8ef1c1c7473ef0ad38193e6aebd1/scikit_learn-1.9.1-cp315-cp315t-macosx_12_0_arm64.whl", hash = "sha256:09f4d73049cd63575157f6b1060e06a8c83a4bd3488dbfaeedf35ccba7aad712", size = 8646602, upload-time = "2026-09-10T18:33:53.23Z" }, - { url = "https://files.pythonhosted.org/packages/7e/cc/687ae4214c2f598906c3b9fa5f86fbaf834b35e20360625528ff1b713f06/scikit_learn-1.9.1-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3da53831534214322d9cb240fa6f390b36cf69eba727a6d4bd3238677630d70", size = 8880366, upload-time = "2026-09-10T18:33:55.764Z" }, - { url = "https://files.pythonhosted.org/packages/c2/03/82215cb78ad1c513a4498777571fb28444621ad26ef636287551767b7732/scikit_learn-1.9.1-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caae15634feceafa2612566b109a3082d3293167fac388eedaf77bff66b51983", size = 9129640, upload-time = "2026-09-10T18:33:58.186Z" }, - { url = "https://files.pythonhosted.org/packages/d4/90/4b7af4efd7909a4a0524a9f18457e4eb2eb60616eff2c7627eda8e3cdceb/scikit_learn-1.9.1-cp315-cp315t-win_amd64.whl", hash = "sha256:ffbcbbbb44202fbe9bc64bced25a145759adb9ef010b3d37a8064958ac13df2a", size = 8678083, upload-time = "2026-09-10T18:34:00.354Z" }, - { url = "https://files.pythonhosted.org/packages/31/27/068e484d4b83004302e0d9cfc1faca69bcc010d76fbb66a642446095af1b/scikit_learn-1.9.1-cp315-cp315t-win_arm64.whl", hash = "sha256:800dd22dd87fe97dcea484c24e85dd93cf1734d86bd74e668ad18f7967f4d1b5", size = 8259434, upload-time = "2026-09-10T18:34:02.678Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/fa/6f/37092bdb25f712817231799fc5674d8e704066a8a70c1d2d40517e18b4ab/scikit_learn-1.9.0.tar.gz", hash = "sha256:8833266989d3a5110178a9fae30783675460724d0e1efb13b14901d2c660c557", size = 7750767, upload-time = "2026-06-02T11:54:32.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/be/e844fd9586e66540a15b71924d17a6cbc1bb749e81ddd0a796bcdba4c055/scikit_learn-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9db6f4d34e68c8899e4cab27fdf8eafe6ed21f2ba52ceb25ea250cd237f8e47b", size = 8789686, upload-time = "2026-06-02T11:53:05.439Z" }, + { url = "https://files.pythonhosted.org/packages/42/e2/ff880f62677a17d035817d543cb0fc8727d01eccbee81c5f7fc733a9d856/scikit_learn-1.9.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:f401448645a3e7bc115aa3c094097865155b34bff1cba8101857d9104e99074c", size = 8256782, upload-time = "2026-06-02T11:53:08.904Z" }, + { url = "https://files.pythonhosted.org/packages/25/64/eb40435e1a508ab1b4e284ce43ae80f6a162e5be5e38ed5a6fab467a9ea4/scikit_learn-1.9.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd3a8ef0c758555a3b23c03adaa858af32f7736785ded50ad5991f59c4ed03fa", size = 8992419, upload-time = "2026-06-02T11:53:11.551Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/4810a28e473185429e45a57eebcc91fc991b33d889cc0676063e671db03d/scikit_learn-1.9.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7e254636164090da847715a27f8e5478feb98c40a9e0ee90cbd277de9e5ceb8", size = 9281411, upload-time = "2026-06-02T11:53:15.063Z" }, + { url = "https://files.pythonhosted.org/packages/3b/67/be3d369f40d8178ba3bd86635d132e08cb5329b023e4669d9426d84bc007/scikit_learn-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:5dc1818c77575d149e25fce9ef82dd7b7263ae372f03494158668ad632a69759", size = 8272736, upload-time = "2026-06-02T11:53:18.108Z" }, + { url = "https://files.pythonhosted.org/packages/37/79/a733f02dc2118da7e77a134b34f39f40201a353311b011d20859d2db3556/scikit_learn-1.9.0-cp311-cp311-win_arm64.whl", hash = "sha256:366652351f092b219c248f1e72821e841960a63d8f358f1dcfd54dc1cbdbbc28", size = 7919564, upload-time = "2026-06-02T11:53:21.2Z" }, + { url = "https://files.pythonhosted.org/packages/ac/20/75f915ff375d6249e6550ac740fdbbd66159a068fd3af1400ff62036b07a/scikit_learn-1.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2bd41b0d201bc81575531b96b713d3eb5e5f50fb0b82101ff0f92294fdc236ac", size = 8741122, upload-time = "2026-06-02T11:53:24.08Z" }, + { url = "https://files.pythonhosted.org/packages/cc/d5/2b5148f2279196775e1db2aeb85d14b70ac80e7e32b3b28e7ebeafb0901d/scikit_learn-1.9.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:5be45aa4a42a68a533913a6ed736cf309de2226411c79ef8d609a5456f1939b1", size = 8261512, upload-time = "2026-06-02T11:53:27.183Z" }, + { url = "https://files.pythonhosted.org/packages/a0/ee/5adbc77656b71f9456a2f5a7a9fdb4bcf9207a6b962889f1c2f9323afa4e/scikit_learn-1.9.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e50ed4da51974e86e940690e9a3d82e729b62b5a49f7c9bac534d515d39d86f", size = 8837603, upload-time = "2026-06-02T11:53:30.328Z" }, + { url = "https://files.pythonhosted.org/packages/6c/c2/63fdda36c56437eeb44aaf9493c8bcd62ce230ab1598924fc626ffbfa943/scikit_learn-1.9.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:056c92bb67ad4c28463c2f2653d9701449201e7e7a9e94e321be0f71c4fef2b8", size = 9132097, upload-time = "2026-06-02T11:53:33.456Z" }, + { url = "https://files.pythonhosted.org/packages/83/a4/c8e67227c680e2259c8864ae72ff48b06e16a6f51253a22167aa02a8aa4e/scikit_learn-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:4306775fad04cc4b472a1b15af1ae9cede1540fbfcc17fbce3767cd8dc7ae283", size = 8211173, upload-time = "2026-06-02T11:53:36.602Z" }, + { url = "https://files.pythonhosted.org/packages/cf/fd/3c0863792e98e67e9184aa4029288a175935eb65443afcd30d4f143450cf/scikit_learn-1.9.0-cp312-cp312-win_arm64.whl", hash = "sha256:26e22435f63bcdcf396b574273f29f13dd531f5ea035801f5be10ba1540a4e60", size = 7867451, upload-time = "2026-06-02T11:53:39.075Z" }, + { url = "https://files.pythonhosted.org/packages/3c/01/cf3310626b6d48d3e9be69a1223f9180360b5e6edb045f50fade723ce494/scikit_learn-1.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:80746d63bd4b6eaca54d36fe5feaf4d28bb38dc6f9470f81c7cad7c40155f119", size = 8705188, upload-time = "2026-06-02T11:53:41.964Z" }, + { url = "https://files.pythonhosted.org/packages/3e/04/5acd7ae280c5f93b6ac5ef6cdec14eef4c8d1cd91d85b3292989c94d96b1/scikit_learn-1.9.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5b934c45c252844a91d69fda3a34cff5e7307e1db10d77cb10a3980312c74713", size = 8228299, upload-time = "2026-06-02T11:53:44.817Z" }, + { url = "https://files.pythonhosted.org/packages/0c/39/ffe829a5b8ecb40a518724a997794657fdc354ada5e8fe8e64d998c0bac9/scikit_learn-1.9.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:38c3dcb9a1ffb85505ec53d54c7b4aea0cff70050425a7760c2af661ac85df05", size = 8789690, upload-time = "2026-06-02T11:53:47.461Z" }, + { url = "https://files.pythonhosted.org/packages/1f/88/8dab5de10c638c083772a6be83a3d8106ced492f74a928c8693638e5bb50/scikit_learn-1.9.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da76d09304a4706db7cc1e3ebaa3b6b98a67365cc11d2996c4f1e58ba47df714", size = 9087723, upload-time = "2026-06-02T11:53:50.702Z" }, + { url = "https://files.pythonhosted.org/packages/20/3f/7917ca72464038f6240ec70c29f94862d08a34a74291ae4d4ec5eb8186a0/scikit_learn-1.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:5808d98f15c6bf6d9d96d2348c1997392a5888ce7097e664105f930c4bca1277", size = 8184330, upload-time = "2026-06-02T11:53:53.396Z" }, + { url = "https://files.pythonhosted.org/packages/78/c7/15739eb2f61fda3c54639e9942414e5a19ad8a8d1f5a3266afad7cb7df80/scikit_learn-1.9.0-cp313-cp313-win_arm64.whl", hash = "sha256:d77f54c017633791bc0225a43e2f8d03745fdcfe4880268fcc4df15f505dec2e", size = 7840653, upload-time = "2026-06-02T11:53:56.035Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7d/c9a35cf59b20a86fec24d306f1547b78dec194b08d367ce2a3e4854169d9/scikit_learn-1.9.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9656acd4e93f74e0b66c8a36c88830a99252dfa900044d36bc2212ae89a47162", size = 8713289, upload-time = "2026-06-02T11:53:58.788Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a7/552a7821597c632b907f7bfe8f36f9f572777af8ef8a48353041cf8e091a/scikit_learn-1.9.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:24360002ae845e7866522b0a5bbf690802e7bc388cac8663502e78aa98598aa2", size = 8245141, upload-time = "2026-06-02T11:54:01.694Z" }, + { url = "https://files.pythonhosted.org/packages/7d/79/f4a0c4fe9711154cddabf913471153af79056382ddc612cfe5ee0ff4b72e/scikit_learn-1.9.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5162ad10a418c8a282dde04c9aa06965de3e9a65f33c1440c0ae69bb1a09d913", size = 8847671, upload-time = "2026-06-02T11:54:04.448Z" }, + { url = "https://files.pythonhosted.org/packages/f0/af/4d72d9e475ac83719160c662619e4bf7b95c19507cd582e7d0167a3c3dae/scikit_learn-1.9.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fea2cc5677ab49d6f5bade978c866da44957b712d92e9635e8b4f723013c3cb", size = 9118104, upload-time = "2026-06-02T11:54:07.205Z" }, + { url = "https://files.pythonhosted.org/packages/a2/d5/6a58eea2cb9abbb9b3f2bb8b2cfb3243d1152d69f442d256c7af71304769/scikit_learn-1.9.0-cp314-cp314-win_amd64.whl", hash = "sha256:64fa347efc1c839c487433e40c5144d38c336e8a2b59c81aa8660373945c2673", size = 8290674, upload-time = "2026-06-02T11:54:10.087Z" }, + { url = "https://files.pythonhosted.org/packages/65/5b/d4c879cf358f1187141cf90ced473f087183489090244f50c124a2ee478b/scikit_learn-1.9.0-cp314-cp314-win_arm64.whl", hash = "sha256:1b944b6db288f6b926e3650026ddafb988929de95d11fc2cc5fa117773c9ba42", size = 7978807, upload-time = "2026-06-02T11:54:12.769Z" }, + { url = "https://files.pythonhosted.org/packages/8a/43/bfae3121ec67ae09150d453c442c7c1cc166e9aefe056e6ab3b7728a5cfc/scikit_learn-1.9.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:4ccacf04ca5f4b492158a5f28afe0ace43f81b2571e4b9a66d34848b46128949", size = 9031941, upload-time = "2026-06-02T11:54:15.436Z" }, + { url = "https://files.pythonhosted.org/packages/75/b0/20a4546eb17f3b25d3c66df15810411c14ed5065bcfab50b53c96fb627b2/scikit_learn-1.9.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:ee1a8db2c18c08e34c7412d4b10be1cac214cd4ea7dc9715a6a327eb49a37c96", size = 8613528, upload-time = "2026-06-02T11:54:18.842Z" }, + { url = "https://files.pythonhosted.org/packages/18/3c/e440e039bb82cd19004edaaad00acbde0fb9b461083c3ecf37941c557312/scikit_learn-1.9.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:147e9329ef0e39f75d4cffa02b2aa48d827832684926cd5210d9a2cb5c57246b", size = 8855050, upload-time = "2026-06-02T11:54:21.699Z" }, + { url = "https://files.pythonhosted.org/packages/43/26/b341b8dab5998da6270a3a42c2152c578501354d36f944b5856757035ef8/scikit_learn-1.9.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bad8f8b9950321b54c965fdcbac6c6c55e79e16646b49977bcf3668d3870a1a", size = 9097190, upload-time = "2026-06-02T11:54:24.454Z" }, + { url = "https://files.pythonhosted.org/packages/fb/de/b650b4d69b84468cfa2e28a3ff7b8103743029e6446ce1a97fe060ef688c/scikit_learn-1.9.0-cp314-cp314t-win_amd64.whl", hash = "sha256:78fc56eafd4edb9575d2d8950d1dd152061abb573341a1cb7e099fc40f6c6666", size = 8963204, upload-time = "2026-06-02T11:54:27.428Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f3/ff83d76d7418112e5a61326443cdda87be3545dd8d6599c95b2481a4419e/scikit_learn-1.9.0-cp314-cp314t-win_arm64.whl", hash = "sha256:051075bda8b7aab87b1906ab3d4740a1e1224a19d7b3781a576736edc94e76aa", size = 8222661, upload-time = "2026-06-02T11:54:30.192Z" }, ] [[package]] @@ -3330,7 +3324,7 @@ wheels = [ [[package]] name = "virtualenv" -version = "21.7.9" +version = "21.7.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -3338,9 +3332,9 @@ dependencies = [ { name = "platformdirs" }, { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/52/5c/ba82fdd0da13ade01453fc08277a70abc79128101319549f55f2e13f8f83/virtualenv-21.7.9.tar.gz", hash = "sha256:a7e42d81d779dec8afd7dc4be71640fb959ea861bccfa5980cb4ad9f92e30675", size = 5348882, upload-time = "2026-09-09T01:03:27.752Z" } +sdist = { url = "https://files.pythonhosted.org/packages/08/1c/69faa2e6a83484e2a8227bce5cfaa183941c5720f99c48f204931d286b07/virtualenv-21.7.8.tar.gz", hash = "sha256:1dc49c790072a9072cb1803f9bd62aa69cd583077cada32390f75505cdc64c9b", size = 5347580, upload-time = "2026-09-01T13:36:13.894Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/fc/888c80b8da917bfc48a41887eca244f995d190bd3c01175727089fcaf27e/virtualenv-21.7.9-py3-none-any.whl", hash = "sha256:ba3b0bb41063c848d84d76a9fe3fb7711aaa0f2fe78708e8f3cd9714770e4eac", size = 5324667, upload-time = "2026-09-09T01:03:26.023Z" }, + { url = "https://files.pythonhosted.org/packages/9f/34/88d507d4a4030fa559788de9c690a214f9a4053aa1d91cfb60e9b36127c2/virtualenv-21.7.8-py3-none-any.whl", hash = "sha256:3040eb3cbf5d32b10ffd57d167e6a162237ad82ba7d8cf1400a1efed593d85ac", size = 5324617, upload-time = "2026-09-01T13:36:11.248Z" }, ] [[package]]