diff --git a/.agent/PLANS.md b/.agent/PLANS.md new file mode 100644 index 0000000000..7cf314d21d --- /dev/null +++ b/.agent/PLANS.md @@ -0,0 +1,274 @@ +# Codex Execution Plans (ExecPlans) + +This document describes the requirements for an execution plan ("ExecPlan"), a +design document that a coding agent can follow to deliver a working feature or +system change. Treat the reader as a complete beginner to this repository: they +have only the current working tree and the single ExecPlan file you provide. +There is no memory of prior plans and no external context. + +## How to use ExecPlans and PLANS.md + +When authoring an executable specification (ExecPlan), follow PLANS.md +_to the letter_. If it is not in your context, refresh your memory by reading +the entire PLANS.md file. Be thorough in reading (and re-reading) source +material to produce an accurate specification. When creating a spec, start from +the skeleton and flesh it out as you do your research. + +When implementing an executable specification (ExecPlan), do not prompt the user +for "next steps"; simply proceed to the next milestone. Keep all sections up to +date, add or split entries in the list at every stopping point to affirmatively +state the progress made and next steps. Resolve ambiguities autonomously, and +commit frequently. + +When discussing an executable specification (ExecPlan), record decisions in a +log in the spec for posterity; it should be unambiguously clear why any change +to the specification was made. ExecPlans are living documents, and it should +always be possible to restart from _only_ the ExecPlan and no other work. + +When researching a design with challenging requirements or significant unknowns, +use milestones to implement proof of concepts, "toy implementations", and +similar work that validates whether the proposal is feasible. Read relevant +library source code, research deeply, and include prototypes that guide a fuller +implementation. + +## Requirements + +NON-NEGOTIABLE REQUIREMENTS: + +- Every ExecPlan must be fully self-contained. Self-contained means that in its + current form it contains all knowledge and instructions needed for a novice to + succeed. +- Every ExecPlan is a living document. Contributors are required to revise it as + progress is made, discoveries occur, and design decisions are finalized. Each + revision must remain fully self-contained. +- Every ExecPlan must enable a complete novice to implement the feature + end-to-end without prior knowledge of this repository. +- Every ExecPlan must produce demonstrably working behavior, not merely code + changes that meet a definition. +- Every ExecPlan must define every term of art in plain language or not use it. + +Purpose and intent come first. Begin by explaining, in a few sentences, why the +work matters from a user's perspective: what someone can do after this change +that they could not do before, and how to see it working. Then guide the reader +through the exact steps to achieve that outcome, including what to edit, what to +run, and what they should observe. + +The agent executing the plan can list files, read files, search, run the +project, and run tests. It does not know prior context and cannot infer intent +from earlier milestones. Repeat every assumption the plan relies on. Do not +point to external blogs or documentation; embed required knowledge in the plan +in your own words. If an ExecPlan builds on a checked-in prior ExecPlan, +incorporate it by reference. Otherwise, include all relevant context from that +plan. + +## Formatting + +Format and envelope are simple and strict. Each ExecPlan must be one single +fenced code block labeled `md` that begins and ends with triple backticks. Do +not nest additional triple-backtick code fences inside it. Present commands, +transcripts, diffs, and code as indented text within that single fence instead. +Use indentation for clarity rather than code fences that could prematurely close +the ExecPlan. + +Use two newlines after every heading, use `#`, `##`, and deeper headings +correctly, and use correct ordered and unordered list syntax. When writing an +ExecPlan to a Markdown file whose content is only that single ExecPlan, omit the +outer triple backticks. + +Write in plain prose. Prefer sentences over lists. Avoid checklists, tables, and +long enumerations unless brevity would obscure meaning. Checklists are permitted +only in the `Progress` section, where they are mandatory. Narrative sections +must remain prose-first. + +## Guidelines + +Self-containment and plain language are paramount. If you introduce a phrase +that is not ordinary English, define it immediately and explain how it appears +in this repository by naming the files or commands where it occurs. Do not say +"as defined previously" or "according to the architecture document." Include the +needed explanation in the ExecPlan, even if that repeats information. + +Avoid common failure modes. Do not rely on undefined jargon. Do not describe a +feature so narrowly that the resulting code compiles but does nothing +meaningful. Do not outsource key decisions to the reader. When ambiguity exists, +resolve it in the plan and explain why. Err on the side of over-explaining +user-visible effects and under-specifying incidental implementation details. + +Anchor the plan with observable outcomes. State what the user can do after +implementation, the commands to run, and the outputs they should see. Acceptance +should be phrased as behavior a human can verify rather than internal +attributes. If a change is internal, explain how to demonstrate its impact, for +example with a test that fails before the change and passes after it or through +a small end-to-end scenario. + +Specify repository context explicitly. Name files with full repository-relative +paths, functions and modules precisely, and where new files belong. If touching +multiple areas, include a short orientation paragraph explaining how those parts +fit together. When running commands, show the working directory and exact +command line. State environment assumptions and reasonable alternatives. + +Be idempotent and safe. Write steps that can be repeated without damage or +drift. If a step can fail halfway, include how to retry or adapt. If a migration +or destructive operation is necessary, spell out backups or safe fallbacks. +Prefer additive, testable changes that can be validated as work proceeds. + +Validation is not optional. Include instructions to run tests, start the system +when applicable, and observe useful behavior. Describe comprehensive tests for +new capabilities. Include expected outputs and error messages so a novice can +distinguish success from failure. Where possible, prove the change beyond +compilation through an end-to-end scenario, CLI invocation, or equivalent. State +the project's exact test commands and how to interpret their results. + +Capture evidence. Put concise, focused transcripts, diffs, and logs inside the +ExecPlan when they prove success. Prefer file-scoped diffs or small excerpts to +large patches. + +## Milestones + +Milestones are narrative, not bureaucracy. Introduce each milestone with a short +paragraph describing its scope, what will exist at its end, the commands to run, +and the acceptance to observe. Keep the sequence readable as a story: goal, +work, result, proof. Progress and milestones are distinct: milestones tell the +story, while progress tracks granular work. Both must exist. + +Never abbreviate a milestone merely for brevity or omit details crucial to a +future implementation. Each milestone must be independently verifiable and +incrementally implement the overall goal. + +## Living plans and design decisions + +- ExecPlans are living documents. Record each key design decision and its + rationale in the `Decision Log`. +- Every ExecPlan must contain and maintain `Progress`, + `Surprises & Discoveries`, `Decision Log`, and `Outcomes & Retrospective` + sections. +- Capture unexpected behavior, performance tradeoffs, and bugs that shape the + approach in `Surprises & Discoveries`, with short evidence snippets such as + test output. +- If the implementation changes course, document why in the `Decision Log` and + reflect the implications in `Progress`. +- At the end of a major task or the complete plan, add an + `Outcomes & Retrospective` entry that records what was achieved, what remains, + and lessons learned. + +## Prototyping milestones and parallel implementations + +Explicit prototyping milestones are encouraged when they de-risk a larger +change. Keep prototypes additive and testable. Label them as prototyping, +describe how to run and observe them, and state the criteria for promotion or +discarding. + +Prefer additive changes followed by safe subtractions that keep tests passing. +Parallel implementations, such as an adapter alongside an older path during a +migration, are appropriate when they reduce risk or keep tests running. Explain +how to validate both paths and retire one safely. When several libraries or +feature areas are involved, consider independent spikes that prove each external +dependency has the required behavior in isolation. + +## MQT Core requirements + +In addition to the requirements above, an MQT Core ExecPlan must describe its +repository-relative scope and any coordination boundaries relevant to the task. +It must preserve unrelated user changes and never modify another task's +worktree. Do not record local filesystem paths, developer accounts, a specific +checkout location, or an ephemeral branch name: the checked-in plan must work +from any clone. Describe command locations as "from the repository root" and +refer to files by repository-relative path. + +It must follow the repository's `AGENTS.md` and `docs/ai_usage.md`, including +the rules for generated files, secrets, AI disclosure, authorization, and human +review. An ExecPlan does not itself authorize external GitHub actions. + +Use the exact Core commands relevant to the change. For example, name the +applicable CMake preset and focused C++ or MLIR test binary; for Python changes, +name the relevant `pytest` or Nox command; for documentation, include the Nox +documentation command. End with `uvx nox -s lint` unless an existing, documented +limitation prevents it, and record the limitation and its evidence. + +## Skeleton of a good ExecPlan + +## + +This ExecPlan is a living document. The sections `Progress`, +`Surprises & Discoveries`, `Decision Log`, and `Outcomes & Retrospective` must +be kept up to date as work proceeds. + +This ExecPlan must be maintained in accordance with `.agent/PLANS.md` from the +repository root. + +### Purpose / Big Picture + +Explain in a few sentences what someone gains after this change and how they can +see it working. State the user-visible behavior to enable. + +### Progress + +Use a checkbox list to summarize granular steps. Document every stopping point, +including partially completed work split into completed and remaining parts. +This section must always reflect the current state. + +- [x] (2026-01-01 00:00Z) Example completed step. +- [ ] Example incomplete step. +- [ ] Example partially completed step (completed: X; remaining: Y). + +Use timestamps to measure the rate of progress. + +### Surprises & Discoveries + +Document unexpected behavior, bugs, optimizations, or insights discovered during +implementation. Provide concise evidence. + +- Observation: … Evidence: … + +### Decision Log + +Record every decision made while working on the plan in this form: + +- Decision: … Rationale: … Date/Author: … + +### Outcomes & Retrospective + +Summarize outcomes, gaps, and lessons learned at major milestones or completion. +Compare the result against the original purpose. + +### Context and Orientation + +Describe the relevant current state as if the reader knows nothing. Name key +files and modules by full path. Define non-obvious terms. Do not refer to prior +plans. + +### Plan of Work + +Describe the sequence of edits and additions in prose. For each edit, name the +file, location, and exact change. Keep it concrete and minimal. + +### Concrete Steps + +State the exact commands and working directories. When a command generates +output, show a short expected transcript. Update this section as work proceeds. + +### Validation and Acceptance + +Describe how to exercise the system and what to observe. Phrase acceptance as +behavior with specific inputs and outputs. Name the relevant Core test commands +and what success looks like. + +### Idempotence and Recovery + +State which steps are repeatable. For risky steps, provide a safe retry or +rollback path. Keep the environment clean after completion. + +### Artifacts and Notes + +Include the most important transcripts, diffs, or snippets as indented examples. +Keep them concise and focused on evidence. + +### Interfaces and Dependencies + +Be prescriptive. Name the libraries, modules, and services to use and why. +Specify the types, interfaces, and function signatures that must exist at the +end of the milestone. Prefer stable names and repository-relative paths. + +When you revise an ExecPlan, ensure the change is reflected across all sections, +including the living-document sections, and add a note at the end describing +what changed and why. An ExecPlan must describe not only what to do, but why. diff --git a/.agent/clean-caches.sh b/.agent/clean-caches.sh new file mode 100755 index 0000000000..707aa4a21c --- /dev/null +++ b/.agent/clean-caches.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env sh +# Copyright (c) 2026 Chair for Design Automation, TUM +# Copyright (c) 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +# Clear only the mutable tool state belonging to this worktree. +set -eu + +script_directory=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +repository_root=$(CDPATH= cd -- "${script_directory}/.." && pwd) +cache_root="${repository_root}/.cache" +uv_cache="${cache_root}/uv" +uv_python="${cache_root}/uv-python" +uv_tool_bin="${cache_root}/uv-bin" +uv_tools="${cache_root}/uv-tools" +ccache_cache="${cache_root}/ccache" +sccache_cache="${cache_root}/sccache" +sccache_socket="${cache_root}/sccache.sock" + +if [ -d "${uv_cache}" ]; then + if command -v uv >/dev/null 2>&1; then + UV_CACHE_DIR="${uv_cache}" uv cache clean + else + rm -rf -- "${uv_cache}" + fi +fi + +rm -rf -- "${uv_python}" "${uv_tool_bin}" "${uv_tools}" + +if [ -d "${ccache_cache}" ]; then + if command -v ccache >/dev/null 2>&1; then + CCACHE_DIR="${ccache_cache}" ccache --clear + else + rm -rf -- "${ccache_cache}" + fi +fi + +if [ -S "${sccache_socket}" ]; then + SCCACHE_DIR="${sccache_cache}" SCCACHE_SERVER_UDS="${sccache_socket}" \ + sccache --stop-server +fi + +rm -rf -- "${cache_root}" + +echo "Cleared worktree-local caches under ${cache_root}" diff --git a/.agent/run.sh b/.agent/run.sh new file mode 100755 index 0000000000..ea375b1224 --- /dev/null +++ b/.agent/run.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env sh +# Copyright (c) 2026 Chair for Design Automation, TUM +# Copyright (c) 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +# Run a command with all mutable tool state kept inside this worktree. +set -eu + +script_directory=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +repository_root=$(CDPATH= cd -- "${script_directory}/.." && pwd) +cache_root="${repository_root}/.cache" + +export XDG_CACHE_HOME="${cache_root}/xdg" +export PREK_HOME="${cache_root}/prek" +export UV_CACHE_DIR="${cache_root}/uv" +export UV_PYTHON_INSTALL_DIR="${cache_root}/uv-python" +export UV_TOOL_BIN_DIR="${cache_root}/uv-bin" +export UV_TOOL_DIR="${cache_root}/uv-tools" +export CCACHE_DIR="${cache_root}/ccache" +export CCACHE_MAXSIZE="4GiB" +export CCACHE_TEMPDIR="${cache_root}/ccache/tmp" +export SCCACHE_CACHE_SIZE="4G" +export SCCACHE_DIR="${cache_root}/sccache" +export SCCACHE_IDLE_TIMEOUT="60" + +case "$(uname -s)" in + Darwin | Linux) + export SCCACHE_SERVER_UDS="${cache_root}/sccache.sock" + ;; +esac + +exec "$@" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 69000717d7..3c231295f4 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -27,6 +27,7 @@ This checklist serves as a reminder of a couple of things that ensure your pull **If PR contains AI-assisted content:** -- [ ] I have disclosed the use of AI tools in the PR description as per our [AI Usage Guidelines](https://github.com/munich-quantum-toolkit/core/blob/main/docs/ai_usage.md). -- [ ] AI-assisted commits include an `Assisted-by: [Model Name] via [Tool Name]` footer. +- [ ] Any agent that created, edited, or submitted GitHub content was explicitly authorized for that scope, as required by our [AI Usage Guidelines](https://github.com/munich-quantum-toolkit/core/blob/main/docs/ai_usage.md). +- [ ] Every agent-authored or agent-edited public text body begins with the visible disclosure `🤖 *AI text below* 🤖` (titles are exempt). +- [ ] I have disclosed AI assistance in the PR description. - [ ] I confirm that I have personally reviewed and understood all AI-generated content, and accept full responsibility for it. diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 20820f5ce5..a73dd2e3b7 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -5,72 +5,92 @@ name-template: "MQT Core $RESOLVED_VERSION Release" tag-template: "v$RESOLVED_VERSION" categories: - title: "⚛️ MQT Core IR" - labels: - - "Core" + when: + labels: + - "Core" - title: "⚖️ MQT Core DD Package" - labels: - - "DD" + when: + labels: + - "DD" - title: "🐉 MQT Core MLIR" - labels: - - "MLIR" + when: + labels: + - "MLIR" - title: "🐲 MQT Core QIR" - labels: - - "QIR" + when: + labels: + - "QIR" - title: "🔌 MQT Core QDMI" - labels: - - "QDMI" + when: + labels: + - "QDMI" - title: "🏼 MQT Core NA Package" - labels: - - "NA" + when: + labels: + - "NA" - title: "🕸️ MQT Core ZX Package" - labels: - - "ZX" + when: + labels: + - "ZX" - title: "🚀 Features and Enhancements" - labels: - - "enhancement" - - "feature" - - "refactor" - - "usability" + when: + labels: + - "enhancement" + - "feature" + - "refactor" + - "usability" - title: "🐛 Bug Fixes" - labels: - - "bug" - - "fix" + when: + labels: + - "bug" + - "fix" - title: "📄 Documentation" - labels: - - "documentation" - - title: "🤖 CI" - labels: - - "continuous integration" + when: + labels: + - "documentation" + - title: "🤖 CI & Tooling" + when: + labels: + - "continuous integration" + - "tooling" - title: "📦 Packaging" - labels: - - "packaging" + when: + labels: + - "packaging" - title: "🧹 Code Quality" - labels: - - "code quality" + when: + labels: + - "code quality" - title: "⬆️ Dependencies" collapse-after: 5 - labels: - - "dependencies" - - "github-actions" - - "pre-commit" - - "submodules" + when: + labels: + - "dependencies" + - "github-actions" + - "pre-commit" + - "submodules" + - type: "pre-exclude" + when: + labels: + - "skip-changelog" + - "backport" + - "release-prep" + - type: "version-resolver" + semver-increment: "major" + when: + label: "major" + - type: "version-resolver" + semver-increment: "minor" + when: + label: "minor" + - type: "version-resolver" + semver-increment: "patch" + when: + label: "patch" + - type: "version-resolver" + semver-increment: "patch" change-template: "- $TITLE ([#$NUMBER]($URL)) (**@$AUTHOR**)" change-title-escapes: '\<*_&' -exclude-labels: - - "skip-changelog" - - "backport" - - "release-prep" -version-resolver: - major: - labels: - - "major" - minor: - labels: - - "minor" - patch: - labels: - - "patch" - default: patch template: | ## 👀 What Changed diff --git a/.github/workflow_inputs/release_drafter_categories.json b/.github/workflow_inputs/release_drafter_categories.json index 2c815ac48c..3cb2dfec78 100644 --- a/.github/workflow_inputs/release_drafter_categories.json +++ b/.github/workflow_inputs/release_drafter_categories.json @@ -14,7 +14,7 @@ ], "🐛 Bug Fixes": ["bug", "fix"], "📄 Documentation": ["documentation"], - "🤖 CI": ["continuous integration"], + "🤖 CI & Tooling": ["continuous integration", "tooling"], "📦 Packaging": ["packaging"], "🧹 Code Quality": ["code quality"], "⬆️ Dependencies": [ diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index d903b8e557..d03cbbbd39 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -12,7 +12,7 @@ permissions: jobs: build-sdist: name: 🐍 Packaging - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-sdist.yml@e7f84f39ce2d3b6c5d1d04526b8f94f98e455143 # v2.2.0 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-sdist.yml@973217bac3ad300d9982fffdd3b37a9b5b3a51ea # v2.2.1 # Builds wheels on all supported platforms using cibuildwheel. # The wheels are uploaded as GitHub artifacts `dev-cibw-*` or `cibw-*`, depending on whether the workflow is triggered from a PR or a release. @@ -30,7 +30,7 @@ jobs: windows-2025, windows-11-arm, ] - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-wheel-cibuildwheel.yml@e7f84f39ce2d3b6c5d1d04526b8f94f98e455143 # v2.2.0 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-wheel-cibuildwheel.yml@973217bac3ad300d9982fffdd3b37a9b5b3a51ea # v2.2.1 with: runs-on: ${{ matrix.runs-on }} @@ -50,7 +50,7 @@ jobs: path: dist merge-multiple: true - name: Generate artifact attestation for sdist and wheel(s) - uses: actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1 + uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.0 with: subject-path: dist/* - - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 + - uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9df818c94c..085ac33f84 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,8 +14,7 @@ concurrency: jobs: change-detection: name: 🔍 Change - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-change-detection.yml@e7f84f39ce2d3b6c5d1d04526b8f94f98e455143 # v2.2.0 - + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-change-detection.yml@973217bac3ad300d9982fffdd3b37a9b5b3a51ea # v2.2.1 cpp-tests-ubuntu: name: 🇨‌ Test 🐧 needs: change-detection @@ -30,7 +29,7 @@ jobs: - runs-on: ubuntu-24.04 compiler: gcc preset: debug-mlir - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-tests-ubuntu.yml@e7f84f39ce2d3b6c5d1d04526b8f94f98e455143 # v2.2.0 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-tests-ubuntu.yml@973217bac3ad300d9982fffdd3b37a9b5b3a51ea # v2.2.1 with: runs-on: ${{ matrix.runs-on }} compiler: ${{ matrix.compiler }} @@ -52,7 +51,7 @@ jobs: - runs-on: macos-26 compiler: clang preset: debug-mlir - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-tests-macos.yml@e7f84f39ce2d3b6c5d1d04526b8f94f98e455143 # v2.2.0 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-tests-macos.yml@973217bac3ad300d9982fffdd3b37a9b5b3a51ea # v2.2.1 with: runs-on: ${{ matrix.runs-on }} compiler: ${{ matrix.compiler }} @@ -74,7 +73,7 @@ jobs: - runs-on: windows-2025 compiler: msvc preset: debug-windows-mlir - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-tests-windows.yml@e7f84f39ce2d3b6c5d1d04526b8f94f98e455143 # v2.2.0 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-tests-windows.yml@973217bac3ad300d9982fffdd3b37a9b5b3a51ea # v2.2.1 with: runs-on: ${{ matrix.runs-on }} compiler: ${{ matrix.compiler }} @@ -94,7 +93,7 @@ jobs: runs-on: [ubuntu-24.04, ubuntu-24.04-arm] compiler: [gcc, clang, clang-20, clang-21] preset: [release-mlir, debug-mlir] - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-tests-ubuntu.yml@e7f84f39ce2d3b6c5d1d04526b8f94f98e455143 # v2.2.0 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-tests-ubuntu.yml@973217bac3ad300d9982fffdd3b37a9b5b3a51ea # v2.2.1 with: runs-on: ${{ matrix.runs-on }} compiler: ${{ matrix.compiler }} @@ -113,7 +112,7 @@ jobs: runs-on: [macos-15, macos-15-intel, macos-26, macos-26-intel] compiler: [clang, clang-20, clang-21, gcc-14, gcc-15] preset: [release-mlir, debug-mlir] - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-tests-macos.yml@e7f84f39ce2d3b6c5d1d04526b8f94f98e455143 # v2.2.0 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-tests-macos.yml@973217bac3ad300d9982fffdd3b37a9b5b3a51ea # v2.2.1 with: runs-on: ${{ matrix.runs-on }} compiler: ${{ matrix.compiler }} @@ -132,7 +131,7 @@ jobs: runs-on: [windows-2022, windows-2025, windows-11-arm] compiler: [msvc, clang] preset: [release-windows-mlir, debug-windows-mlir] - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-tests-windows.yml@e7f84f39ce2d3b6c5d1d04526b8f94f98e455143 # v2.2.0 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-tests-windows.yml@973217bac3ad300d9982fffdd3b37a9b5b3a51ea # v2.2.1 with: runs-on: ${{ matrix.runs-on }} compiler: ${{ matrix.compiler }} @@ -145,7 +144,7 @@ jobs: name: 🇨‌ Coverage needs: change-detection if: fromJSON(needs.change-detection.outputs.run-cpp-tests) - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-coverage.yml@e7f84f39ce2d3b6c5d1d04526b8f94f98e455143 # v2.2.0 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-coverage.yml@973217bac3ad300d9982fffdd3b37a9b5b3a51ea # v2.2.1 with: setup-mlir: true llvm-version: 21.1.8 @@ -157,7 +156,7 @@ jobs: name: 🇨‌ Lint needs: change-detection if: fromJSON(needs.change-detection.outputs.run-cpp-linter) - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-linter.yml@e7f84f39ce2d3b6c5d1d04526b8f94f98e455143 # v2.2.0 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-linter.yml@973217bac3ad300d9982fffdd3b37a9b5b3a51ea # v2.2.1 with: clang-version: 22 build-project: true @@ -165,6 +164,9 @@ jobs: setup-python: true install-pkgs: "nanobind==2.13.0" cpp-linter-extra-args: "-std=c++20" + # The vendored toml++ header is checked upstream and is not maintained + # according to MQT Core's clang-tidy configuration. + cpp-linter-ignore-extra: "vendor/**" setup-mlir: true llvm-version: 21.1.8 @@ -183,7 +185,7 @@ jobs: macos-26-intel, windows-2025, ] - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@e7f84f39ce2d3b6c5d1d04526b8f94f98e455143 # v2.2.0 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@973217bac3ad300d9982fffdd3b37a9b5b3a51ea # v2.2.1 with: runs-on: ${{ matrix.runs-on }} @@ -191,7 +193,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@e7f84f39ce2d3b6c5d1d04526b8f94f98e455143 # v2.2.0 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-coverage.yml@973217bac3ad300d9982fffdd3b37a9b5b3a51ea # v2.2.1 permissions: contents: read id-token: write @@ -205,7 +207,7 @@ jobs: fail-fast: false matrix: runs-on: [macos-15, macos-15-intel, windows-2022] - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@e7f84f39ce2d3b6c5d1d04526b8f94f98e455143 # v2.2.0 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@973217bac3ad300d9982fffdd3b37a9b5b3a51ea # v2.2.1 with: runs-on: ${{ matrix.runs-on }} @@ -213,7 +215,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@e7f84f39ce2d3b6c5d1d04526b8f94f98e455143 # v2.2.0 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-linter.yml@973217bac3ad300d9982fffdd3b37a9b5b3a51ea # v2.2.1 with: check-stubs: true enable-ty: true @@ -223,7 +225,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@e7f84f39ce2d3b6c5d1d04526b8f94f98e455143 # v2.2.0 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-sdist.yml@973217bac3ad300d9982fffdd3b37a9b5b3a51ea # v2.2.1 build-wheel: name: 🚀 CD @@ -241,7 +243,7 @@ jobs: windows-2025, windows-11-arm, ] - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-wheel-cibuildwheel.yml@e7f84f39ce2d3b6c5d1d04526b8f94f98e455143 # v2.2.0 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-wheel-cibuildwheel.yml@973217bac3ad300d9982fffdd3b37a9b5b3a51ea # v2.2.1 with: runs-on: ${{ matrix.runs-on }} diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 855f09357d..558ffba089 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -15,4 +15,4 @@ jobs: contents: write runs-on: ubuntu-slim steps: - - uses: release-drafter/release-drafter@4d75298e00d9e34c483e5ff8c68d0ea1c1940c1e # v7.5.1 + - uses: release-drafter/release-drafter@eada3c96a64734dd381cfbda23511034e328ddb0 # v7.6.0 diff --git a/.github/workflows/templating.yml b/.github/workflows/templating.yml index 1e2ed3a9bd..e20eff2178 100644 --- a/.github/workflows/templating.yml +++ b/.github/workflows/templating.yml @@ -26,7 +26,7 @@ jobs: with: client-id: ${{ secrets.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} - - uses: munich-quantum-toolkit/templates@6b796f4c0404f74be469f32a3dcac05ef61d7b72 # v1.3.3 + - uses: munich-quantum-toolkit/templates@d0c5adb86d19a5095f871cb6184cfdab298e943c # v1.4.2 with: token: ${{ steps.create-token.outputs.token }} name: Core @@ -34,4 +34,5 @@ jobs: project-type: c++-python repository: ${{ github.event.repository.name }} base-branch: v3.x + synchronize-agents-md: false release-drafter-categories: ${{ steps.read-release-drafter-categories.outputs.release_drafter_categories }} diff --git a/.github/workflows/upstream.yml b/.github/workflows/upstream.yml index f7ef34fa39..4d5d72c390 100644 --- a/.github/workflows/upstream.yml +++ b/.github/workflows/upstream.yml @@ -18,8 +18,8 @@ jobs: strategy: fail-fast: false matrix: - runs-on: [ubuntu-24.04, macos-14, windows-2022] - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-qiskit-upstream-tests.yml@e7f84f39ce2d3b6c5d1d04526b8f94f98e455143 # v2.2.0 + runs-on: [ubuntu-24.04, macos-15, windows-2025] + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-qiskit-upstream-tests.yml@973217bac3ad300d9982fffdd3b37a9b5b3a51ea # v2.2.1 with: runs-on: ${{ matrix.runs-on }} setup-z3: true @@ -28,7 +28,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@e7f84f39ce2d3b6c5d1d04526b8f94f98e455143 # v2.2.0 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-qiskit-upstream-issue.yml@973217bac3ad300d9982fffdd3b37a9b5b3a51ea # v2.2.1 with: tests-result: ${{ needs.qiskit-upstream-tests.result }} permissions: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 30dbb0025a..bf4f87d766 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -47,14 +47,14 @@ repos: ## Check pyproject.toml file - repo: https://github.com/henryiii/validate-pyproject-schema-store - rev: 2026.06.26 + rev: 2026.07.17 hooks: - id: validate-pyproject priority: 0 ## Ensure uv.lock is up to date - repo: https://github.com/astral-sh/uv-pre-commit - rev: 0.11.25 + rev: 0.11.29 hooks: - id: uv-lock priority: 0 @@ -74,6 +74,7 @@ repos: rev: v1.47.2 hooks: - id: typos + exclude: ^vendor/ priority: 3 ## Check license headers @@ -81,6 +82,7 @@ repos: rev: v2.9.0 hooks: - id: license-tools + exclude: ^vendor/ priority: 4 ## Format BibTeX files with bibtex-tidy @@ -112,7 +114,7 @@ repos: ## Format Markdown files with rumdl - repo: https://github.com/rvben/rumdl-pre-commit - rev: v0.2.28 + rev: v0.2.37 hooks: - id: rumdl args: [--fix] @@ -136,6 +138,7 @@ repos: hooks: - id: clang-format types_or: [c++, c, cuda] + exclude: ^vendor/ priority: 5 - id: clang-format name: clang-format (TableGen) @@ -145,7 +148,7 @@ repos: ## Format and lint Python files with ruff - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.20 + rev: v0.15.22 hooks: - id: ruff-format types_or: [python, pyi, jupyter, markdown] @@ -156,7 +159,7 @@ repos: ## Check Python types with ty - repo: https://github.com/astral-sh/ty-pre-commit - rev: v0.0.55 + rev: v0.0.61 hooks: - id: ty args: [--only-dev] diff --git a/AGENTS.md b/AGENTS.md index 1b2bd3efab..bf6ae4ed9d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,119 +1,181 @@ - - - -# MQT Core - -## C++ - -- Configure: `cmake --preset release` -- Build: `cmake --build --preset release` -- Test: `ctest --preset release` -- Single test binary: `./build/release/test/path/to/binary` -- For debug builds, replace `release` with `debug`. -- For more presets, see `CMakePresets.json`. - -## Python - -- Set up build and test dependencies: - `uv sync --inexact --only-group build --only-group test` -- Install package without build isolation (fast rebuilds): - `uv sync --inexact --no-dev --no-build-isolation-package mqt-core` -- Run tests: `uv run --no-sync pytest` -- Nox test shortcuts: `uvx nox -s tests`, `uvx nox -s minimums` -- Python 3.14 variants: `uvx nox -s tests-3.14`, `uvx nox -s minimums-3.14` - -## Documentation - -- Sources: `docs/` -- Build docs locally: `uvx nox --non-interactive -s docs` -- Link check: `uvx nox -s docs -- -b linkcheck` - -## Tech Stack - -### General - -- `prek` for pre-commit hooks - -### C++ - -- Targets Linux (glibc 2.28+), macOS (11.0+), and Windows on x86_64 and arm64 - architectures -- C++20 -- CMake 3.24+ -- `FetchContent` for dependency management (configured in - `cmake/ExternalDependencies.cmake`) -- `clang-format` and `clang-tidy` for formatting/linting (see `.clang-format` - and `.clang-tidy`) -- GoogleTest for unit tests (located in `test/`) - -### Python - -- Python 3.10+ -- Stable ABI wheels for 3.12+; free-threading support for 3.14+ -- `scikit-build-core` as build backend -- `nanobind` for bindings -- `uv` for installation, packaging, and tooling -- `ruff` for formatting/linting (configured in `pyproject.toml`) -- `ty` for type checking -- `pytest` for unit tests (located in `test/python/`) -- `nox` for task orchestration (tests, linting, docs) - -### Documentation - -- `sphinx` -- MyST (Markdown) -- Furo theme -- `breathe` for C++ API docs - -## Development Guidelines - -### General - -- MUST run `uvx nox -s lint` after every batch of changes. This runs the full - `prek` hook set from `.pre-commit-config.yaml` (including `ruff`, `typos`, - `ty`, formatting, and metadata checks). All hooks must pass before submitting. -- MUST add or update tests for every code change, even if not explicitly - requested. -- MUST follow existing code style by checking neighboring files for patterns. -- MUST update `CHANGELOG.md` and `UPGRADING.md` when changes are user-facing, - breaking, or otherwise noteworthy. -- MUST include a commit footer attribution in the form - `Assisted-by: [Model Name] via [Tool Name]` (example: - `Assisted-by: Claude Sonnet 4.6 via GitHub Copilot`) if AI tools are used to - prepare a commit. -- NEVER modify files that start with "This file has been generated from an - external template. Please do not modify it directly." These files are managed - by - [the MQT templates action](https://github.com/munich-quantum-toolkit/templates) - and changes will be overwritten. -- PREFER running targeted tests over the full test suite during development. +# MQT Core Agent Guide + +This file contains repository-specific instructions for coding agents working on +MQT Core. The project-wide policy for AI-assisted contributions is +[`docs/ai_usage.md`](docs/ai_usage.md); follow it in addition to this guide. + +## Repository Layout + +- `include/mqt-core/` contains the public C++ headers; implementations live in + `src/`. +- `bindings/` contains the nanobind-based Python bindings, and + `python/mqt/core/` contains the Python package and generated type stubs. +- `mlir/` contains the optional MQT MLIR dialects, transformations, tools, and + unit tests. Building it requires LLVM/MLIR 21.1 or newer. +- `test/` contains the C++ and Python tests. C++ tests generally mirror the + corresponding component under `src/`. +- `docs/` contains the Sphinx and MyST documentation; `json/` contains schemas + and data used by the project. +- `cmake/` and `CMakePresets.json` define the supported builds. Keep generated + build output in `build/` and do not commit it. + +## Working Principles + +- Keep changes focused on the assigned task. Do not perform unrelated cleanup, + broad reformatting, dependency upgrades, or refactors without explicit + authorization. +- Preserve user changes and inspect the working tree before editing. Never + discard or overwrite changes that are outside the task. +- Follow the patterns in neighboring files and prefer the smallest change that + fully solves the problem. +- Add or update automated tests for every behavioral code change. During + development, run the narrowest relevant test first, then the required lint + checks before handoff. +- Place tests in the corresponding test tree, organized by the subsystem that + owns the behavior. Within MLIR, keep tests under `mlir/unittests/` or another + established test root; never place them under `mlir/tools/` or another + production source directory. Prefer pass, compiler, or dialect unit tests for + semantic contracts, and reserve subprocess tests for irreducible driver-level + CLI behavior. Normal test targets and dependencies belong in the test build; + avoid promoting an otherwise optional production tool into the default build + solely for subprocess testing. +- Update `CHANGELOG.md` and `UPGRADING.md` for user-facing, breaking, or + otherwise noteworthy changes. +- Format changelog entries with the pull request reference and every + contributing author, for example `([#123]) ([**@username**])`, and define the + corresponding links at the bottom of `CHANGELOG.md`. +- Never commit credentials, tokens, private keys, personal data, or other + secrets. Do not print secrets from the environment or GitHub Actions. Use + documented environment variables and repository secrets instead. +- Do not edit files whose header says that they are generated from an external + template. Propose those changes in the + [MQT templates repository](https://github.com/munich-quantum-toolkit/templates) + or let the templating workflow update them. + +## Build and Test ### C++ -- MUST use Doxygen-style comments. -- MUST use `#pragma once` for header guards. -- MUST regenerate stubs via `uvx nox -s stubs` when files in `bindings/` are - added or modified. -- NEVER edit `.pyi` files in `python/mqt/core/` manually; they are - auto-generated by `nanobind.stubgen`. -- PREFER C++20 STL features over custom implementations. - -### Python - -- MUST use Google-style docstrings -- PREFER running a single Python version over the full test suite during - development. -- PREFER fixing reported warnings over suppressing them (e.g., with `# noqa` - comments for ruff); only add ignore rules when necessary and document why. -- PREFER fixing typing issues reported by `ty` before adding suppression - comments (`# ty: ignore[code]`); suppressions are sometimes necessary for - incompletely typed libraries (e.g., Qiskit). - -## Self-Review Checklist - -- Did `uvx nox -s lint` pass without errors? -- Are all changes covered by at least one automated test? -- Were Python stubs regenerated via `uvx nox -s stubs` if bindings were - modified? -- Are `CHANGELOG.md` and `UPGRADING.md` updated when changes are user-facing, - breaking, or otherwise noteworthy? +- Configure a release build with `./.agent/run.sh cmake --preset release`. +- Build it with `./.agent/run.sh cmake --build --preset release`. +- Run all configured C++ tests with `./.agent/run.sh ctest --preset release`. +- Run a component binary directly when iterating, for example + `./build/release/test/ir/mqt-core-ir-test` or + `./build/release/test/qdmi/driver/mqt-core-qdmi-driver-test`. +- Use GoogleTest filters to narrow a binary further, for example + `./build/release/test/ir/mqt-core-ir-test --gtest_filter='StandardOperation.*'`. +- Replace `release` with `debug` for a debug build. Consult `CMakePresets.json` + for other supported configurations. + +The C++ code targets C++20 and uses GoogleTest. Use Doxygen-style documentation, +`#pragma once` in headers, and existing project abstractions. Prefer C++20 +standard-library facilities over custom equivalents. Within `mlir/`, prefer LLVM +types such as `llvm::SmallVector` and `llvm::function_ref` where appropriate. + +### Python and Bindings + +- Install build and test dependencies with + `./.agent/run.sh uv sync --inexact --only-group build --only-group test`. +- Install the package for fast local rebuilds with + `./.agent/run.sh uv sync --inexact --no-dev --no-build-isolation-package mqt-core`. +- Run the Python tests with `./.agent/run.sh uv run --no-sync pytest`; pass a + file or `-k` expression while iterating. +- Run the supported test sessions with `./.agent/run.sh uvx nox -s tests` and + `./.agent/run.sh uvx nox -s minimums`. Python 3.14 variants are `tests-3.14` + and `minimums-3.14`. +- If a file in `bindings/` is added or changed, regenerate type stubs with + `./.agent/run.sh uvx nox -s stubs`. Never edit generated `.pyi` files in + `python/mqt/core/` manually. + +Use Google-style Python docstrings. Prefer fixing diagnostics from `ruff` and +`ty` over suppressing them; document suppressions that are genuinely required. + +### Worktree-Local Tool Caches + +- Run cache-producing commands through `.agent/run.sh`. It derives the + repository root from its own location, so it works from any directory in the + worktree and exports worktree-local cache paths before executing the requested + command. In addition to the download cache, this localizes `uv` tool + environments, tool binaries, and managed Python installations. It also + supplies a local XDG cache root and `PREK_HOME` so other cache-aware + development tools stay within the worktree. +- The wrapper configures `uv` and `uvx` to use `.cache/uv`. The CMake presets + configure `ccache` and `sccache` to use `.cache/ccache` and `.cache/sccache`, + respectively. These paths are ignored by Git. Outside agent-driven work, + contributors remain free to use their preferred cache configuration. +- Do not redirect these tools to a user-level or shared cache outside the + worktree. In particular, do not work around sandbox failures by requesting + access to a cache under a home directory. +- Use the CMake presets for configuration, builds, and tests so the compiler + cache environment is applied consistently. The compiler caches are capped at 4 + GiB per worktree and clean up automatically as they reach that limit. +- If invoking `ccache` or `sccache` outside a CMake preset, set `CCACHE_DIR` or + `SCCACHE_DIR` to the corresponding repository-local path first. +- After a significant batch of work, and only once no `uv`, build, or compiler + cache process is running, run `./.agent/clean-caches.sh`. This clears only the + current worktree's local caches. Do not remove cache contents manually while + another process may be using them. + +### MLIR and Documentation + +- Build the MLIR documentation with + `./.agent/run.sh cmake --build --preset release --target mlir-doc`. +- A real focused MLIR test binary is + `./build/release/mlir/unittests/Compiler/mqt-core-mlir-unittests-compiler`. +- Build the complete documentation with + `./.agent/run.sh uvx nox --non-interactive -s docs`. +- Check documentation links with + `./.agent/run.sh uvx nox -s docs -- -b linkcheck`. +- When changing MLIR passes, pipelines, or command-line options, keep summaries + and descriptions aligned with the implementation's actual scope, defaults, + supported operation shapes, compile-time or runtime limitations, failure + modes, and deliberately out-of-scope behavior. + +## Generated Files and Validation + +- Do not hand-edit generated stubs, rendered documentation, CMake-generated + files, or template-managed files. +- Run `./.agent/run.sh uvx nox -s lint` after each completed batch of changes. + It runs the full `prek` hook set, including formatting, spelling, type, and + metadata checks. +- Inspect the final diff and working-tree status. Report every check run and + clearly distinguish passes, failures, and checks that could not be run. + +## ExecPlans + +When writing complex features or significant refactors, use an ExecPlan (as +described in [`.agent/PLANS.md`](.agent/PLANS.md)) from design to +implementation. Keep one ExecPlan per independently implemented task and store +it under `.agent/plans/.md`; the plan is a living record of that +task's decisions and progress. + +## Git and GitHub Actions + +- A coding agent may perform coding, Git, and GitHub workflow tasks that a human + has explicitly delegated. Authorization is limited to that stated scope; + request fresh authorization before taking an external action outside it. +- Scoped authorization to create or update public GitHub text permits posting + within that scope without separate approval for each message. A human remains + accountable and must review agent-assisted work before it is accepted or + merged. +- Every public text body authored or edited by an agent—including issue and + pull-request descriptions, comments, and reviews—must visibly include the + exact disclosure `🤖 *AI text below* 🤖`. Titles are exempt. +- Never use an agent to work on an issue labeled `good first issue`, and never + generate spam, repetitive reviews, or unreviewed contributions. +- Do not push, open or merge a pull request, post on GitHub, or otherwise change + remote state unless the human has explicitly authorized that action. +- Review findings should focus on substantive correctness, contracts, + maintainability, tests, documentation, licensing, and validation rather than + optional process metadata. + +## Handoff Checklist + +- The diff is focused and follows neighboring code conventions. +- Behavioral changes have automated test coverage, and targeted tests pass. +- `./.agent/run.sh uvx nox -s lint` passes. +- Binding changes have regenerated stubs. +- User-facing changes update `CHANGELOG.md` and `UPGRADING.md` when appropriate. +- Generated, template-managed, secret, and unrelated files are absent from the + diff. +- AI assistance and validation results are reported transparently. diff --git a/CHANGELOG.md b/CHANGELOG.md index 5562e48fbf..e6b1aa2cb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,18 +12,44 @@ releases may include breaking changes. ### Added +- ✨ Add binary-safe QDMI program submission and retrieval to FoMaC ([#1957]) + ([**@burgholzer**]) +- ✨ Add versioned, relocatable configuration and stable-ID registration for + QDMI device libraries, including disabled-ID reservations, fresh device + sessions, idempotent registration, and external-device target metadata + ([#1912]) ([**@burgholzer**]) +- ✨ Add native relative-phase CCX (`rccx`) support across the IR, DD package, + ZX diagrams, OpenQASM import/export, and Python/Qiskit bindings ([#1886], + [#1950]) ([**@simon1hofmann**]) - ✨ Add support for QDMI child devices to the driver and FoMaC libraries - ([#1897]) ([**@burgholzer**]) + ([#1897], [#1952]) ([**@burgholzer**]) - ✨ Add typed custom property and result queries to the C++ and Python FoMaC libraries ([#1895]) ([**@burgholzer**]) - ✨ Add support for custom job parameters to C++ and Python FoMaC library ([#1887]) ([**@flowerthrower**], [**@burgholzer**]) +- ✨ Add QIR output schemas support to the QIR runtime ([#1877]) + ([**@rturrado**]) +- ✨ Add QIR program format support to the DDSIM QDMI device ([#1766]) + ([**@rturrado**]) ### Changed - ⬆️ Raise the minimum supported QDMI version to 1.3.2 ([#1897]) ([**@burgholzer**]) +### Removed + +- 🔥 Replace the unstable C++ `Driver::addDynamicDeviceLibrary` and Python + `add_dynamic_device_library` APIs with definition registration and stable-ID + opening ([#1912]) ([**@burgholzer**]) + +### Fixed + +- 🐛 Allow MQT Core to be embedded as a CMake subproject without target + collisions and make its bundled QDMI devices individually configurable + ([#1965]) ([**@burgholzer**]) +- 🐛 Fix QIR function names for adjoint gates ([#1830]) ([**@denialhaag**]) + ## [3.7.0] - 2026-07-09 _If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md#370)._ @@ -579,17 +605,26 @@ changelogs._ +[#1965]: https://github.com/munich-quantum-toolkit/core/pull/1965 +[#1957]: https://github.com/munich-quantum-toolkit/core/pull/1957 +[#1952]: https://github.com/munich-quantum-toolkit/core/pull/1952 +[#1950]: https://github.com/munich-quantum-toolkit/core/pull/1950 +[#1912]: https://github.com/munich-quantum-toolkit/core/pull/1912 [#1897]: https://github.com/munich-quantum-toolkit/core/pull/1897 [#1895]: https://github.com/munich-quantum-toolkit/core/pull/1895 [#1887]: https://github.com/munich-quantum-toolkit/core/pull/1887 +[#1886]: https://github.com/munich-quantum-toolkit/core/pull/1886 +[#1877]: https://github.com/munich-quantum-toolkit/core/pull/1877 [#1873]: https://github.com/munich-quantum-toolkit/core/pull/1873 [#1849]: https://github.com/munich-quantum-toolkit/core/pull/1849 [#1848]: https://github.com/munich-quantum-toolkit/core/pull/1848 [#1844]: https://github.com/munich-quantum-toolkit/core/pull/1844 [#1842]: https://github.com/munich-quantum-toolkit/core/pull/1842 +[#1830]: https://github.com/munich-quantum-toolkit/core/pull/1830 [#1828]: https://github.com/munich-quantum-toolkit/core/pull/1828 [#1826]: https://github.com/munich-quantum-toolkit/core/pull/1826 [#1817]: https://github.com/munich-quantum-toolkit/core/pull/1817 +[#1766]: https://github.com/munich-quantum-toolkit/core/pull/1766 [#1737]: https://github.com/munich-quantum-toolkit/core/pull/1737 [#1720]: https://github.com/munich-quantum-toolkit/core/pull/1720 [#1719]: https://github.com/munich-quantum-toolkit/core/pull/1719 @@ -747,6 +782,7 @@ changelogs._ [**@burgholzer**]: https://github.com/burgholzer +[**@simon1hofmann**]: https://github.com/simon1hofmann [**@ystade**]: https://github.com/ystade [**@DRovara**]: https://github.com/DRovara [**@flowerthrower**]: https://github.com/flowerthrower @@ -764,6 +800,7 @@ changelogs._ [**@marcelwa**]: https://github.com/marcelwa [**@lirem101**]: https://github.com/lirem101 [**@keefehuang**]: https://github.com/keefehuang +[**@rturrado**]: https://github.com/rturrado diff --git a/CMakeLists.txt b/CMakeLists.txt index fc4addaf69..1af6427aed 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ include(PreventInSourceBuilds) include(PackageAddTest) include(Cache) include(AddMQTCoreLibrary) +include(AddMQTQDMIDevice) option(BUILD_MQT_CORE_BINDINGS "Build the MQT Core Python bindings" OFF) if(BUILD_MQT_CORE_BINDINGS) @@ -62,6 +63,12 @@ option(BUILD_MQT_CORE_TESTS "Build tests for the MQT Core project" ${PROJECT_IS_ option(BUILD_MQT_CORE_SHARED_LIBS "Build MQT Core libraries as shared libraries" ${BUILD_SHARED_LIBS}) option(BUILD_MQT_CORE_MLIR "Build the MLIR submodule of the MQT Core project" OFF) +option(BUILD_MQT_CORE_QDMI_DDSIM_DEVICE "Build the MQT Core DDSIM QDMI device" + ${PROJECT_IS_TOP_LEVEL}) +option(BUILD_MQT_CORE_QDMI_NA_DEVICE "Build the MQT Core neutral-atom QDMI device" + ${PROJECT_IS_TOP_LEVEL}) +option(BUILD_MQT_CORE_QDMI_SC_DEVICE "Build the MQT Core superconducting QDMI device" + ${PROJECT_IS_TOP_LEVEL}) if(APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(BUILD_MQT_CORE_MLIR OFF @@ -121,6 +128,10 @@ endif() cmake_dependent_option(BUILD_MQT_CORE_QIR_RUNNER "Build the QIR runner of the MQT Core project" ON "BUILD_MQT_CORE_MLIR" OFF) +cmake_dependent_option( + BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR "Enable QIR program format support for the DDSIM QDMI Device" + ON "BUILD_MQT_CORE_MLIR" OFF) + # add main library code add_subdirectory(src) @@ -144,16 +155,19 @@ endif() if(BUILD_MQT_CORE_MLIR) add_subdirectory(mlir) - # copy generated MLIR documentation - add_custom_command( - TARGET mlir-doc - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_BINARY_DIR}/docs/ - ${CMAKE_CURRENT_SOURCE_DIR}/docs/mlir/ - COMMAND ${CMAKE_COMMAND} -D DOCS_DIR:PATH=${CMAKE_CURRENT_SOURCE_DIR}/docs/mlir -P - ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CleanMLIRDocs.cmake - COMMENT "Copying and cleaning up generated MLIR documentation" - VERBATIM) + # Preserve the top-level `mlir-doc` behavior without modifying a target owned by an embedding + # project. + if(PROJECT_IS_TOP_LEVEL) + add_custom_command( + TARGET mlir-doc + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_BINARY_DIR}/docs/ + ${CMAKE_CURRENT_SOURCE_DIR}/docs/mlir/ + COMMAND ${CMAKE_COMMAND} -D DOCS_DIR:PATH=${CMAKE_CURRENT_SOURCE_DIR}/docs/mlir -P + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CleanMLIRDocs.cmake + COMMENT "Copying and cleaning up generated MLIR documentation" + VERBATIM) + endif() endif() if(PROJECT_IS_TOP_LEVEL) diff --git a/CMakePresets.json b/CMakePresets.json index ecbb114d81..973af4c626 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -4,7 +4,14 @@ { "name": "base", "hidden": true, - "binaryDir": "${sourceDir}/build/${presetName}" + "binaryDir": "${sourceDir}/build/${presetName}", + "environment": { + "CCACHE_DIR": "${sourceDir}/.cache/ccache", + "CCACHE_MAXSIZE": "4GiB", + "CCACHE_TEMPDIR": "${sourceDir}/.cache/ccache/tmp", + "SCCACHE_CACHE_SIZE": "4G", + "SCCACHE_DIR": "${sourceDir}/.cache/sccache" + } }, { "name": "base-mlir", @@ -26,7 +33,10 @@ "list": ["Linux", "Darwin"] }, "description": "Default configuration for Linux and macOS builds. Uses the Ninja generator", - "generator": "Ninja" + "generator": "Ninja", + "environment": { + "SCCACHE_SERVER_UDS": "${sourceDir}/.cache/sccache.sock" + } }, { "name": "base-unix-mlir", diff --git a/UPGRADING.md b/UPGRADING.md index 714e0f93e7..4ac0cbe0db 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -6,6 +6,53 @@ of changes including minor and patch releases, please refer to the ## [Unreleased] +### Bundled QDMI devices in embedded builds + +The bundled QDMI devices now have individual CMake options: +`BUILD_MQT_CORE_QDMI_DDSIM_DEVICE`, `BUILD_MQT_CORE_QDMI_NA_DEVICE`, and +`BUILD_MQT_CORE_QDMI_SC_DEVICE`. All three remain enabled by default in a +standalone MQT Core build. They default to disabled when MQT Core is consumed +through CMake's `FetchContent` or `add_subdirectory`; embedded consumers can +enable only the devices they need before making MQT Core available. The QDMI +driver and FoMaC libraries remain available independently. + +### QDMI runtime device registration + +The unstable runtime-loading helpers have been replaced with registration by a +stable device ID followed by an explicit open. In Python, replace +`add_dynamic_device_library(library_path, prefix, ...)` with: + +```python +from mqt.core.fomac import DeviceDefinition, open_device, register_device + +definition = DeviceDefinition("my.device", library_path, prefix, base_url="https://device.example") +register_device(definition) +device = open_device("my.device") +``` + +Per-backend session values can be passed directly to +`open_device("my.device", base_url=..., token=...)`. Every call creates a fresh +device session without registering another device ID. Repeated integration setup +can use `register_device_if_absent(definition)` instead of suppressing +duplicate-ID errors; invalid definitions are still rejected, and a device +disabled by higher-precedence configuration remains reserved. + +The equivalent C++ flow is: + +```cpp +qdmi::DeviceDefinition definition{.id = "my.device", + .library = libraryPath, + .prefix = prefix}; +auto& driver = qdmi::Driver::get(); +driver.registerDevice(definition); +auto device = fomac::Session::openDevice("my.device"); +``` + +Registration validates and stores metadata without loading native code. Opening +an unknown or disabled ID fails. `fomac::Session::openDevice` creates a fresh +owned session on every call. `qdmi::Driver::open(id)` retains its cached-device +behavior for client callers. + ### QDMI child devices The QDMI driver now translates device-library-specific `QDMI_Child_Device` diff --git a/bindings/fomac/fomac.cpp b/bindings/fomac/fomac.cpp index fcdfd0ddf8..3606920292 100644 --- a/bindings/fomac/fomac.cpp +++ b/bindings/fomac/fomac.cpp @@ -14,17 +14,20 @@ #include #include -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) +#include // NOLINT(misc-include-cleaner) +#include // NOLINT(misc-include-cleaner) +#include // NOLINT(misc-include-cleaner) +#include // NOLINT(misc-include-cleaner) +#include // NOLINT(misc-include-cleaner) +#include // NOLINT(misc-include-cleaner) +#include // NOLINT(misc-include-cleaner) +#include // NOLINT(misc-include-cleaner) #include #include +#include #include +#include #include #include #include @@ -70,6 +73,28 @@ template throw nb::type_error( "value_type must be exactly str, bool, int, float, or bytes"); } + +[[nodiscard]] auto makeDeviceSessionConfig( + std::optional baseUrl, std::optional token, + std::optional authFile, + std::optional authUrl, std::optional username, + std::optional password, std::optional custom1, + std::optional custom2, std::optional custom3, + std::optional custom4, std::optional custom5) + -> qdmi::DeviceSessionConfig { + return {.baseUrl = std::move(baseUrl), + .token = std::move(token), + .authFile = std::move(authFile), + .authUrl = std::move(authUrl), + .username = std::move(username), + .password = std::move(password), + .custom1 = std::move(custom1), + .custom2 = std::move(custom2), + .custom3 = std::move(custom3), + .custom4 = std::move(custom4), + .custom5 = std::move(custom5)}; +} + } // namespace NB_MODULE(MQT_CORE_MODULE_NAME, m) { @@ -77,13 +102,13 @@ NB_MODULE(MQT_CORE_MODULE_NAME, m) { auto session = nb::class_( m, "Session", R"pb(A FoMaC session for managing QDMI devices. -Allows creating isolated sessions with independent authentication settings. +Allows creating isolated sessions with separate authentication settings. All authentication parameters are optional and can be provided as keyword arguments to the constructor.)pb"); session.def( "__init__", [](fomac::Session* self, std::optional token, - std::optional authFile, + std::optional authFile, std::optional authUrl, std::optional username, std::optional password, @@ -244,6 +269,14 @@ when the custom slot is unsupported.)pb"); job.def_prop_ro("program", &fomac::Job::getProgram, "The submitted program."); + job.def_prop_ro( + "program_bytes", + [](const fomac::Job& self) { + const auto program = self.getProgramBytes(); + return nb::bytes(program.data(), program.size()); + }, + "The exact bytes of the submitted program."); + job.def_prop_ro("num_shots", &fomac::Job::getNumShots, "The number of shots."); @@ -274,6 +307,7 @@ when the custom slot is unsupported.)pb"); .value("CALIBRATION", QDMI_PROGRAM_FORMAT_CALIBRATION) .value("QPY", QDMI_PROGRAM_FORMAT_QPY) .value("IQM_JSON", QDMI_PROGRAM_FORMAT_IQMJSON) + .value("BATCH_JOB", QDMI_PROGRAM_FORMAT_BATCHJOB) .value("CUSTOM1", QDMI_PROGRAM_FORMAT_CUSTOM1) .value("CUSTOM2", QDMI_PROGRAM_FORMAT_CUSTOM2) .value("CUSTOM3", QDMI_PROGRAM_FORMAT_CUSTOM3) @@ -383,12 +417,43 @@ The caller must provide the type documented by the device implementation. Use ``bytes`` to retrieve the value without interpretation. Returns ``None`` when the custom slot is unsupported.)pb"); - device.def("submit_job", &fomac::Device::submitJob, "program"_a, - "program_format"_a, "num_shots"_a, nb::kw_only(), - "custom1"_a = nb::none(), "custom2"_a = nb::none(), - "custom3"_a = nb::none(), "custom4"_a = nb::none(), - "custom5"_a = nb::none(), nb::rv_policy::reference_internal, - "Submits a job to the device."); + device.def( + "submit_job", + [](const fomac::Device& self, const std::string& program, + const QDMI_Program_Format format, const size_t numShots, + const std::optional& custom1, + const std::optional& custom2, + const std::optional& custom3, + const std::optional& custom4, + const std::optional& custom5) { + return self.submitJob(program, format, numShots, custom1, custom2, + custom3, custom4, custom5); + }, + "program"_a, "program_format"_a, "num_shots"_a, nb::kw_only(), + "custom1"_a = nb::none(), "custom2"_a = nb::none(), + "custom3"_a = nb::none(), "custom4"_a = nb::none(), + "custom5"_a = nb::none(), nb::rv_policy::reference_internal, + "Submits a text job to the device."); + + device.def( + "submit_job", + [](const fomac::Device& self, const nb::bytes& program, + const QDMI_Program_Format format, const size_t numShots, + const std::optional& custom1, + const std::optional& custom2, + const std::optional& custom3, + const std::optional& custom4, + const std::optional& custom5) { + const auto bytes = std::span{ + static_cast(program.data()), program.size()}; + return self.submitJob(bytes, format, numShots, custom1, custom2, + custom3, custom4, custom5); + }, + "program"_a, "program_format"_a, "num_shots"_a, nb::kw_only(), + "custom1"_a = nb::none(), "custom2"_a = nb::none(), + "custom3"_a = nb::none(), "custom4"_a = nb::none(), + "custom5"_a = nb::none(), nb::rv_policy::reference_internal, + "Submits an exact byte payload to the device."); device.def("__repr__", [](const fomac::Device& dev) { return ""; @@ -570,80 +635,150 @@ when the custom slot is unsupported.)pb"); operation.def(nb::self != nb::self, nb::sig("def __ne__(self, arg: object, /) -> bool")); - // Module-level function to add dynamic device libraries - m.def( - "add_dynamic_device_library", - [](const std::string& libraryPath, const std::string& prefix, - const std::optional& baseUrl = std::nullopt, - const std::optional& token = std::nullopt, - const std::optional& authFile = std::nullopt, - const std::optional& authUrl = std::nullopt, - const std::optional& username = std::nullopt, - const std::optional& password = std::nullopt, - const std::optional& custom1 = std::nullopt, - const std::optional& custom2 = std::nullopt, - const std::optional& custom3 = std::nullopt, - const std::optional& custom4 = std::nullopt, - const std::optional& custom5 = - std::nullopt) -> fomac::Device { - const qdmi::DeviceSessionConfig config{.baseUrl = baseUrl, - .token = token, - .authFile = authFile, - .authUrl = authUrl, - .username = username, - .password = password, - .custom1 = custom1, - .custom2 = custom2, - .custom3 = custom3, - .custom4 = custom4, - .custom5 = custom5}; - auto* const qdmiDevice = qdmi::Driver::get().addDynamicDeviceLibrary( - libraryPath, prefix, config); - return fomac::Session::createSessionlessDevice(qdmiDevice); - }, - "library_path"_a, "prefix"_a, nb::kw_only(), "base_url"_a = std::nullopt, - "token"_a = std::nullopt, "auth_file"_a = std::nullopt, - "auth_url"_a = std::nullopt, "username"_a = std::nullopt, - "password"_a = std::nullopt, "custom1"_a = std::nullopt, - "custom2"_a = std::nullopt, "custom3"_a = std::nullopt, - "custom4"_a = std::nullopt, "custom5"_a = std::nullopt, - R"pb(Load a dynamic device library into the QDMI driver. - -This function loads a shared library (.so, .dll, or .dylib) that implements a QDMI device interface and makes it available for use in sessions. + nb::class_( + m, "DeviceDefinition", + R"pb(A stable QDMI device registration that can be stored before loading.)pb") + .def( + "__init__", + [](qdmi::DeviceDefinition* self, std::string deviceId, + std::filesystem::path libraryPath, std::string prefix, + const std::optional& baseUrl = std::nullopt, + const std::optional& token = std::nullopt, + const std::optional& authFile = + std::nullopt, + const std::optional& authUrl = std::nullopt, + const std::optional& username = std::nullopt, + const std::optional& password = std::nullopt, + const std::optional& custom1 = std::nullopt, + const std::optional& custom2 = std::nullopt, + const std::optional& custom3 = std::nullopt, + const std::optional& custom4 = std::nullopt, + const std::optional& custom5 = std::nullopt) { + new (self) qdmi::DeviceDefinition{ + .id = std::move(deviceId), + .library = std::move(libraryPath), + .prefix = std::move(prefix), + .session = makeDeviceSessionConfig( + baseUrl, token, authFile, authUrl, username, password, + custom1, custom2, custom3, custom4, custom5)}; + }, + "device_id"_a, "library_path"_a, "prefix"_a, nb::kw_only(), + "base_url"_a = std::nullopt, "token"_a = std::nullopt, + "auth_file"_a = std::nullopt, "auth_url"_a = std::nullopt, + "username"_a = std::nullopt, "password"_a = std::nullopt, + "custom1"_a = std::nullopt, "custom2"_a = std::nullopt, + "custom3"_a = std::nullopt, "custom4"_a = std::nullopt, + "custom5"_a = std::nullopt, + R"pb(Create a device definition without loading its native library. Args: - library_path: Path to the shared library file to load. - prefix: Function prefix used by the library (e.g., "MY_DEVICE"). + device_id: Stable identifier used by :func:`open_device`. + library_path: Path to the shared QDMI device library. + prefix: Function prefix used by the library (for example, ``MY_DEVICE``). base_url: Optional base URL for the device API endpoint. token: Optional authentication token. - auth_file: Optional path to authentication file. + auth_file: Optional path to an authentication file. auth_url: Optional authentication server URL. - username: Optional username for authentication. - password: Optional password for authentication. + username: Optional authentication username. + password: Optional authentication password. custom1: Optional custom configuration parameter 1. custom2: Optional custom configuration parameter 2. custom3: Optional custom configuration parameter 3. custom4: Optional custom configuration parameter 4. - custom5: Optional custom configuration parameter 5. + custom5: Optional custom configuration parameter 5.)pb") + .def_ro("device_id", &qdmi::DeviceDefinition::id, + R"pb(Stable identifier used to open the device.)pb") + .def_ro("library_path", &qdmi::DeviceDefinition::library, + R"pb(Path to the native QDMI device library.)pb") + .def_ro("prefix", &qdmi::DeviceDefinition::prefix, + R"pb(Prefix used for the QDMI device interface functions.)pb"); + + m.def( + "register_device", + [](qdmi::DeviceDefinition definition, const bool replace) { + qdmi::Driver::get().registerDevice(std::move(definition), replace); + }, + "definition"_a, nb::kw_only(), "replace"_a = false, + R"pb(Register a QDMI device definition without loading its library. + +Args: + definition: Definition to validate and store. + replace: Replace an existing definition if it has not been opened. + +Raises: + ValueError: If the definition is invalid or its ID is already registered. + RuntimeError: If replacing an already opened ID.)pb"); + + m.def( + "register_device_if_absent", + [](qdmi::DeviceDefinition definition) { + return qdmi::Driver::get().registerDeviceIfAbsent( + std::move(definition)); + }, + "definition"_a, + R"pb(Register a valid QDMI device definition if its ID is absent. + +Existing and explicitly disabled IDs are not inserted. Invalid definitions +still raise. + +Args: + definition: Definition to validate and store. Returns: - Device: The newly loaded device that can be used to create backends. + bool: Whether the definition was inserted. Raises: - RuntimeError: If library loading fails or configuration is invalid. + ValueError: If the definition is invalid.)pb"); + + m.def( + "open_device", + [](const std::string& deviceId, std::optional baseUrl, + std::optional token, + std::optional authFile, + std::optional authUrl, + std::optional username, + std::optional password, + std::optional custom1, std::optional custom2, + std::optional custom3, std::optional custom4, + std::optional custom5) { + const auto overrides = makeDeviceSessionConfig( + std::move(baseUrl), std::move(token), std::move(authFile), + std::move(authUrl), std::move(username), std::move(password), + std::move(custom1), std::move(custom2), std::move(custom3), + std::move(custom4), std::move(custom5)); + return fomac::Session::openDevice(deviceId, overrides); + }, + "device_id"_a, nb::kw_only(), "base_url"_a = std::nullopt, + "token"_a = std::nullopt, "auth_file"_a = std::nullopt, + "auth_url"_a = std::nullopt, "username"_a = std::nullopt, + "password"_a = std::nullopt, "custom1"_a = std::nullopt, + "custom2"_a = std::nullopt, "custom3"_a = std::nullopt, + "custom4"_a = std::nullopt, "custom5"_a = std::nullopt, + R"pb(Open a registered QDMI device by stable ID. -Examples: - Load a device library with configuration: +Every call creates a fresh device session while keeping the stable registration +unchanged. Opening the device loads trusted native device code. - >>> import mqt.core.fomac as fomac - >>> device = fomac.add_dynamic_device_library( - ... "/path/to/libmy_device.so", "MY_DEVICE", base_url="http://localhost:8080", custom1="API_V2" - ... ) +Args: + device_id: Stable ID of a registered device. + base_url: Optional base URL override for the device API endpoint. + token: Optional authentication token override. + auth_file: Optional authentication-file override. + auth_url: Optional authentication server URL override. + username: Optional authentication username override. + password: Optional authentication password override. + custom1: Optional custom configuration parameter 1 override. + custom2: Optional custom configuration parameter 2 override. + custom3: Optional custom configuration parameter 3 override. + custom4: Optional custom configuration parameter 4 override. + custom5: Optional custom configuration parameter 5 override. - Now the device can be used directly: +Returns: + Device: The opened device, ready for direct backend construction. - >>> from mqt.core.plugins.qiskit import QDMIBackend - >>> backend = QDMIBackend(device=device))pb"); +Raises: + IndexError: If the ID is not registered. + RuntimeError: If the device library cannot be loaded or initialized.)pb"); } } // namespace mqt diff --git a/bindings/ir/operations/register_optype.cpp b/bindings/ir/operations/register_optype.cpp index 6654f3e6c1..339807551d 100644 --- a/bindings/ir/operations/register_optype.cpp +++ b/bindings/ir/operations/register_optype.cpp @@ -198,6 +198,11 @@ See Also: See Also: :meth:`mqt.core.ir.QuantumComputation.xx_plus_yy`)pb") + .value("rccx", qc::OpType::RCCX, R"pb(A relative-phase CCX gate. + +See Also: + :meth:`mqt.core.ir.QuantumComputation.rccx`)pb") + .value("compound", qc::OpType::Compound, R"pb(A compound operation. It is used to group multiple operations into a single operation. diff --git a/bindings/ir/register_quantum_computation.cpp b/bindings/ir/register_quantum_computation.cpp index 618d52e7e4..fbf3ce3453 100644 --- a/bindings/ir/register_quantum_computation.cpp +++ b/bindings/ir/register_quantum_computation.cpp @@ -1971,6 +1971,46 @@ See Also: target1: The first target qubit target2: The second target qubit)pb"); + // RCCX + + qc.def("rccx", &qc::QuantumComputation::rccx, "target1"_a, "target2"_a, + "target3"_a, + R"pb(Apply a relative-phase CCX (RCCX) gate. + +Args: + target1: The first target qubit + target2: The second target qubit + target3: The third target qubit)pb"); + qc.def("crccx", &qc::QuantumComputation::crccx, "control"_a, "target1"_a, + "target2"_a, "target3"_a, + nb::sig("def crccx(self, control: mqt.core.ir.operations.Control | " + "int, target1: int, target2: int, target3: int) -> None"), + R"pb(Apply a controlled RCCX gate. + +See Also: + :meth:`rccx` + +Args: + control: The control qubit + target1: The first target qubit + target2: The second target qubit + target3: The third target qubit)pb"); + qc.def("mcrccx", &qc::QuantumComputation::mcrccx, "controls"_a, "target1"_a, + "target2"_a, "target3"_a, + nb::sig("def mcrccx(self, controls: " + "collections.abc.Set[mqt.core.ir.operations.Control | " + "int], target1: int, target2: int, target3: int) -> None"), + R"pb(Apply a multi-controlled RCCX gate. + +See Also: + :meth:`rccx` + +Args: + controls: The control qubits + target1: The first target qubit + target2: The second target qubit + target3: The third target qubit)pb"); + qc.def("gphase", &qc::QuantumComputation::gphase, "phase"_a, R"pb(Apply a global phase gate. diff --git a/cmake/AddMQTQDMIDevice.cmake b/cmake/AddMQTQDMIDevice.cmake new file mode 100644 index 0000000000..a87e2164b0 --- /dev/null +++ b/cmake/AddMQTQDMIDevice.cmake @@ -0,0 +1,145 @@ +# 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 + +include(GNUInstallDirs) + +function(_mqt_qdmi_json_escape result value) + string(REPLACE "\\" "\\\\" escaped "${value}") + string(REPLACE "\"" "\\\"" escaped "${escaped}") + string(REPLACE "\n" "\\n" escaped "${escaped}") + string(REPLACE "\r" "\\r" escaped "${escaped}") + string(REPLACE "\t" "\\t" escaped "${escaped}") + set(${result} + "${escaped}" + PARENT_SCOPE) +endfunction() + +# Configure and register a relocatable built-in QDMI device. The generated fragment is emitted +# beside the runtime library in both build and install trees. +function(mqt_configure_qdmi_device target) + cmake_parse_arguments(ARG "" "ID;PREFIX" "" ${ARGN}) + if(NOT TARGET ${target}) + message(FATAL_ERROR "Unknown QDMI device target: ${target}") + endif() + if(NOT ARG_ID OR NOT ARG_PREFIX) + message(FATAL_ERROR "mqt_configure_qdmi_device requires ID and PREFIX") + endif() + + set_target_properties( + ${target} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") + target_compile_definitions(${target} PRIVATE QDMI_VERSION="${QDMI_VERSION}" + ${ARG_PREFIX}_QDMI_device_EXPORTS) + _mqt_qdmi_json_escape(device_id "${ARG_ID}") + _mqt_qdmi_json_escape(device_prefix "${ARG_PREFIX}") + + set(fragment "${CMAKE_CURRENT_BINARY_DIR}/$/${target}.qdmi.json") + file( + GENERATE + OUTPUT "${fragment}" + CONTENT + "{\n \"schema-version\": 1,\n \"qdmi\": {\n \"devices\": [\n {\n \"id\": \"${device_id}\",\n \"library\": \"$\",\n \"prefix\": \"${device_prefix}\",\n \"enabled\": true\n }\n ]\n }\n}\n" + ) + + add_custom_command( + TARGET ${target} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${fragment}" + "$/${target}.qdmi.json") + set_target_properties( + ${target} + PROPERTIES QDMI_DEVICE_ID "${ARG_ID}" + QDMI_DEVICE_PREFIX "${ARG_PREFIX}" + QDMI_MANIFEST_NAME "${target}.qdmi.json") + set_property(GLOBAL APPEND PROPERTY MQT_QDMI_DEVICE_TARGETS ${target}) + set_property( + TARGET ${target} + APPEND + PROPERTY EXPORT_PROPERTIES QDMI_DEVICE_ID QDMI_DEVICE_PREFIX QDMI_MANIFEST_NAME) + if(WIN32) + # Shared-library targets are runtime artifacts on Windows and are installed under bin. Keep the + # fragment beside the DLL so its relative path resolves. + set(fragment_install_dir ${CMAKE_INSTALL_BINDIR}) + else() + set(fragment_install_dir ${CMAKE_INSTALL_LIBDIR}) + endif() + set(install_arguments) + if(MQT_CORE_TARGET_NAME) + list(APPEND install_arguments COMPONENT ${MQT_CORE_TARGET_NAME}_Runtime) + endif() + install( + FILES "${fragment}" + DESTINATION ${fragment_install_dir} + ${install_arguments}) +endfunction() + +# Return every QDMI device registered through mqt_configure_qdmi_device. +function(mqt_get_qdmi_device_targets result) + get_property(devices GLOBAL PROPERTY MQT_QDMI_DEVICE_TARGETS) + set(${result} + ${devices} + PARENT_SCOPE) +endfunction() + +# Copy QDMI device libraries and their manifests beside a static consumer executable. +function(mqt_copy_qdmi_runtime target) + if(NOT TARGET ${target}) + message(FATAL_ERROR "Unknown QDMI runtime consumer target: ${target}") + endif() + set(devices ${ARGN}) + if(NOT devices) + mqt_get_qdmi_device_targets(devices) + endif() + if(NOT devices) + message(FATAL_ERROR "mqt_copy_qdmi_runtime requires at least one QDMI device target") + endif() + foreach(device IN LISTS devices) + if(NOT TARGET ${device}) + message(FATAL_ERROR "Unknown QDMI device target: ${device}") + endif() + get_target_property(device_target ${device} ALIASED_TARGET) + if(NOT device_target) + set(device_target ${device}) + endif() + get_target_property(manifest_name ${device_target} QDMI_MANIFEST_NAME) + if(NOT manifest_name) + get_target_property(device_id ${device_target} QDMI_DEVICE_ID) + get_target_property(device_prefix ${device_target} QDMI_DEVICE_PREFIX) + if(NOT device_id OR NOT device_prefix) + message( + FATAL_ERROR + "QDMI device target '${device}' must define either QDMI_MANIFEST_NAME or both QDMI_DEVICE_ID and QDMI_DEVICE_PREFIX" + ) + endif() + _mqt_qdmi_json_escape(device_id "${device_id}") + _mqt_qdmi_json_escape(device_prefix "${device_prefix}") + string(MAKE_C_IDENTIFIER "${target}-${device}" manifest_stem) + set(manifest_name "${manifest_stem}.qdmi.json") + set(manifest "${CMAKE_CURRENT_BINARY_DIR}/$/${manifest_name}") + file( + GENERATE + OUTPUT "${manifest}" + CONTENT + "{\n \"schema-version\": 1,\n \"qdmi\": {\n \"devices\": [\n {\n \"id\": \"${device_id}\",\n \"library\": \"$\",\n \"prefix\": \"${device_prefix}\",\n \"enabled\": true\n }\n ]\n }\n}\n" + ) + else() + set(manifest "$/${manifest_name}") + endif() + get_target_property(device_imported ${device_target} IMPORTED) + if(NOT device_imported) + add_dependencies(${target} ${device}) + endif() + add_custom_command( + TARGET ${target} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different "$" + "$" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${manifest}" + "$/${manifest_name}") + endforeach() +endfunction() diff --git a/cmake/ExternalDependencies.cmake b/cmake/ExternalDependencies.cmake index 6556b51acc..daf04df5c1 100644 --- a/cmake/ExternalDependencies.cmake +++ b/cmake/ExternalDependencies.cmake @@ -92,24 +92,28 @@ FetchContent_Declare( FIND_PACKAGE_ARGS ${QDMI_MINIMUM_VERSION}) list(APPEND FETCH_PACKAGES qdmi) -set(SPDLOG_VERSION - 1.17.0 - CACHE STRING "spdlog version") -set(SPDLOG_URL https://github.com/gabime/spdlog/archive/refs/tags/v${SPDLOG_VERSION}.tar.gz) -# Add position independent code for spdlog, this is required for python bindings on linux -set(SPDLOG_BUILD_PIC ON) -set(SPDLOG_SYSTEM_INCLUDES - ON - CACHE INTERNAL "Treat the library headers like system headers") -cmake_dependent_option(MQT_CORE_SPDLOG_INSTALL "Install spdlog library" ON "MQT_CORE_INSTALL" OFF) -# Disable upstream spdlog install rules and install with explicit MQT components below. -set(SPDLOG_INSTALL - OFF - CACHE BOOL "Disable upstream spdlog install rules; handled by mqt-core" FORCE) -cmake_dependent_option(SPDLOG_BUILD_SHARED "Build spdlog as shared library" ON - "BUILD_MQT_CORE_SHARED_LIBS" OFF) -FetchContent_Declare(spdlog URL ${SPDLOG_URL} FIND_PACKAGE_ARGS ${SPDLOG_VERSION}) -list(APPEND FETCH_PACKAGES spdlog) +set(MQT_CORE_MANAGES_SPDLOG OFF) +if(NOT TARGET spdlog::spdlog) + set(SPDLOG_VERSION + 1.17.0 + CACHE STRING "spdlog version") + set(SPDLOG_URL https://github.com/gabime/spdlog/archive/refs/tags/v${SPDLOG_VERSION}.tar.gz) + # Add position independent code for spdlog, this is required for Python bindings on Linux. + set(SPDLOG_BUILD_PIC ON) + set(SPDLOG_SYSTEM_INCLUDES + ON + CACHE INTERNAL "Treat the library headers like system headers") + cmake_dependent_option(MQT_CORE_SPDLOG_INSTALL "Install spdlog library" ON "MQT_CORE_INSTALL" OFF) + # Disable upstream spdlog install rules and install with explicit MQT components below. + set(SPDLOG_INSTALL + OFF + CACHE BOOL "Disable upstream spdlog install rules; handled by mqt-core" FORCE) + cmake_dependent_option(SPDLOG_BUILD_SHARED "Build spdlog as shared library" ON + "BUILD_MQT_CORE_SHARED_LIBS" OFF) + FetchContent_Declare(spdlog URL ${SPDLOG_URL} FIND_PACKAGE_ARGS ${SPDLOG_VERSION}) + list(APPEND FETCH_PACKAGES spdlog) + set(MQT_CORE_MANAGES_SPDLOG ON) +endif() # Make all declared dependencies available. FetchContent_MakeAvailable(${FETCH_PACKAGES}) @@ -173,7 +177,7 @@ if(MQT_CORE_JSON_INSTALL AND TARGET nlohmann_json) endif() # Ensure external shared libraries end up in a common lib layout used by our RUNPATH -if(TARGET spdlog) +if(MQT_CORE_MANAGES_SPDLOG AND TARGET spdlog) set_target_properties( spdlog PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}" @@ -182,7 +186,8 @@ if(TARGET spdlog) endif() # Install spdlog with explicit MQT components. -if(MQT_CORE_SPDLOG_INSTALL +if(MQT_CORE_MANAGES_SPDLOG + AND MQT_CORE_SPDLOG_INSTALL AND TARGET spdlog AND TARGET spdlog_header_only) include(CMakePackageConfigHelpers) diff --git a/cmake/PackageAddTest.cmake b/cmake/PackageAddTest.cmake index d0e6baaaa3..488e267cd0 100644 --- a/cmake/PackageAddTest.cmake +++ b/cmake/PackageAddTest.cmake @@ -19,8 +19,10 @@ macro(PACKAGE_ADD_TEST testname linklibs) # discover tests gtest_discover_tests( ${testname} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" DISCOVERY_TIMEOUT 60) + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + # Defer the property name so it is not parsed as the discovery working directory. + PROPERTIES "$<1:WORKING_DIRECTORY>" "${CMAKE_CURRENT_SOURCE_DIR}" + VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" DISCOVERY_TIMEOUT 60) set_target_properties(${testname} PROPERTIES FOLDER tests) # Set c++ standard @@ -40,8 +42,10 @@ macro(PACKAGE_ADD_TEST_WITH_WORKING_DIR testname linklibs test_working_directory # discover tests gtest_discover_tests( ${testname} - WORKING_DIRECTORY ${test_working_directory} - PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${test_working_directory}" DISCOVERY_TIMEOUT 60) + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + # Defer the property name so it is not parsed as the discovery working directory. + PROPERTIES "$<1:WORKING_DIRECTORY>" "${test_working_directory}" VS_DEBUGGER_WORKING_DIRECTORY + "${test_working_directory}" DISCOVERY_TIMEOUT 60) set_target_properties(${testname} PROPERTIES FOLDER tests) # Set c++ standard diff --git a/cmake/mqt-core-config.cmake.in b/cmake/mqt-core-config.cmake.in index f8d1982f46..0855dc9466 100644 --- a/cmake/mqt-core-config.cmake.in +++ b/cmake/mqt-core-config.cmake.in @@ -34,6 +34,7 @@ if(TARGET MQT::Core) endif() include("${CMAKE_CURRENT_LIST_DIR}/AddMQTPythonBinding.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/AddMQTQDMIDevice.cmake") include("${CMAKE_CURRENT_LIST_DIR}/Cache.cmake") include("${CMAKE_CURRENT_LIST_DIR}/PackageAddTest.cmake") include("${CMAKE_CURRENT_LIST_DIR}/PreventInSourceBuilds.cmake") diff --git a/docs/ai_usage.md b/docs/ai_usage.md index 7f297cca20..f0d98cf5cc 100644 --- a/docs/ai_usage.md +++ b/docs/ai_usage.md @@ -31,41 +31,61 @@ not submit it. ### 2. Human in the Loop -> **Autonomous contributions from AI agents are not allowed.** +**Humans remain accountable for AI-assisted contributions.** -When using AI tools, you must be the driver. The AI is the assistant. +AI agents may implement changes and manage issue and pull request workflows when +a human explicitly authorizes the scope and relevant external actions. The human +remains the driver and is responsible for the resulting contribution. -- **Review:** You must read and review all AI-generated code or text before - submitting it. +- **Review:** You must read and review all AI-generated code and relevant public + text before merging or otherwise accepting the contribution. You remain + accountable for public text posted within an authorized scope. - **Edit:** AI-generated code often requires significant editing to meet project standards and correctness. - **Verify:** Ensure the code actually solves the problem and doesn't introduce subtle bugs or security vulnerabilities. +- **Authorize:** Explicitly authorize agents before they create, edit, submit, + or comment on issues and pull requests. A single authorization may cover a + clearly scoped task; per-message approval is not required. Remain accountable + for all external communication that agents submit on your behalf. Actions + outside the authorized scope require fresh authorization. ### 3. Communication -**Do not use AI tools to generate issue descriptions, pull request comments, or -code reviews.** +**AI tools may be used to prepare and manage issue and pull request workflows, +including descriptions, comments, and code reviews, when a human has authorized +the action and remains responsible for the result.** -We value your personal input and communication. LLMs are notoriously unreliable -and can produce "smart-sounding" but incorrect or irrelevant claims -("hallucinations"). Phrase your communications in your own words. It is more -important that we can follow your reasoning than that the text sounds "perfect". +AI-generated communication can be incorrect, irrelevant, or overly generic. Keep +it accurate, specific, and useful to reviewers and contributors. Do not post +low-quality, repetitive, or unsolicited messages. + +Every agent-authored or agent-edited public text body must begin with the +following visible disclosure: + +`🤖 *AI text below* 🤖` + +This requirement applies to issue and pull request descriptions, review bodies, +inline review comments, issue-style comments, replies, and other submitted text +bodies. Pull request and issue titles are exempt. When editing existing +human-authored text, preserve the original content and add the disclosure at the +beginning of the edited field. ### 4. Transparency and Disclosure Transparency helps the community understand the role of these tools and develop best practices. -**We encourage you to disclose any AI assistance.** This helps us understand how -these tools are being used and identify potential issues. You can disclose this -information in the following ways: +**AI assistance must be disclosed in the PR description.** This helps reviewers +understand how the tools were used and where human verification was applied. -- **Commit Messages**: Add a trailer to your commit message in the form - `Assisted-by: [Model Name] via [Tool Name]` (example: - `Assisted-by: Claude Sonnet 4.6 via GitHub Copilot`) -- **PR Description**: Mention the tool (name and version) and how it was used in - the PR description. +- **PR Description**: Briefly state how AI tools materially assisted the + contribution. If an agent authored or edited the description, use the visible + disclosure required in the [communication policy](#3-communication). +- **Commit Messages**: We recommend adding an + `Assisted-by: [Model Name] via [Tool Name]` trailer to commits prepared with + AI assistance. For example: + `Assisted-by: Claude Sonnet 4.6 via GitHub Copilot`. ### 5. Licensing and Copyright @@ -125,10 +145,10 @@ its contributors can thrive. - **"Good First Issues"**: Do not use AI tools to solve issues labeled as "good first issue". These are intended as learning opportunities for new contributors. Automating them defeats the purpose. -- **Spam**: Do not use AI to generate low-quality or repetitive comments/reviews - ("AI Slop"). -- **Unreviewed Code**: Submitting code that you, as a human, have not reviewed - and tested yourself. +- **Spam**: Do not use AI to generate low-quality, repetitive, or unsolicited + comments or reviews ("AI Slop"). +- **Unreviewed Code**: Merging or accepting code that you, as a human, have not + reviewed and tested yourself. ## Summary diff --git a/docs/conf.py b/docs/conf.py index c8893c795a..e48569be0f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -119,7 +119,7 @@ class CDAStyle(UnsrtStyle): """Custom style for including PDF links.""" - def format_url(self, _e: Entry) -> HRef: # noqa: PLR6301 + def format_url(self, _e: Entry) -> HRef: # ruff:ignore[no-self-use] """Format URL field as a link to the PDF. Returns: @@ -167,9 +167,9 @@ def format_url(self, _e: Entry) -> HRef: # noqa: PLR6301 read_the_docs_build = os.environ.get("READTHEDOCS", None) == "True" if read_the_docs_build: - subprocess.call("mkdir -p _build/doxygen && doxygen", shell=True) # noqa: S602, S607 - subprocess.call( # noqa: S602 - "mkdir -p api/cpp && breathe-apidoc -o api/cpp -m -f -g namespace _build/doxygen/xml/", # noqa: S607 + subprocess.call("mkdir -p _build/doxygen && doxygen", shell=True) # ruff:ignore[subprocess-popen-with-shell-equals-true, start-process-with-partial-path] + subprocess.call( # ruff:ignore[subprocess-popen-with-shell-equals-true] + "mkdir -p api/cpp && breathe-apidoc -o api/cpp -m -f -g namespace _build/doxygen/xml/", # ruff:ignore[start-process-with-partial-path] shell=True, ) diff --git a/docs/contributing.md b/docs/contributing.md index 38d511b079..7face58961 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -94,8 +94,14 @@ quality and licensing compliance. Please carefully read and follow our dedicated {doc}`ai_usage` before submitting any AI-assisted contribution. In short: **You are responsible for every line of code you submit**, and a -**human must always be in the loop**. We require disclosure of AI tool usage in -your PR description. +**human must always be in the loop**. Agents may perform coding and GitHub tasks +within an explicitly authorized scope, but you must review their work and remain +accountable for the result. Every agent-authored or agent-edited public text +body must begin with `🤖 *AI text below* 🤖`; issue and pull request titles are +exempt. AI assistance must be disclosed in the PR description. Commit-level +`Assisted-by: [Model Name] via [Tool Name]` trailers are recommended for commits +prepared with AI assistance. AI assistance must not be used for contributions to +issues labeled "good first issue". If you use an agent, it will automatically read the provided {code}`AGENTS.md`, which contains context and instructions to help the agent work on MQT Core. For @@ -592,7 +598,7 @@ The docs can then be found in the {code}`docs/_build` directory. If something goes wrong, the CI pipeline will notify you. Here are some tips for finding the cause of certain failures: -- If any of the {code}`CI / 🇨‌ Test` checks fail, this indicates build errors or +- If any of the {code}`CI / 🇨 Test` checks fail, this indicates build errors or test failures in the C++ part of the code base. Look through the respective logs on GitHub for any error or failure messages. @@ -609,7 +615,7 @@ finding the cause of certain failures: been raised by {code}`clang-tidy` when checking the C++ part of your changes for warnings or style guideline violations. The individual messages frequently provide helpful suggestions on how to fix the warnings. If you don't see any - messages, but the {code}`🇨‌ Lint / 🚨 Lint` check is red, click on the + messages, but the {code}`🇨 Lint / 🚨 Lint` check is red, click on the {code}`Details` link to see the full log of the check and a step summary. - If the {code}`pre-commit.ci` check fails, some of the {code}`prek` checks diff --git a/docs/qdmi/configuration.md b/docs/qdmi/configuration.md new file mode 100644 index 0000000000..a8190e8111 --- /dev/null +++ b/docs/qdmi/configuration.md @@ -0,0 +1,174 @@ +# QDMI device configuration + +MQT Core discovers QDMI device definitions from versioned JSON or TOML +configuration. Discovery only parses definitions. The QDMI client opens the +configured native libraries when its first session is allocated, while stable-ID +APIs open only the requested device. Configuration is therefore a trusted input. + +## Device definitions + +The following `qdmi.json` registers one device: + +```json +{ + "schema-version": 1, + "qdmi": { + "devices": [ + { + "id": "example.device", + "library": "libexample-device.so", + "prefix": "EXAMPLE", + "enabled": true, + "session": { + "base-url": "https://device.example", + "auth-file": "credentials.json", + "custom1": "device-specific" + } + } + ] + } +} +``` + +Every enabled definition requires a stable, unique `id`, a `library`, and a QDMI +symbol `prefix`. The `session` object supports `base-url`, `token`, `auth-file`, +`auth-url`, `username`, `password`, and `custom1` through `custom5`. + +Relative library and authentication-file paths are resolved against the file +that declared them. For `MQT_CORE_QDMI_CONFIG_JSON`, they resolve against the +current working directory. + +Unknown keys, invalid types, duplicate IDs within one source, unsupported schema +versions, and incomplete enabled definitions are hard errors. Diagnostics name +the source and configuration path. Credentials and session values are not +included in Driver warnings. + +## Discovery and precedence + +Definitions are merged field by field by ID, from lowest to highest precedence: + +1. generated `*.qdmi.json` fragments packaged beside the MQT Core Driver; +2. the system `qdmi.json`; +3. the user or XDG `qdmi.json`; +4. the nearest project `qdmi.json`, or `[tool.qdmi]` in `pyproject.toml` when no + dedicated file exists in that directory; +5. `MQT_CORE_QDMI_CONFIG_JSON`. + +On Unix, file configuration uses `/etc/mqt-core/qdmi.json` and then +`${XDG_CONFIG_HOME}/mqt-core/qdmi.json`, falling back to +`${HOME}/.config/mqt-core/qdmi.json`. On Windows, it uses the corresponding +`mqt-core/qdmi.json` files below `PROGRAMDATA` and `APPDATA`. + +An entry containing only its ID and `"enabled": false` masks an inherited +definition. Since definitions are merged field by field, a later definition with +the same ID must explicitly set `"enabled": true` to enable it again. Within one +directory, `qdmi.json` takes precedence over `pyproject.toml`. The final +disabled ID remains reserved, so fallback registration cannot silently re-enable +a device that an administrator disabled. + +`MQT_CORE_QDMI_CONFIG_FILE` replaces the system, user, and project levels while +retaining packaged built-ins. + +## Using configured devices + +The Driver opens the discovered definitions when the first client session is +allocated, and a failure to load one definition does not hide the remaining +devices. Registration alone does not initialize device libraries. + +```python +from mqt.core.fomac import Session + +for device in Session().get_devices(): + print(device.name()) +``` + +Set `MQT_CORE_QDMI_CONFIG_FILE` or `MQT_CORE_QDMI_CONFIG_JSON` before creating +the first session. Applications can also register a definition without loading +its library and open it later by stable ID: + +```python +from mqt.core.fomac import DeviceDefinition, open_device, register_device + +register_device( + DeviceDefinition( + "example.device", + "/path/to/libexample-device.so", + "EXAMPLE", + base_url="https://device.example", + ) +) +device = open_device("example.device") +``` + +Every `open_device` call creates a fresh device session while preserving the +registered defaults and stable ID. This lets separate backend instances use +different credentials without registering process-lifetime UUIDs. The returned +`Device` and any child `Device`, `Site`, `Operation`, or `Job` wrappers derived +from it keep that fresh device session alive. The session is released after the +last such wrapper is destroyed. + +Code paths that may be imported more than once can use +`register_device_if_absent(definition)`. It returns whether the definition was +inserted and ignores an existing or explicitly disabled stable ID; malformed +definitions still raise an error. + +The equivalent C++ registration operation is `qdmi::Driver::registerDevice`. +Duplicate IDs are rejected unless `replace` is true, and an opened definition +cannot be replaced. `qdmi::Driver::open(id)` returns the cached device. +`fomac::Session::openDevice(id, overrides)` returns a fresh device session and +does not add it to the QDMI client catalog. Runtime registrations and explicit +opens are not added to that catalog. + +Multiple definitions may refer to the same library and prefix. MQT Core reuses +the initialized library while creating a fresh QDMI device session, with its own +session parameters, for every definition. + +## Relocatable packages and static consumers + +Built-in targets generate manifests beside their runtime libraries in both build +and install trees. Library paths in those fragments contain only the target +filename, so moving an installed tree or Python wheel preserves discovery. +Automatic discovery searches relative to the MQT Core Driver, not every library +loaded by the process. An application using a separately installed device +implementation therefore copies its manifest beside the Driver or registers its +definition by stable ID. + +A fully static executable has no portable shared-module location. Place the +fragments beside the executable, point `MQT_CORE_QDMI_CONFIG_FILE` at a complete +configuration, or use `qdmi::Driver::registerDevice` and `qdmi::Driver::open`. +No install prefix is compiled into the manifests. + +An installed MQT Core CMake package provides a helper that colocates selected +device libraries and manifests with an executable: + +```cmake +find_package(mqt-core CONFIG REQUIRED) +add_executable(my-application main.cpp) +target_link_libraries(my-application PRIVATE MQT::CoreFoMaC) +mqt_copy_qdmi_runtime( + my-application + MQT::CoreQDMINaDevice + MQT::CoreQDMIScDevice + MQT::CoreQDMI_DDSIM_Device) +``` + +Inside an MQT Core build, omitting the device list copies every device +registered through `mqt_configure_qdmi_device`. Installed consumers select the +exported device targets they need, as shown above. + +An external device implementation does not need MQT Core as a build dependency. +It can export its stable ID and prefix as target metadata: + +```cmake +set_target_properties( + example-device + PROPERTIES QDMI_DEVICE_ID "example.device" + QDMI_DEVICE_PREFIX "EXAMPLE") +set_property( + TARGET example-device + APPEND + PROPERTY EXPORT_PROPERTIES QDMI_DEVICE_ID QDMI_DEVICE_PREFIX) +``` + +When `mqt_copy_qdmi_runtime` receives that built or imported target, it +generates the relocatable manifest while copying the device. diff --git a/docs/qdmi/driver.md b/docs/qdmi/driver.md index 37e38f9e8a..5635255cba 100644 --- a/docs/qdmi/driver.md +++ b/docs/qdmi/driver.md @@ -16,9 +16,33 @@ A QDMI Driver manages the communication between QDMI devices, such as [QDMI specification](https://munich-quantum-software-stack.github.io/QDMI/). It is responsible for loading the device, forwarding requests from the client to the device, and sending back the results. MQT Core's QDMI Driver, -{cpp:class}`qdmi::Driver`, comes with several preloaded devices that can be used -directly. Other devices can be loaded dynamically at runtime via -{cpp:func}`qdmi::Driver::addDynamicDeviceLibrary`. +{cpp:class}`qdmi::Driver`, comes with several preloaded devices when the bundled +devices are enabled. Other devices can be loaded dynamically at runtime via +{cpp:func}`qdmi::Driver::registerDevice` and {cpp:func}`qdmi::Driver::open`. +Built-in and external devices can also be registered through +[versioned QDMI device configuration](configuration.md). + +## Building the Bundled Devices + +Standalone MQT Core builds include the DDSIM, superconducting, and neutral-atom +QDMI device libraries by default. When MQT Core is embedded in another CMake +project using {code}`FetchContent` or {code}`add_subdirectory`, these device +libraries are disabled by default so the consumer does not build implementations +it may not use. They can be selected independently before making MQT Core +available: + +- {code}`BUILD_MQT_CORE_QDMI_DDSIM_DEVICE` +- {code}`BUILD_MQT_CORE_QDMI_NA_DEVICE` +- {code}`BUILD_MQT_CORE_QDMI_SC_DEVICE` + +For example, an embedded simulator consumer can enable only the DDSIM device, +while CUDA-Q can enable the DDSIM and superconducting devices used by its +integration tests. + +The QDMI driver and FoMaC libraries are available independently. Device-free +builds can register external device libraries through +[QDMI device configuration](configuration.md). When MQT Core's C++ tests are +enabled, device-specific tests are omitted with their corresponding device. ## Python Bindings diff --git a/docs/qdmi/index.md b/docs/qdmi/index.md index d26d2cbe52..039bffdd50 100644 --- a/docs/qdmi/index.md +++ b/docs/qdmi/index.md @@ -15,5 +15,6 @@ components, such as a [QDMI driver](driver.md), a NA QDMI Device DDSIM QDMI Device QDMI Driver +QDMI device configuration QDMI-Qiskit Backend ``` diff --git a/docs/qdmi/qdmi_backend.md b/docs/qdmi/qdmi_backend.md index 21f6ed1894..882a3cfb0d 100644 --- a/docs/qdmi/qdmi_backend.md +++ b/docs/qdmi/qdmi_backend.md @@ -242,7 +242,7 @@ including: `swap`, `iswap`, `dcx`, `ecr` - **Two-qubit parametric gates**: `cp`, `cu1`, `cu3`, `crx`, `cry`, `crz`, `rxx`, `ryy`, `rzz`, `rzx`, `xx_plus_yy`, `xx_minus_yy` -- **Three-qubit gates**: `ccx`, `ccz`, `cswap` +- **Three-qubit gates**: `ccx`, `ccz`, `cswap`, `rccx` - **Multi-controlled gates**: `mcx`, `mcz`, `mcp`, `mcrx`, `mcry`, `mcrz` - **Non-unitary operations**: `reset`, `measure` diff --git a/docs/qir/index.md b/docs/qir/index.md index 1236354533..1c986de0fc 100644 --- a/docs/qir/index.md +++ b/docs/qir/index.md @@ -42,5 +42,43 @@ The `mqt-core-qir-runner` can be used to execute a QIR file (typically with a ./build/bin/mqt-core-qir-runner bell.ll ``` -This will simulate the circuit and print the measurement results to the console. -The runner supports the QIR Base Profile. +The runner prints the program's outputs to the console in one of the two +[QIR Output Schemas][output-schemas] (Labeled or Ordered): the two `HEADER` +records announce the schema, and each shot is wrapped in `START` and `END` +records with a `METADATA\toutput_labeling_schema\t` line inside. + +The active schema is selected by the `output_labeling_schema` function attribute +on the entry-point function of the QIR program. The value `ordered` selects +Ordered; anything else, or a missing attribute, selects Labeled. + +[output-schemas]: https://github.com/qir-alliance/qir-spec/tree/main/specification/output_schemas + +### QIR Support in the DDSIM QDMI Device + +The QDMI device accepts jobs in the following program formats: QASM2, QASM3, QIR +Base/Adaptive Profile Module (LLVM bitcode), and QIR Base/Adaptive Profile +String (LLVM assembly). These QIR formats are only supported when the +`BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR` CMake option is enabled. It is enabled by +default, but depends on `BUILD_MQT_CORE_MLIR` being set. + +FoMaC C++ applications submit textual programs through the +`Device::submitJob(const std::string&, ...)` overload, which includes the +terminating null byte required by QDMI. Binary module payloads use the +`Device::submitJob(std::span, ...)` overload instead. It +preserves embedded null bytes and submits exactly the span's size without +appending a terminator. `Job::getProgramBytes()` retrieves such a payload +without interpreting its format or removing terminal null bytes; the existing +`Job::getProgram()` remains the textual, null-terminated accessor. It rejects +known binary and non-text formats based on their QDMI format identifier, even if +their payload happens to end in a null byte. + +The Python API follows the same distinction: pass `str` to `Device.submit_job` +for a textual program and `bytes` for an exact binary payload. +`Job.program_bytes` always returns the unmodified payload, while `Job.program` +expects a null-terminated UTF-8 text payload and rejects known binary or +non-text formats. + +The generic submission APIs intentionally reject QDMI calibration and batch-job +formats. Calibration jobs do not carry a program, while batch jobs contain job +handles rather than serialized program bytes. Their format identifiers remain +available for capability discovery; they require dedicated typed APIs. diff --git a/include/mqt-core/dd/DDDefinitions.hpp b/include/mqt-core/dd/DDDefinitions.hpp index 1c7b4319a5..edb76033dd 100644 --- a/include/mqt-core/dd/DDDefinitions.hpp +++ b/include/mqt-core/dd/DDDefinitions.hpp @@ -81,6 +81,11 @@ using SparseCMat = std::unordered_map, using GateMatrix = std::array, NEDGE>; using TwoQubitGateMatrix = std::array, NEDGE>, NEDGE>; +/// Dimension of a three-qubit gate matrix (`2^3`). +static constexpr std::uint8_t THREE_QUBIT_GATE_DIM = 8; +using ThreeQubitGateMatrix = + std::array, THREE_QUBIT_GATE_DIM>, + THREE_QUBIT_GATE_DIM>; /** * @brief Converts a decimal number to a binary string (big endian) diff --git a/include/mqt-core/dd/GateMatrixDefinitions.hpp b/include/mqt-core/dd/GateMatrixDefinitions.hpp index b5252303be..e9f195c56a 100644 --- a/include/mqt-core/dd/GateMatrixDefinitions.hpp +++ b/include/mqt-core/dd/GateMatrixDefinitions.hpp @@ -40,4 +40,13 @@ GateMatrix opToSingleQubitGateMatrix(qc::OpType t, TwoQubitGateMatrix opToTwoQubitGateMatrix(qc::OpType t, const std::vector& params = {}); +/** + * @brief Converts a given quantum operation to a three-qubit gate matrix + * @param t The quantum operation to convert + * @param params The parameters of the quantum operation + * @return The three-qubit gate matrix representation of the quantum operation + */ +ThreeQubitGateMatrix +opToThreeQubitGateMatrix(qc::OpType t, const std::vector& params = {}); + } // namespace dd diff --git a/include/mqt-core/dd/Package.hpp b/include/mqt-core/dd/Package.hpp index 1ad91f8b71..459b41cec7 100644 --- a/include/mqt-core/dd/Package.hpp +++ b/include/mqt-core/dd/Package.hpp @@ -432,6 +432,49 @@ class Package { const qc::Controls& controls, qc::Qubit target0, qc::Qubit target1); + /** + * @brief Creates the DD for a three-qubit gate + * @param mat Matrix representation of the gate + * @param target0 First target qubit + * @param target1 Second target qubit + * @param target2 Third target qubit + * @return DD representing the gate + * @throws std::runtime_error if the number of qubits is larger than the + * package configuration + */ + mEdge makeThreeQubitGateDD(const ThreeQubitGateMatrix& mat, qc::Qubit target0, + qc::Qubit target1, qc::Qubit target2); + + /** + * @brief Creates the DD for a three-qubit gate + * @param mat Matrix representation of the gate + * @param control Control qubit of the three-qubit gate + * @param target0 First target qubit + * @param target1 Second target qubit + * @param target2 Third target qubit + * @return DD representing the gate + * @throws std::runtime_error if the number of qubits is larger than the + * package configuration + */ + mEdge makeThreeQubitGateDD(const ThreeQubitGateMatrix& mat, + const qc::Control& control, qc::Qubit target0, + qc::Qubit target1, qc::Qubit target2); + + /** + * @brief Creates the DD for a three-qubit gate + * @param mat Matrix representation of the gate + * @param controls Control qubits of the three-qubit gate + * @param target0 First target qubit + * @param target1 Second target qubit + * @param target2 Third target qubit + * @return DD representing the gate + * @throws std::runtime_error if the number of qubits is larger than the + * package configuration + */ + mEdge makeThreeQubitGateDD(const ThreeQubitGateMatrix& mat, + const qc::Controls& controls, qc::Qubit target0, + qc::Qubit target1, qc::Qubit target2); + /** * @brief Converts a given matrix to a decision diagram * @param matrix A complex matrix to convert to a DD. diff --git a/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index 472d6bda05..67e88d84a1 100644 --- a/include/mqt-core/fomac/FoMaC.hpp +++ b/include/mqt-core/fomac/FoMaC.hpp @@ -11,6 +11,7 @@ #pragma once #include "qdmi/common/Common.hpp" +#include "qdmi/driver/Driver.hpp" #include "qdmi/types.h" #include @@ -21,11 +22,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -308,7 +311,7 @@ struct SessionConfig { /// Authentication token std::optional token; /// Path to file containing authentication information - std::optional authFile; + std::optional authFile; /// URL to authentication server std::optional authUrl; /// Username for authentication @@ -351,6 +354,16 @@ class Session { */ [[nodiscard]] static Device createSessionlessDevice(QDMI_Device device); + /** + * @brief Opens a registered QDMI device as a fresh device session. + * @param id Stable registered device ID. + * @param overrides Session values that replace registered defaults. + * @return An owning device wrapper for the new session. + */ + [[nodiscard]] static Device + openDevice(std::string_view id, + const qdmi::DeviceSessionConfig& overrides = {}); + /** * @brief Constructs a new QDMI Session with optional authentication. * @param config Optional session configuration containing authentication @@ -401,7 +414,7 @@ static_assert(std::is_move_assignable()); class Device { public: // NOLINTNEXTLINE(google-explicit-constructor, *-explicit-conversions) - operator QDMI_Device() const { return device_; } + operator QDMI_Device() const { return device_.get(); } /// @see QDMI_DEVICE_PROPERTY_NAME [[nodiscard]] std::string getName() const; @@ -494,14 +507,21 @@ class Device { const auto qdmiProperty = detail::toDeviceProperty(property); return detail::queryCustomValue( [this, qdmiProperty](const size_t size, void* value, size_t* sizeRet) { - return QDMI_device_query_device_property(device_, qdmiProperty, size, - value, sizeRet); + return QDMI_device_query_device_property(device_.get(), qdmiProperty, + size, value, sizeRet); }, "custom device property " + std::to_string(static_cast(property))); } - /// @see QDMI_job_submit + /** + * @brief Submits a textual program. + * @details The terminating null byte required by QDMI text formats is + * included in the submitted payload. + * @throws std::invalid_argument If the format requires binary submission or + * does not carry a generic program payload. + * @see QDMI_job_submit + */ [[nodiscard]] Job submitJob( const std::string& program, QDMI_Program_Format format, size_t numShots, const std::optional& custom1 = std::nullopt, @@ -510,6 +530,23 @@ class Device { const std::optional& custom4 = std::nullopt, const std::optional& custom5 = std::nullopt) const; + /** + * @brief Submits a binary program. + * @details The bytes are submitted exactly as provided without appending a + * null byte. + * @throws std::invalid_argument If the format does not carry a generic + * program payload. + * @see QDMI_job_submit + */ + [[nodiscard]] Job submitJob( + std::span program, QDMI_Program_Format format, + size_t numShots, + const std::optional& custom1 = std::nullopt, + const std::optional& custom2 = std::nullopt, + const std::optional& custom3 = std::nullopt, + const std::optional& custom4 = std::nullopt, + const std::optional& custom5 = std::nullopt) const; + auto operator<=>(const Device&) const noexcept = default; private: @@ -517,7 +554,15 @@ class Device { * @brief Constructs a Device object from a QDMI_Device handle. * @param device The QDMI_Device handle to wrap. */ - explicit Device(QDMI_Device device) : device_(device) {} + explicit Device(QDMI_Device device) + : device_(device, [](QDMI_Device_impl_d*) {}) {} + + /** + * @brief Constructs a wrapper that retains an owning session. + * @param device The QDMI device handle to wrap. + */ + explicit Device(std::shared_ptr device) + : device_(std::move(device)) {} /// Query a device property. template @@ -527,8 +572,8 @@ class Device { if constexpr (string_or_optional_string) { size_t size = 0; - auto result = - QDMI_device_query_device_property(device_, prop, 0, nullptr, &size); + auto result = QDMI_device_query_device_property(device_.get(), prop, 0, + nullptr, &size); if constexpr (is_optional) { if (result == QDMI_ERROR_NOTSUPPORTED) { @@ -538,15 +583,15 @@ class Device { qdmi::throwIfError(result, msg); std::string value(size - 1, '\0'); - result = QDMI_device_query_device_property(device_, prop, size, + result = QDMI_device_query_device_property(device_.get(), prop, size, value.data(), nullptr); qdmi::throwIfError(result, msg); return value; } else if constexpr (maybe_optional_size_constructible_contiguous_range< T>) { size_t size = 0; - auto result = - QDMI_device_query_device_property(device_, prop, 0, nullptr, &size); + auto result = QDMI_device_query_device_property(device_.get(), prop, 0, + nullptr, &size); if constexpr (is_optional) { if (result == QDMI_ERROR_NOTSUPPORTED) { @@ -557,14 +602,14 @@ class Device { qdmi::throwIfError(result, msg); remove_optional_t value( size / sizeof(typename remove_optional_t::value_type)); - result = QDMI_device_query_device_property(device_, prop, size, + result = QDMI_device_query_device_property(device_.get(), prop, size, value.data(), nullptr); qdmi::throwIfError(result, msg); return value; } else { remove_optional_t value{}; const auto result = QDMI_device_query_device_property( - device_, prop, sizeof(remove_optional_t), &value, nullptr); + device_.get(), prop, sizeof(remove_optional_t), &value, nullptr); if constexpr (is_optional) { if (result == QDMI_ERROR_NOTSUPPORTED) { @@ -581,7 +626,7 @@ class Device { const CustomJobParameter& value); /// @brief The underlying device pointer. - QDMI_Device device_; + std::shared_ptr device_; friend class Session; }; @@ -598,6 +643,10 @@ class Device { */ class Job { public: + Job(Job&&) noexcept = default; + + auto operator=(Job&& other) noexcept -> Job&; + // NOLINTNEXTLINE(google-explicit-constructor, *-explicit-conversions) operator QDMI_Job() const { return job_.get(); } @@ -621,9 +670,18 @@ class Job { /// Get the program format [[nodiscard]] QDMI_Program_Format getProgramFormat() const; - /// Get the program to be executed + /** + * @brief Gets a textual program without its terminating null byte. + * @throws std::invalid_argument If the format is not textual or the device + * does not return a null-terminated payload. + */ [[nodiscard]] std::string getProgram() const; + /** + * @brief Gets the submitted program bytes exactly as returned by the device. + */ + [[nodiscard]] std::vector getProgramBytes() const; + /// Get the number of shots [[nodiscard]] size_t getNumShots() const; @@ -716,8 +774,16 @@ class Job { /** * @brief Constructs a Job object from a QDMI_Job handle. * @param job The QDMI_Job handle to wrap. + * @param device The device that owns the job. */ - explicit Job(QDMI_Job job) : job_(job, QDMI_job_free) {} + explicit Job(QDMI_Job job, std::shared_ptr device) + : device_(std::move(device)), job_(job, QDMI_job_free) {} + + /** + * @brief Ownership of the device session that owns the job. + * @note Declared before `job_` so the job is freed before its device. + */ + std::shared_ptr device_; std::unique_ptr job_{ nullptr, QDMI_job_free}; @@ -797,8 +863,8 @@ class Site { const auto qdmiProperty = detail::toSiteProperty(property); return detail::queryCustomValue( [this, qdmiProperty](const size_t size, void* value, size_t* sizeRet) { - return QDMI_device_query_site_property(*device_, site_, qdmiProperty, - size, value, sizeRet); + return QDMI_device_query_site_property( + device_.get(), site_, qdmiProperty, size, value, sizeRet); }, "custom site property " + std::to_string(static_cast(property))); @@ -809,18 +875,19 @@ class Site { private: /** * @brief Constructs a Site object from a QDMI_Site handle. - * @param device The device that owns the site. + * @param device The QDMI device handle that owns the site. * @param site The QDMI_Site handle to wrap. */ - Site(const Device* device, QDMI_Site site) : device_(device), site_(site) {} + Site(std::shared_ptr device, QDMI_Site site) + : device_(std::move(device)), site_(site) {} /// Query a site property. template [[nodiscard]] T queryProperty(const QDMI_Site_Property prop) const { if constexpr (string_or_optional_string) { size_t size = 0; - const auto result = QDMI_device_query_site_property(*device_, site_, prop, - 0, nullptr, &size); + const auto result = QDMI_device_query_site_property( + device_.get(), site_, prop, 0, nullptr, &size); if constexpr (is_optional) { if (result == QDMI_ERROR_NOTSUPPORTED) { return std::nullopt; @@ -829,15 +896,16 @@ class Site { qdmi::throwIfError(result, std::string("Querying size") + qdmi::toString(prop)); std::string value(size - 1, '\0'); - qdmi::throwIfError(QDMI_device_query_site_property(*device_, site_, prop, - size, value.data(), - nullptr), + qdmi::throwIfError(QDMI_device_query_site_property(device_.get(), site_, + prop, size, + value.data(), nullptr), std::string("Querying ") + qdmi::toString(prop)); return value; } else { remove_optional_t value{}; const auto result = QDMI_device_query_site_property( - *device_, site_, prop, sizeof(remove_optional_t), &value, nullptr); + device_.get(), site_, prop, sizeof(remove_optional_t), &value, + nullptr); if constexpr (is_optional) { if (result == QDMI_ERROR_NOTSUPPORTED) { return std::nullopt; @@ -849,8 +917,8 @@ class Site { } } - /// @brief A pointer to the device that owns the site. - Device const* device_; + /// @brief The QDMI device handle that owns the site. + std::shared_ptr device_; /// @brief The underlying QDMI_Site object. QDMI_Site site_; @@ -963,7 +1031,7 @@ class Operation { [this, qdmiProperty, &qdmiSites, ¶ms](const size_t size, void* value, size_t* sizeRet) { return QDMI_device_query_operation_property( - *device_, operation_, qdmiSites.size(), qdmiSites.data(), + device_.get(), operation_, qdmiSites.size(), qdmiSites.data(), params.size(), params.data(), qdmiProperty, size, value, sizeRet); }, "custom operation property " + @@ -975,11 +1043,12 @@ class Operation { private: /** * @brief Constructs an Operation object from a QDMI_Operation handle. - * @param device The device that owns the site. + * @param device The QDMI device handle that owns the operation. * @param operation The QDMI_Operation handle to wrap. */ - Operation(const Device* device, QDMI_Operation operation) - : device_(device), operation_(operation) {} + Operation(std::shared_ptr device, + QDMI_Operation operation) + : device_(std::move(device)), operation_(operation) {} /// Query an operation property. template @@ -995,8 +1064,8 @@ class Operation { if constexpr (string_or_optional_string) { size_t size = 0; auto result = QDMI_device_query_operation_property( - *device_, operation_, sites.size(), qdmiSites.data(), params.size(), - params.data(), prop, 0, nullptr, &size); + device_.get(), operation_, sites.size(), qdmiSites.data(), + params.size(), params.data(), prop, 0, nullptr, &size); if constexpr (is_optional) { if (result == QDMI_ERROR_NOTSUPPORTED) { return std::nullopt; @@ -1005,16 +1074,16 @@ class Operation { qdmi::throwIfError(result, msg); std::string value(size - 1, '\0'); result = QDMI_device_query_operation_property( - *device_, operation_, sites.size(), qdmiSites.data(), params.size(), - params.data(), prop, size, value.data(), nullptr); + device_.get(), operation_, sites.size(), qdmiSites.data(), + params.size(), params.data(), prop, size, value.data(), nullptr); qdmi::throwIfError(result, msg); return value; } else if constexpr (maybe_optional_size_constructible_contiguous_range< T>) { size_t size = 0; auto result = QDMI_device_query_operation_property( - *device_, operation_, sites.size(), qdmiSites.data(), params.size(), - params.data(), prop, 0, nullptr, &size); + device_.get(), operation_, sites.size(), qdmiSites.data(), + params.size(), params.data(), prop, 0, nullptr, &size); if constexpr (is_optional) { if (result == QDMI_ERROR_NOTSUPPORTED) { return std::nullopt; @@ -1024,15 +1093,16 @@ class Operation { remove_optional_t value( size / sizeof(typename remove_optional_t::value_type)); result = QDMI_device_query_operation_property( - *device_, operation_, sites.size(), qdmiSites.data(), params.size(), - params.data(), prop, size, value.data(), nullptr); + device_.get(), operation_, sites.size(), qdmiSites.data(), + params.size(), params.data(), prop, size, value.data(), nullptr); qdmi::throwIfError(result, msg); return value; } else { remove_optional_t value{}; const auto result = QDMI_device_query_operation_property( - *device_, operation_, sites.size(), qdmiSites.data(), params.size(), - params.data(), prop, sizeof(remove_optional_t), &value, nullptr); + device_.get(), operation_, sites.size(), qdmiSites.data(), + params.size(), params.data(), prop, sizeof(remove_optional_t), + &value, nullptr); if constexpr (is_optional) { if (result == QDMI_ERROR_NOTSUPPORTED) { return std::nullopt; @@ -1043,8 +1113,8 @@ class Operation { } } - /// @brief A pointer to the device that owns the operation. - Device const* device_; + /// @brief The QDMI device handle that owns the operation. + std::shared_ptr device_; /// @brief The underlying QDMI_Operation object. QDMI_Operation operation_; diff --git a/include/mqt-core/ir/QuantumComputation.hpp b/include/mqt-core/ir/QuantumComputation.hpp index 68f2e295b1..2941bb67cb 100644 --- a/include/mqt-core/ir/QuantumComputation.hpp +++ b/include/mqt-core/ir/QuantumComputation.hpp @@ -316,6 +316,17 @@ class QuantumComputation { #undef DECLARE_TWO_TARGET_TWO_PARAMETER_OPERATION +#define DECLARE_THREE_TARGET_OPERATION(op) \ + void op(const Qubit target0, const Qubit target1, const Qubit target2); \ + void c##op(const Control& control, Qubit target0, Qubit target1, \ + Qubit target2); \ + void mc##op(const Controls& controls, Qubit target0, Qubit target1, \ + Qubit target2); + + DECLARE_THREE_TARGET_OPERATION(rccx) + +#undef DECLARE_THREE_TARGET_OPERATION + void measure(Qubit qubit, std::size_t bit); void measure(const Targets& qubits, const std::vector& bits); @@ -519,6 +530,8 @@ class QuantumComputation { void checkQubitRange(Qubit qubit, const Controls& controls) const; void checkQubitRange(Qubit qubit0, Qubit qubit1, const Controls& controls) const; + void checkQubitRange(Qubit qubit0, Qubit qubit1, Qubit qubit2, + const Controls& controls) const; void checkQubitRange(const std::vector& qubits) const; void checkBitRange(Bit bit) const; void checkBitRange(const std::vector& bits) const; diff --git a/include/mqt-core/ir/operations/OpType.hpp b/include/mqt-core/ir/operations/OpType.hpp index 9ef3896c92..4ad8e17bbd 100644 --- a/include/mqt-core/ir/operations/OpType.hpp +++ b/include/mqt-core/ir/operations/OpType.hpp @@ -67,6 +67,15 @@ std::string shortName(OpType opType); } } +[[nodiscard]] constexpr bool isThreeQubitGate(const OpType opType) { + switch (opType) { + case RCCX: + return true; + default: + return false; + } +} + /** * @brief Checks if given OpType is a single qubit gate */ diff --git a/include/mqt-core/ir/operations/OpType.inc b/include/mqt-core/ir/operations/OpType.inc index ca3a225749..2450c92933 100644 --- a/include/mqt-core/ir/operations/OpType.inc +++ b/include/mqt-core/ir/operations/OpType.inc @@ -46,6 +46,7 @@ HANDLE_OP_TYPE(25, RZZ, OpTypeDiag, "rzz") HANDLE_OP_TYPE(26, RZX, OpTypeNone, "rzx") HANDLE_OP_TYPE(27, XXminusYY, OpTypeNone, "xx_minus_yy") HANDLE_OP_TYPE(28, XXplusYY, OpTypeNone, "xx_plus_yy") +HANDLE_OP_TYPE(33, RCCX, OpTypeNone, "rccx") // Compound Operation HANDLE_OP_TYPE(29, Compound, OpTypeNone, "compound") diff --git a/include/mqt-core/ir/operations/StandardOperation.hpp b/include/mqt-core/ir/operations/StandardOperation.hpp index 58ab0141be..9db74aa1e7 100644 --- a/include/mqt-core/ir/operations/StandardOperation.hpp +++ b/include/mqt-core/ir/operations/StandardOperation.hpp @@ -134,6 +134,9 @@ class StandardOperation : public Operation { void dumpGateType(std::ostream& of, std::ostringstream& op, const QubitIndexToRegisterMap& qubitMap) const; + void dumpOpenQASMGateOperands(std::ostream& of, + const QubitIndexToRegisterMap& qubitMap) const; + void dumpControls(std::ostringstream& op) const; }; diff --git a/include/mqt-core/qasm3/StdGates.hpp b/include/mqt-core/qasm3/StdGates.hpp index 4bcdc66ad2..a02d7b009b 100644 --- a/include/mqt-core/qasm3/StdGates.hpp +++ b/include/mqt-core/qasm3/StdGates.hpp @@ -27,14 +27,7 @@ const std::string STDGATES = // Non-natively supported gates from // https://github.com/Qiskit/qiskit/blob/main/qiskit/qasm/libs/qelib1.inc -const std::string QE1LIB = "gate rccx a, b, c {\n" - " u2(0, pi) c; u1(pi/4) c; \n" - " cx b, c; u1(-pi/4) c; \n" - " cx a, c; u1(pi/4) c; \n" - " cx b, c; u1(-pi/4) c; \n" - " u2(0, pi) c; \n" - "}\n" - "gate rc3x a,b,c,d {\n" +const std::string QE1LIB = "gate rc3x a,b,c,d {\n" " u2(0,pi) d; u1(pi/4) d; \n" " cx c,d; u1(-pi/4) d; u2(0,pi) d; \n" " cx a,d; u1(pi/4) d; \n" @@ -231,5 +224,8 @@ const std::map> STANDARD_GATES = { .nTargets = 2, .nParameters = 2, .type = qc::XXplusYY}))}, + {"rccx", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 3, .nParameters = 0, .type = qc::RCCX}))}, }; } // namespace qasm3 diff --git a/include/mqt-core/qdmi/common/Common.hpp b/include/mqt-core/qdmi/common/Common.hpp index c3586e6013..0e21332eea 100644 --- a/include/mqt-core/qdmi/common/Common.hpp +++ b/include/mqt-core/qdmi/common/Common.hpp @@ -249,6 +249,8 @@ constexpr auto toString(const QDMI_Device_Session_Parameter param) -> const return "USERNAME"; case QDMI_DEVICE_SESSION_PARAMETER_PASSWORD: return "PASSWORD"; + case QDMI_DEVICE_SESSION_PARAMETER_CHILDDEVICE: + return "CHILD DEVICE"; case QDMI_DEVICE_SESSION_PARAMETER_MAX: return "MAX"; case QDMI_DEVICE_SESSION_PARAMETER_CUSTOM1: @@ -386,6 +388,8 @@ constexpr auto toString(const QDMI_Device_Property prop) -> const char* { return "PULSE SUPPORT"; case QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS: return "SUPPORTED PROGRAM FORMATS"; + case QDMI_DEVICE_PROPERTY_CHILDDEVICES: + return "CHILD DEVICES"; case QDMI_DEVICE_PROPERTY_MAX: return "MAX"; case QDMI_DEVICE_PROPERTY_CUSTOM1: diff --git a/include/mqt-core/qdmi/devices/dd/Device.hpp b/include/mqt-core/qdmi/devices/dd/Device.hpp index d6e4350f13..38b7c5e203 100644 --- a/include/mqt-core/qdmi/devices/dd/Device.hpp +++ b/include/mqt-core/qdmi/devices/dd/Device.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -30,6 +31,8 @@ #include #include #include +#include +#include namespace qdmi::dd { class Device final : public Singleton { @@ -189,8 +192,11 @@ struct MQT_DDSIM_QDMI_Device_Job_impl_d { /// The program format QDMI_Program_Format format_ = QDMI_PROGRAM_FORMAT_QASM3; - /// The quantum program associated with the job - std::string program_; + /// The quantum program associated with the job. + /// Text formats (QASM2/3, QIR Base/Adaptive String) are stored as + /// @c std::string; binary formats (QIR Base/Adaptive Module) are stored as + /// @c std::vector. + std::variant> program_; /// The number of shots for the job size_t numShots_ = 1024U; @@ -234,6 +240,41 @@ struct MQT_DDSIM_QDMI_Device_Job_impl_d { auto getProbabilities(size_t size, void* data, size_t* sizeRet) -> QDMI_STATUS; + /// Run @p body on a worker thread with the standard job lifecycle: + /// - increase the running-job count, + /// - set status to RUNNING, + /// - run @p body, + /// - set status to DONE or FAILED, and + /// - decrease the running-job count. + /// Typically, @p body will: + /// - parse the program, + /// - run or simulate it, and + /// - store the results in the job's output fields. + /// @returns @c QDMI_SUCCESS once the worker has been spawned. + /// Failures inside @p body are reported through the job status (FAILED), + /// not through the return value. + auto submitProgramAsync(std::function body) -> QDMI_STATUS; + + /// Submit a QASM 2 or QASM 3 program. + /// Dispatches to the sampling or the state-extraction helper depending on + /// @c numShots_. + auto submitQASMProgram() -> QDMI_STATUS; + /// Sampling path for a QASM program (@c numShots_ > 0). + auto submitQASMProgramSampling() -> QDMI_STATUS; + /// State-extraction path for a QASM program (@c numShots_ == 0). + auto submitQASMProgramStateExtraction() -> QDMI_STATUS; + +#ifdef BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR + /// Submit a QIR Base/Adaptive Module or String program. + /// Dispatches to the sampling or the state-extraction helper depending on + /// @c numShots_. + auto submitQIRProgram() -> QDMI_STATUS; + /// Sampling path for a QIR program (@c numShots_ > 0). + auto submitQIRProgramSampling() -> QDMI_STATUS; + /// State-extraction path for a QIR Base Profile program (@c numShots_ == 0). + auto submitQIRProgramStateExtraction() -> QDMI_STATUS; +#endif + public: /// Constructor for the MQT_DDSIM_QDMI_Device_Job_impl_d. explicit MQT_DDSIM_QDMI_Device_Job_impl_d( @@ -251,6 +292,15 @@ struct MQT_DDSIM_QDMI_Device_Job_impl_d { /** * @brief Sets a parameter for the job. + * @note When setting @c QDMI_DEVICE_JOB_PARAMETER_PROGRAM, the device uses + * the current @c QDMI_DEVICE_JOB_PARAMETER_PROGRAMFORMAT to decide whether + * the payload's wire @p size: + * - includes a trailing @c '\0' (text formats: QASM2, QASM3, + * QIR Base/Adaptive String) or + * - is the exact byte count (binary formats: QIR Base/Adaptive Module). + * Callers should therefore set @c PROGRAMFORMAT before @c PROGRAM. + * The default of @c QDMI_PROGRAM_FORMAT_QASM3 is assumed if @c PROGRAMFORMAT + * is not set. * @see MQT_DDSIM_QDMI_device_job_set_parameter */ auto setParameter(QDMI_Device_Job_Parameter param, size_t size, diff --git a/include/mqt-core/qdmi/driver/Driver.hpp b/include/mqt-core/qdmi/driver/Driver.hpp index e45ea7926f..6afa589b5a 100644 --- a/include/mqt-core/qdmi/driver/Driver.hpp +++ b/include/mqt-core/qdmi/driver/Driver.hpp @@ -17,12 +17,19 @@ #include #include +#include #include #include #include +#include #include +#include #include +namespace fomac { +class Session; +} + namespace qdmi { /** @@ -36,7 +43,7 @@ struct DeviceSessionConfig { /// Authentication token std::optional token; /// Path to file containing authentication information - std::optional authFile; + std::optional authFile; /// URL to authentication server std::optional authUrl; /// Username for authentication @@ -55,6 +62,22 @@ struct DeviceSessionConfig { std::optional custom5; }; +/** + * @brief Stable registration for a QDMI device. + * @details Registration records this metadata without loading the native + * library. Use @ref Driver::open to create the corresponding device session. + */ +struct DeviceDefinition { + /// Stable identifier used to open this device. + std::string id; + /// Path to the native QDMI device library. + std::filesystem::path library; + /// Prefix used for the QDMI device interface functions. + std::string prefix; + /// Parameters applied before the device session is initialized. + DeviceSessionConfig session; +}; + /** * @brief Definition of the device library. * @details The device library contains function pointers to the QDMI @@ -347,14 +370,16 @@ struct QDMI_Session_impl_d { private: /// @brief The status of the session. qdmi::SessionStatus status_ = qdmi::SessionStatus::ALLOCATED; - /// @brief A pointer to the list of all devices. - const std::vector>* devices_; + /// @brief Snapshot of devices visible when this session was allocated. + std::vector devices_; public: /// @brief Constructor for the QDMI session. explicit QDMI_Session_impl_d( - const std::vector>& devices) - : devices_(&devices) {} + const std::vector>& devices); + + /// @brief Constructor from an explicit device-handle snapshot. + explicit QDMI_Session_impl_d(const std::vector& devices); /** * @brief Initializes the session. @@ -380,14 +405,15 @@ struct QDMI_Session_impl_d { namespace qdmi { /** * @brief The MQT QDMI driver class. - * @details This driver loads all statically known and linked QDMI device - * libraries. Additional devices can be added dynamically. + * @details This driver discovers configured QDMI device definitions and opens + * their libraries. Additional definitions can be registered at runtime. * @note This class is a singleton that manages the QDMI libraries and * sessions. It is responsible for loading the libraries, allocating sessions, * and providing access to the devices. */ class Driver final : public Singleton { friend class Singleton; + friend class fomac::Session; /// @brief Private constructor to enforce the singleton pattern. Driver(); @@ -397,6 +423,24 @@ class Driver final : public Singleton { */ std::vector> devices_; + /// @brief Registered definitions in stable registration order. + std::vector definitions_; + + /// @brief IDs disabled by the highest-precedence configuration source. + std::unordered_set disabledDeviceIds_; + + /// @brief Initially discovered definitions visible to the client API. + std::vector clientDefinitionIds_; + + /// @brief Materialized devices exposed through the client API. + std::vector clientDevices_; + + /// @brief Whether the configured client device catalog has been opened. + bool clientCatalogMaterialized_ = false; + + /// @brief Opened devices indexed by their stable registration ID. + std::unordered_map openedDevices_; + /** * @brief Map of sessions to their corresponding unique pointers to * QDMI_Session_impl_d objects. @@ -404,24 +448,48 @@ class Driver final : public Singleton { std::unordered_map> sessions_; + /// Opens the initially configured definitions for the client API. + void materializeClientCatalog(); + + /// Opens a fresh device session with per-call overrides. + auto openFresh(std::string_view id, const DeviceSessionConfig& overrides) + -> std::shared_ptr; + public: /** - * @brief Loads a dynamic device library and adds it to the driver. - * - * @param libName The path to the dynamic library to load. - * @param prefix The prefix used for the device interface functions in the - * library. - * @param config Configuration for device session parameters. - * - * @return A pointer to the newly created device. - * - * @throws std::runtime_error If the device cannot be initialized. - * @throws std::bad_alloc If memory allocation fails during the process. - */ - auto addDynamicDeviceLibrary(const std::string& libName, - const std::string& prefix, - const DeviceSessionConfig& config = {}) - -> QDMI_Device; + * @returns the process-wide Driver instance. + * @details This out-of-line accessor keeps static-library consumers from + * instantiating separate singleton storage in different translation units. + */ + [[nodiscard]] static auto get() -> Driver&; + + /** + * @brief Registers a device definition without loading its library. + * @param definition The definition to validate and store. + * @param replace Whether an existing unopened definition may be replaced. + * @throws std::invalid_argument If the definition is incomplete or its ID is + * already registered. + * @throws std::runtime_error If replacing an already opened definition. + */ + void registerDevice(DeviceDefinition definition, bool replace = false); + + /** + * @brief Registers a device definition unless its ID is already present. + * @param definition The definition to validate and store. + * @returns Whether the definition was inserted. + * @throws std::invalid_argument If the definition is incomplete. + * @details Existing and explicitly disabled IDs are not inserted. The + * complete definition is validated before checking for either condition. + */ + auto registerDeviceIfAbsent(DeviceDefinition definition) -> bool; + + /** + * @brief Opens the registered device with the given stable ID. + * @returns The existing device handle when the ID is already open. + * @throws std::out_of_range If the ID is unknown. + * @throws std::runtime_error If loading or session initialization fails. + */ + auto open(std::string_view id) -> QDMI_Device; /** * @brief Allocates a new session. diff --git a/include/mqt-core/qir/jit/IRRewriter.hpp b/include/mqt-core/qir/jit/IRRewriter.hpp new file mode 100644 index 0000000000..d144f0fe45 --- /dev/null +++ b/include/mqt-core/qir/jit/IRRewriter.hpp @@ -0,0 +1,50 @@ +/* + * 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 + */ + +#pragma once + +#include +#include + +#include +#include + +namespace qir { + +/// The set of call targets that @ref stripMeasurementRelatedCalls erases. +inline constexpr std::array STRIP_TARGETS = { + "__quantum__qis__mz__body", + "__quantum__qis__m__body", + "__quantum__qis__measure__body", + "__quantum__rt__result_record_output", + "__quantum__rt__result_update_reference_count", +}; + +/** + * @brief Strips QIR measurement-related calls from @p m in place. + * @details Erases calls to the QIR measurement intrinsics, to the + * result-recording intrinsic, and to the result reference-count update + * intrinsic (whose Result operands would otherwise reference the null + * pointers left by the stripped measurements). + * Intended for QIR Base Profile programs only: in Adaptive Profile programs, + * measurement results feed classical control flow, so removing them silently + * changes observable behavior. + * + * The typical use is to prepare a Base Profile module for state-vector + * extraction: after this transform the JIT'd @c main can be run once and + * the resulting state remains in the @ref qir::Runtime DD instead of being + * collapsed by measurement. + * + * @param m Module to rewrite in place. + * @return Number of instructions erased. + */ +std::size_t stripMeasurementRelatedCalls(llvm::Module& m); + +} // namespace qir diff --git a/include/mqt-core/qir/jit/Session.hpp b/include/mqt-core/qir/jit/Session.hpp new file mode 100644 index 0000000000..abeb416fc0 --- /dev/null +++ b/include/mqt-core/qir/jit/Session.hpp @@ -0,0 +1,126 @@ +/* + * 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 + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace qir { + +/** + * @brief Whether the JIT'd program runs to produce measurement samples or + * to leave the final quantum state in @ref qir::Runtime for external + * extraction. + * @details In @c StateExtraction mode the session strips QIR measurement + * and result-management calls from the IR before JIT-compiling, so the + * runtime's quantum state remains intact after @c main returns. Intended + * for QIR Base Profile programs only. + */ +enum class Execution { Sampling, StateExtraction }; + +/** + * @brief In-process JIT executor for QIR programs. + * @details The session does the following, in order: + * - Loads an LLVM module from either an IR file (text or bitcode) or + * an in-memory buffer, + * - JIT-compiles it via LLVM's OrcJIT with lazy compilation. + * - wires up the QIR runtime symbols, and + * - runs the module's @c main function. + * A session owns a single LLJIT instance and is not meant to be reused across + * modules; create a new @ref JitSession for each program. + */ +class JitSession { +public: + /// Signature of the @c main function produced by QIR-compiled modules. + using MainFn = int(int, char**); + + /** + * @brief Build a session by loading IR from a file on disk. + * @param inputFile Path to a textual IR or bitcode file. + * @param mode Execution mode; see @ref Execution. + * @throws std::runtime_error if the file cannot be parsed or the JIT fails + * to initialize. + */ + explicit JitSession(llvm::StringRef inputFile, + Execution mode = Execution::Sampling); + + /** + * @brief Build a session by loading IR from a memory buffer. + * @details Accepts either textual IR or bitcode. The buffer does not have + * to be null-terminated. + * @param irBytes Byte view of the IR. + * @param bufferName Identifier used in diagnostics. + * @param mode Execution mode; see @ref Execution. + * @throws std::runtime_error if the IR cannot be parsed or the JIT fails + * to initialize. + */ + JitSession(llvm::StringRef irBytes, llvm::StringRef bufferName, + Execution mode = Execution::Sampling); + + /// Tears down the LLJIT and any JIT'd resources owned by the session. + ~JitSession(); + + /** + * @brief Executes the JIT'd @c main function. + * @param args Argument strings passed as @c argv (excluding @c argv[0]). + * @param progName Value used as @c argv[0]. + * @return The integer returned by the JIT'd @c main. + */ + int run(llvm::ArrayRef args = {}, + llvm::StringRef progName = "") const; + +private: + llvm::orc::ThreadSafeContext tsCtx_{std::make_unique()}; + llvm::orc::ThreadSafeModule module_; + std::unique_ptr jit_; + MainFn* mainFn_ = nullptr; + + /// Registers the QIR runtime symbols with @c llvm::sys::DynamicLibrary so the + /// JIT can resolve them at link time. + /// Safe to call multiple times; the work runs only on the first call. + static void registerRuntimeSymbols(); + + /// Initializes the native target, asm printer and asm parser. + /// Safe to call multiple times; the work runs only on the first call. + static void initNativeTargets(); + + /// Parses LLVM IR from @p irPath using the session's thread-safe context. + llvm::Expected + loadModuleFromFile(llvm::StringRef irPath); + + /// Parses LLVM IR (textual or bitcode) from @p irBytes using the session's + /// thread-safe context. @p bufferName is used in diagnostics. + llvm::Expected + loadModuleFromMemory(llvm::StringRef irBytes, llvm::StringRef bufferName); + + /// Prepares the session to run the program: + /// - Validates the loaded module. + /// - Optionally strips measurement and result management calls + /// (for @ref Execution::StateExtraction). + /// - Builds the @c LLJIT instance + /// - Registers QIR runtime symbols + /// - Resolves @c main. + /// @throws std::runtime_error if loading failed or the JIT cannot start. + void initialize(llvm::Expected llvmModule, + Execution mode); + + /// Tears down the @c LLJIT. + void deinitialize() const; +}; + +} // namespace qir diff --git a/include/mqt-core/qir/runtime/QIR.h b/include/mqt-core/qir/runtime/QIR.h index fd1463a0f8..89254efe07 100644 --- a/include/mqt-core/qir/runtime/QIR.h +++ b/include/mqt-core/qir/runtime/QIR.h @@ -109,13 +109,13 @@ void __quantum__qis__y__body(Qubit*); void __quantum__qis__z__body(Qubit*); void __quantum__qis__h__body(Qubit*); void __quantum__qis__s__body(Qubit*); -void __quantum__qis__sdg__body(Qubit*); +void __quantum__qis__s__adj(Qubit*); void __quantum__qis__sx__body(Qubit*); -void __quantum__qis__sxdg__body(Qubit*); +void __quantum__qis__sx__adj(Qubit*); void __quantum__qis__sqrtx__body(Qubit*); -void __quantum__qis__sqrtxdg__body(Qubit*); +void __quantum__qis__sqrtx__adj(Qubit*); void __quantum__qis__t__body(Qubit*); -void __quantum__qis__tdg__body(Qubit*); +void __quantum__qis__t__adj(Qubit*); void __quantum__qis__r__body(Qubit*, double, double); void __quantum__qis__prx__body(Qubit*, double, double); void __quantum__qis__rx__body(Qubit*, double); @@ -167,6 +167,31 @@ bool __quantum__rt__read_result(Result*); /// the label is included in the output or omitted. void __quantum__rt__result_record_output(Result*, const char*); +/// Adds a boolean value to the generated output. The second parameter defines +/// a string label for the value. Depending on the output schema, the label is +/// included in the output or omitted. +void __quantum__rt__bool_record_output(bool, const char*); + +/// Adds an integer value to the generated output. The second parameter defines +/// a string label for the value. Depending on the output schema, the label is +/// included in the output or omitted. +void __quantum__rt__int_record_output(int64_t, const char*); + +/// Adds a floating-point value to the generated output. The second parameter +/// defines a string label for the value. Depending on the output schema, the +/// label is included in the output or omitted. +void __quantum__rt__float_record_output(double, const char*); + +/// Inserts a marker in the generated output indicating that the next +/// \p elementCount recorded values form the contents of a tuple. The second +/// parameter defines a string label for the tuple. +void __quantum__rt__tuple_record_output(int64_t elementCount, const char*); + +/// Inserts a marker in the generated output indicating that the next \p size +/// recorded values form the contents of an array. The second parameter defines +/// a string label for the array. +void __quantum__rt__array_record_output(int64_t size, const char*); + // NOLINTEND(readability-identifier-naming) // NOLINTEND(modernize-deprecated-headers) // NOLINTEND(modernize-use-using) diff --git a/include/mqt-core/qir/runtime/Runtime.hpp b/include/mqt-core/qir/runtime/Runtime.hpp index 009b3d66dd..756ff1fe1d 100644 --- a/include/mqt-core/qir/runtime/Runtime.hpp +++ b/include/mqt-core/qir/runtime/Runtime.hpp @@ -29,6 +29,8 @@ #include #include #include +#include +#include #include #include #include @@ -49,6 +51,7 @@ struct ArrayImpl { }; namespace qir { + // Primary template template static constexpr bool IS_STD_ARRAY_V = false; // Specialization for std::array @@ -183,6 +186,7 @@ class Utils { return array; } }; + /** * @note This class is implemented following the design pattern Singleton in * order to access an instance of this class from the C function without having @@ -193,6 +197,39 @@ class Runtime { static constexpr uintptr_t RESULT_ZERO_ADDRESS = 0x10000; static constexpr uintptr_t RESULT_ONE_ADDRESS = 0x10001; + /// The quantum state held by the runtime: + /// - a DD package, + /// - the root edge into that package, and + /// - the number of qubits the state spans. + struct QState { + std::unique_ptr dd; + dd::vEdge edge; + dd::Qubit numQubits; + + QState() + : dd(std::make_unique()), edge(dd::vEdge::one()), + numQubits(0) {} + + /// Reset to a fresh empty state. + /// If @c dd is currently populated, the existing package's `decRef` plus + /// `garbageCollect` path is used so the package (and its internal caches) + /// is kept warm. + /// If @c dd was moved out (e.g., by @ref Runtime::takeState), a new package + /// is allocated. + auto reset() -> void { + if (dd) { + dd->decRef(edge); + dd->garbageCollect(); + } else { + dd = std::make_unique(); + } + edge = dd::vEdge::one(); + numQubits = 0; + } + }; + + enum class OutputSchema : uint8_t { Labeled, Ordered }; + private: static constexpr uintptr_t MIN_DYN_QUBIT_ADDRESS = 0x10000; enum class AddressMode : uint8_t { UNKNOWN, DYNAMIC, STATIC }; @@ -203,13 +240,16 @@ class Runtime { std::vector qubitPermutation; static constexpr uintptr_t MIN_DYN_RESULT_ADDRESS = 0x10000; std::unordered_map rRegister; + std::string measurements; uintptr_t currentMaxQubitAddress; qc::Qubit currentMaxQubitId; uintptr_t currentMaxResultAddress; - dd::Qubit numQubitsInQState; - std::unique_ptr dd; - dd::vEdge qState; + QState qState; std::mt19937_64 mt; + std::ostream* os = &std::cout; + // The QIR spec does not define a default output schema. + // The runtime picks @c Labeled when a program doesn't declare one. + OutputSchema outputSchema = OutputSchema::Labeled; Runtime(); explicit Runtime(uint64_t randomSeed); @@ -255,6 +295,13 @@ class Runtime { return {targets, Op, paramVec}; } + // Helper function to output a type (bool, int...) to @c os, honoring the + // active @c outputSchema. + // The label is included only in Labeled mode. + // Tab separator between fields, newline at end. + void outputType(const char* type, std::string_view value, + const char* label) const; + public: [[nodiscard]] static auto generateRandomSeed() -> uint64_t; static Runtime& getInstance(); @@ -269,7 +316,7 @@ class Runtime { auto apply(Args&&... args) -> void { const qc::StandardOperation& operation = createOperation(std::forward(args)...); - qState = applyUnitaryOperation(operation, qState, *dd); + qState.edge = applyUnitaryOperation(operation, qState.edge, *qState.dd); } template auto measure(Args... args) -> void { const auto& qubits = Utils::packOfType(args...); @@ -293,8 +340,8 @@ class Runtime { // measure qubits Utils::apply2( [&](const auto q, auto& r) { - const auto& result = - dd->measureOneCollapsing(qState, static_cast(q), mt); + const auto& result = qState.dd->measureOneCollapsing( + qState.edge, static_cast(q), mt); deref(r).r = result == '1'; }, targets, results); @@ -306,7 +353,7 @@ class Runtime { } const qc::NonUnitaryOperation resetOp( {targets.data(), targets.data() + SIZE}, qc::Reset); - qState = applyReset(resetOp, qState, *dd, mt); + qState.edge = applyReset(resetOp, qState.edge, *qState.dd, mt); } auto swap(Qubit* qubit1, Qubit* qubit2) -> void; auto qAlloc() -> Qubit*; @@ -356,5 +403,61 @@ class Runtime { auto deref(Result* result) -> ResultStruct&; auto rFree(Result* result) -> void; auto equal(Result* result1, Result* result2) -> bool; + + /// Append a measurement bit to the measurement string. + auto appendMeasurementBit(bool result) -> void; + + /// @returns the accumulated measurement string. + auto getMeasurements() const -> const std::string&; + + /// Move the quantum state out of the runtime. + /// Then reset the runtime to a clean state ready for the next job. + /// Intended for use after a @c JitSession constructed with + /// @c Execution::StateExtraction has finished running. + /// @returns the moved @c QState from the runtime. + auto takeState() -> QState; + + auto getOstream() const -> std::ostream&; + auto setOstream(std::ostream& other) -> void; + auto resetOstream() -> void; + + /// Emit `OUTPUT\tRESULT\t<0|1>[\tlabel]\n` to the output stream. + auto outputResult(bool value, const char* label) const -> void; + + /// Emit `OUTPUT\tBOOL\t[\tlabel]\n` to the output stream. + auto outputBool(bool value, const char* label) const -> void; + + /// Emit `OUTPUT\tINT\t[\tlabel]\n` to the output stream. + auto outputInt(int64_t value, const char* label) const -> void; + + /// Emit `OUTPUT\tDOUBLE\t[\tlabel]\n` to the output stream. + auto outputFloat(double value, const char* label) const -> void; + + /// Emit `OUTPUT\tTUPLE\t[\tlabel]\n` to the output stream. + auto outputTuple(int64_t elementCount, const char* label) const -> void; + + /// Emit `OUTPUT\tARRAY\t[\tlabel]\n` to the output stream. + auto outputArray(int64_t elementCount, const char* label) const -> void; + + /// Emit the HEADER records (once per submitted program): + /// `HEADER\tschema_id\t` + /// `HEADER\tschema_version\t2.1` + auto outputProgramHeader() const -> void; + + /// Emit `START\n` followed by + /// `METADATA\toutput_labeling_schema\t\n` (one per shot). + auto outputShotStart() const -> void; + + /// Emit `END\t0\n` (one per shot). + /// The trailing `0` is a spec literal, not a runtime exit code. + auto outputShotEnd() const -> void; + + [[nodiscard]] auto getOutputSchema() const -> OutputSchema; + auto setOutputSchema(OutputSchema schema) -> void; }; + +/// Write the schema's spec-mandated literal (`labeled` or `ordered`). +auto operator<<(std::ostream& os, Runtime::OutputSchema schema) + -> std::ostream&; + } // namespace qir diff --git a/include/mqt-core/zx/FunctionalityConstruction.hpp b/include/mqt-core/zx/FunctionalityConstruction.hpp index ab4a9e6f14..5a2fc5990b 100644 --- a/include/mqt-core/zx/FunctionalityConstruction.hpp +++ b/include/mqt-core/zx/FunctionalityConstruction.hpp @@ -112,6 +112,13 @@ class FunctionalityConstruction { const std::optional& unconvertedPhase = std::nullopt); static void addDcx(ZXDiagram& diag, Qubit qubit1, Qubit qubit2, std::vector& qubits); + static void addRccx(ZXDiagram& diag, Qubit qubit0, Qubit qubit1, Qubit qubit2, + std::vector& qubits); + static void addCrccx(ZXDiagram& diag, Qubit control, Qubit qubit0, + Qubit qubit1, Qubit qubit2, std::vector& qubits); + static void addMcrccx(ZXDiagram& diag, const std::vector& controls, + Qubit qubit0, Qubit qubit1, Qubit qubit2, + std::vector& qubits); static void addXXplusYY(ZXDiagram& diag, const PiExpression& theta, const PiExpression& beta, Qubit qubit0, Qubit qubit1, diff --git a/mlir/test/lit.cfg.py b/mlir/test/lit.cfg.py index 5d877ad3a2..20766da129 100644 --- a/mlir/test/lit.cfg.py +++ b/mlir/test/lit.cfg.py @@ -6,7 +6,7 @@ # # Licensed under the MIT License -# ruff: noqa: INP001 +# ruff:file-ignore[implicit-namespace-package] """LIT Configuration file for the MQT MLIR test suite. diff --git a/noxfile.py b/noxfile.py index 707bab6c84..ed0eddb774 100755 --- a/noxfile.py +++ b/noxfile.py @@ -95,9 +95,6 @@ def _run_tests( ) if extra_command: session.run(*extra_command, env=env) - if "--cov" in session.posargs: - # try to use the lighter-weight `sys.monitoring` coverage core - env["COVERAGE_CORE"] = "sysmon" session.run( "uv", "run", @@ -214,6 +211,7 @@ def stubs(session: nox.Session) -> None: session.run( "uv", "sync", + "--no-editable", "--no-dev", "--group", "build", diff --git a/pyproject.toml b/pyproject.toml index 4b5af38deb..dd38ea5057 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ [build-system] requires = [ "nanobind~=2.13.0", - "scikit-build-core~=0.12", + "scikit-build-core~=1.0.3", "setuptools-scm>=9.2.2", ] build-backend = "scikit_build_core.build" @@ -81,7 +81,7 @@ wheel.py-api = "cp312" ninja.version = ">=1.10" # Setuptools-style build caching in a local directory -build-dir = "build/{wheel_tag}/{build_type}" +build-dir = "build/python/{build_type}" # All the targets to build build.targets = [ @@ -178,8 +178,10 @@ testpaths = ["test/python"] [tool.coverage] run.source = ["mqt.core"] +run.core = "sysmon" +run.parallel = true run.omit = [ - '*/_compat/*', + '*/_compat/*', ] run.disable_warnings = [ "no-sysmon", @@ -272,10 +274,10 @@ PC170 = "We do not use rST files anymore" [tool.cibuildwheel] build = "cp3*" -skip = ["*-musllinux_*", "cp313t-*"] +skip = ["*-musllinux_*"] archs = "auto64" test-groups = ["test"] -test-sources = ["test/python", "json/na"] +test-sources = ["test/python", "test/circuits", "json/na"] test-command = "pytest test/python" build-frontend = "uv" test-skip = [ @@ -294,12 +296,6 @@ environment = { MACOSX_DEPLOYMENT_TARGET = "11.0" } [tool.cibuildwheel.windows] repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel} --namespace-pkg mqt --ignore-existing" -[[tool.cibuildwheel.overrides]] -select = "cp312-*" -inherit.repair-wheel-command = "append" -repair-wheel-command = "uvx abi3audit --strict --report {wheel}" - - [tool.uv] required-version = ">=0.5.20" cache-keys = [ @@ -327,7 +323,7 @@ exclude = [ [dependency-groups] build = [ "nanobind~=2.13.0", - "scikit-build-core~=0.12", + "scikit-build-core~=1.0.3", "setuptools-scm>=9.2.2", ] docs = [ @@ -351,7 +347,7 @@ docs = [ test = [ "pytest>=9.0.1", "pytest-console-scripts>=1.4.1", - "pytest-cov>=7.0.0", + "pytest-cov>=7.1.0", "pytest-sugar>=1.1.1", "pytest-xdist>=3.8.0", "qiskit[qasm3-import]>=1.1.0", diff --git a/python/mqt/core/__init__.py b/python/mqt/core/__init__.py index 2a6c912088..5f2febb262 100644 --- a/python/mqt/core/__init__.py +++ b/python/mqt/core/__init__.py @@ -15,11 +15,11 @@ from pathlib import Path # under Windows, make sure to add the appropriate DLL directory to the PATH -if sys.platform == "win32": # noqa: RUF067 This is actually required on Windows +if sys.platform == "win32": # ruff:ignore[non-empty-init-module] This is actually required on Windows def _dll_patch() -> None: """Add the DLL directory to the PATH.""" - import sysconfig # noqa: PLC0415 only used in Windows + import sysconfig # ruff:ignore[import-outside-top-level] only used in Windows bin_dir = Path(sysconfig.get_paths()["purelib"]) / "mqt" / "core" / "bin" os.add_dll_directory(str(bin_dir)) diff --git a/python/mqt/core/fomac.pyi b/python/mqt/core/fomac.pyi index 3e8bd79642..0c3e77df02 100644 --- a/python/mqt/core/fomac.pyi +++ b/python/mqt/core/fomac.pyi @@ -7,13 +7,15 @@ # Licensed under the MIT License import enum +import os +import pathlib from collections.abc import Sequence from typing import overload class Session: """A FoMaC session for managing QDMI devices. - Allows creating isolated sessions with independent authentication settings. + Allows creating isolated sessions with separate authentication settings. All authentication parameters are optional and can be provided as keyword arguments to the constructor. """ @@ -21,7 +23,7 @@ class Session: self, *, token: str | None = None, - auth_file: str | None = None, + auth_file: str | os.PathLike | None = None, auth_url: str | None = None, username: str | None = None, password: str | None = None, @@ -170,6 +172,10 @@ class Job: def program(self) -> str: """The submitted program.""" + @property + def program_bytes(self) -> bytes: + """The exact bytes of the submitted program.""" + @property def num_shots(self) -> int: """The number of shots.""" @@ -215,6 +221,8 @@ class ProgramFormat(enum.Enum): IQM_JSON = 8 + BATCH_JOB = 9 + CUSTOM1 = 999999995 CUSTOM2 = 999999996 @@ -331,6 +339,7 @@ class Device: when the custom slot is unsupported. """ + @overload def submit_job( self, program: str, @@ -343,7 +352,22 @@ class Device: custom4: str | bool | float | None = None, custom5: str | bool | float | None = None, ) -> Job: - """Submits a job to the device.""" + """Submits a text job to the device.""" + + @overload + def submit_job( + self, + program: bytes, + program_format: ProgramFormat, + num_shots: int, + *, + custom1: str | bool | float | None = None, + custom2: str | bool | float | None = None, + custom3: str | bool | float | None = None, + custom4: str | bool | float | None = None, + custom5: str | bool | float | None = None, + ) -> Job: + """Submits an exact byte payload to the device.""" def __eq__(self, arg: object, /) -> bool: ... def __ne__(self, arg: object, /) -> bool: ... @@ -511,13 +535,92 @@ class Device: def __eq__(self, arg: object, /) -> bool: ... def __ne__(self, arg: object, /) -> bool: ... -def add_dynamic_device_library( - library_path: str, - prefix: str, +class DeviceDefinition: + """A stable QDMI device registration that can be stored before loading.""" + + def __init__( + self, + device_id: str, + library_path: str | os.PathLike, + prefix: str, + *, + base_url: str | None = None, + token: str | None = None, + auth_file: str | os.PathLike | None = None, + auth_url: str | None = None, + username: str | None = None, + password: str | None = None, + custom1: str | None = None, + custom2: str | None = None, + custom3: str | None = None, + custom4: str | None = None, + custom5: str | None = None, + ) -> None: + """Create a device definition without loading its native library. + + Args: + device_id: Stable identifier used by :func:`open_device`. + library_path: Path to the shared QDMI device library. + prefix: Function prefix used by the library (for example, ``MY_DEVICE``). + base_url: Optional base URL for the device API endpoint. + token: Optional authentication token. + auth_file: Optional path to an authentication file. + auth_url: Optional authentication server URL. + username: Optional authentication username. + password: Optional authentication password. + custom1: Optional custom configuration parameter 1. + custom2: Optional custom configuration parameter 2. + custom3: Optional custom configuration parameter 3. + custom4: Optional custom configuration parameter 4. + custom5: Optional custom configuration parameter 5. + """ + + @property + def device_id(self) -> str: + """Stable identifier used to open the device.""" + + @property + def library_path(self) -> pathlib.Path: + """Path to the native QDMI device library.""" + + @property + def prefix(self) -> str: + """Prefix used for the QDMI device interface functions.""" + +def register_device(definition: DeviceDefinition, *, replace: bool = False) -> None: + """Register a QDMI device definition without loading its library. + + Args: + definition: Definition to validate and store. + replace: Replace an existing definition if it has not been opened. + + Raises: + ValueError: If the definition is invalid or its ID is already registered. + RuntimeError: If replacing an already opened ID. + """ + +def register_device_if_absent(definition: DeviceDefinition) -> bool: + """Register a valid QDMI device definition if its ID is absent. + + Existing and explicitly disabled IDs are not inserted. Invalid definitions + still raise. + + Args: + definition: Definition to validate and store. + + Returns: + bool: Whether the definition was inserted. + + Raises: + ValueError: If the definition is invalid. + """ + +def open_device( + device_id: str, *, base_url: str | None = None, token: str | None = None, - auth_file: str | None = None, + auth_file: str | os.PathLike | None = None, auth_url: str | None = None, username: str | None = None, password: str | None = None, @@ -527,41 +630,29 @@ def add_dynamic_device_library( custom4: str | None = None, custom5: str | None = None, ) -> Device: - """Load a dynamic device library into the QDMI driver. + """Open a registered QDMI device by stable ID. - This function loads a shared library (.so, .dll, or .dylib) that implements a QDMI device interface and makes it available for use in sessions. + Every call creates a fresh device session while keeping the stable registration + unchanged. Opening the device loads trusted native device code. Args: - library_path: Path to the shared library file to load. - prefix: Function prefix used by the library (e.g., "MY_DEVICE"). - base_url: Optional base URL for the device API endpoint. - token: Optional authentication token. - auth_file: Optional path to authentication file. - auth_url: Optional authentication server URL. - username: Optional username for authentication. - password: Optional password for authentication. - custom1: Optional custom configuration parameter 1. - custom2: Optional custom configuration parameter 2. - custom3: Optional custom configuration parameter 3. - custom4: Optional custom configuration parameter 4. - custom5: Optional custom configuration parameter 5. + device_id: Stable ID of a registered device. + base_url: Optional base URL override for the device API endpoint. + token: Optional authentication token override. + auth_file: Optional authentication-file override. + auth_url: Optional authentication server URL override. + username: Optional authentication username override. + password: Optional authentication password override. + custom1: Optional custom configuration parameter 1 override. + custom2: Optional custom configuration parameter 2 override. + custom3: Optional custom configuration parameter 3 override. + custom4: Optional custom configuration parameter 4 override. + custom5: Optional custom configuration parameter 5 override. Returns: - Device: The newly loaded device that can be used to create backends. + Device: The opened device, ready for direct backend construction. Raises: - RuntimeError: If library loading fails or configuration is invalid. - - Examples: - Load a device library with configuration: - - >>> import mqt.core.fomac as fomac - >>> device = fomac.add_dynamic_device_library( - ... "/path/to/libmy_device.so", "MY_DEVICE", base_url="http://localhost:8080", custom1="API_V2" - ... ) - - Now the device can be used directly: - - >>> from mqt.core.plugins.qiskit import QDMIBackend - >>> backend = QDMIBackend(device=device) + IndexError: If the ID is not registered. + RuntimeError: If the device library cannot be loaded or initialized. """ diff --git a/python/mqt/core/ir/__init__.pyi b/python/mqt/core/ir/__init__.pyi index 69ea76967a..4f6e1fe877 100644 --- a/python/mqt/core/ir/__init__.pyi +++ b/python/mqt/core/ir/__init__.pyi @@ -1820,6 +1820,41 @@ class QuantumComputation(MutableSequence[operations.Operation]): target2: The second target qubit """ + def rccx(self, target1: int, target2: int, target3: int) -> None: + """Apply a relative-phase CCX (RCCX) gate. + + Args: + target1: The first target qubit + target2: The second target qubit + target3: The third target qubit + """ + + def crccx(self, control: operations.Control | int, target1: int, target2: int, target3: int) -> None: + """Apply a controlled RCCX gate. + + See Also: + :meth:`rccx` + + Args: + control: The control qubit + target1: The first target qubit + target2: The second target qubit + target3: The third target qubit + """ + + def mcrccx(self, controls: AbstractSet[operations.Control | int], target1: int, target2: int, target3: int) -> None: + """Apply a multi-controlled RCCX gate. + + See Also: + :meth:`rccx` + + Args: + controls: The control qubits + target1: The first target qubit + target2: The second target qubit + target3: The third target qubit + """ + def gphase(self, phase: float) -> None: r"""Apply a global phase gate. diff --git a/python/mqt/core/ir/operations.pyi b/python/mqt/core/ir/operations.pyi index 9c6bc8f35d..2a3b042bd9 100644 --- a/python/mqt/core/ir/operations.pyi +++ b/python/mqt/core/ir/operations.pyi @@ -296,6 +296,14 @@ class OpType(enum.Enum): :meth:`mqt.core.ir.QuantumComputation.xx_plus_yy` """ + rccx = 132 + """ + A relative-phase CCX gate. + + See Also: + :meth:`mqt.core.ir.QuantumComputation.rccx` + """ + compound = 116 """ A compound operation. diff --git a/python/mqt/core/load.py b/python/mqt/core/load.py index 11a6cb3ced..61de3bcfcf 100644 --- a/python/mqt/core/load.py +++ b/python/mqt/core/load.py @@ -51,6 +51,6 @@ def load(input_circuit: QuantumComputation | str | os.PathLike[str] | QuantumCir return QuantumComputation.from_qasm(input_str) case _: # At this point, we know that the input is a Qiskit QuantumCircuit - from .plugins.qiskit import qiskit_to_mqt # noqa: PLC0415 lazy import + from .plugins.qiskit import qiskit_to_mqt # ruff:ignore[import-outside-top-level] lazy import return qiskit_to_mqt(input_circuit) diff --git a/python/mqt/core/plugins/qiskit/__init__.py b/python/mqt/core/plugins/qiskit/__init__.py index 0e810b2ee8..0753f87c89 100644 --- a/python/mqt/core/plugins/qiskit/__init__.py +++ b/python/mqt/core/plugins/qiskit/__init__.py @@ -15,7 +15,7 @@ from ..._compat.optional import OptionalDependencyTester # Optional dependency tester for Qiskit -HAS_QISKIT = OptionalDependencyTester( # noqa: RUF067 Used for handling optional plugin +HAS_QISKIT = OptionalDependencyTester( # ruff:ignore[non-empty-init-module] Used for handling optional plugin "qiskit", install_msg="Install with 'pip install mqt-core[qiskit]'", ) @@ -24,7 +24,7 @@ "HAS_QISKIT", ] -if TYPE_CHECKING or HAS_QISKIT: # noqa: RUF067 Used for handling optional plugin +if TYPE_CHECKING or HAS_QISKIT: # ruff:ignore[non-empty-init-module] Used for handling optional plugin from .backend import QDMIBackend from .converters import qiskit_to_iqm_json from .estimator import QDMIEstimator diff --git a/python/mqt/core/plugins/qiskit/backend.py b/python/mqt/core/plugins/qiskit/backend.py index ab6f92a730..80c0463f30 100644 --- a/python/mqt/core/plugins/qiskit/backend.py +++ b/python/mqt/core/plugins/qiskit/backend.py @@ -192,7 +192,7 @@ def target(self) -> Target: return self._target @property - def provider(self) -> Any | None: # noqa: ANN401 + def provider(self) -> Any | None: # ruff:ignore[any-type] """The provider that created the backend.""" return self._provider @@ -441,7 +441,7 @@ def _get_operation_qargs(self, op: fomac.Device.Operation) -> list[tuple[int]] | # Operation has unspecified qubit count or 3+ qubits -> assume it applies to all qubits return [None] - def _preprocess_circuit(self, circuit: QuantumCircuit) -> QuantumCircuit: # noqa: PLR6301 + def _preprocess_circuit(self, circuit: QuantumCircuit) -> QuantumCircuit: # ruff:ignore[no-self-use] """Rewrite a bound circuit before validation and conversion. Called once per circuit in :meth:`run`, after parameter binding and @@ -568,7 +568,7 @@ def run( self, run_input: QuantumCircuit | Sequence[QuantumCircuit], parameter_values: Sequence[ParametersType] | None = None, - **options: Any, # noqa: ANN401 + **options: Any, # ruff:ignore[any-type] ) -> QDMIJob: """Execute one or more :class:`~qiskit.circuit.QuantumCircuit` instances on the backend. diff --git a/python/mqt/core/plugins/qiskit/converters.py b/python/mqt/core/plugins/qiskit/converters.py index b551f1ac2a..3ec1120bea 100644 --- a/python/mqt/core/plugins/qiskit/converters.py +++ b/python/mqt/core/plugins/qiskit/converters.py @@ -78,7 +78,7 @@ def _raise_error(exception_type: type[Exception], message: str) -> None: """ raise exception_type(message) - try: # noqa: PLW0717 + try: # ruff:ignore[too-many-statements-in-try-clause] # Check for unbound parameters if circuit.parameters: param_names = ", ".join(sorted(p.name for p in circuit.parameters)) diff --git a/python/mqt/core/plugins/qiskit/estimator.py b/python/mqt/core/plugins/qiskit/estimator.py index d0ffd22206..db3d8c0779 100644 --- a/python/mqt/core/plugins/qiskit/estimator.py +++ b/python/mqt/core/plugins/qiskit/estimator.py @@ -23,7 +23,7 @@ except ImportError: # For Qiskit versions < 1.1 from qiskit.primitives.containers.data_bin import ( - _make_data_bin as make_data_bin, # noqa: PLC2701 # ty: ignore[unresolved-import] + _make_data_bin as make_data_bin, # ruff:ignore[import-private-name] # ty: ignore[unresolved-import] ) from qiskit.primitives.containers.estimator_pub import EstimatorPub @@ -87,7 +87,7 @@ def run( # Use PrimitiveJob to handle asynchronous execution job = PrimitiveJob(self._run, coerced_pubs) - job._submit() # noqa: SLF001 + job._submit() # ruff:ignore[private-member-access] return job def _run(self, pubs: list[EstimatorPub]) -> PrimitiveResult[PubResult]: diff --git a/python/mqt/core/plugins/qiskit/mqt_to_qiskit.py b/python/mqt/core/plugins/qiskit/mqt_to_qiskit.py index 402c0a8e65..a1cd0a81f3 100644 --- a/python/mqt/core/plugins/qiskit/mqt_to_qiskit.py +++ b/python/mqt/core/plugins/qiskit/mqt_to_qiskit.py @@ -26,6 +26,7 @@ HGate, IGate, PhaseGate, + RCCXGate, RGate, RXGate, RXXGate, @@ -147,6 +148,7 @@ def _add_standard_operation(circ: QuantumCircuit, op: StandardOperation, qubit_m OpType.ecr: ECRGate(), OpType.swap: SwapGate(), OpType.iswap: iSwapGate(), + OpType.rccx: RCCXGate(), } if op.type_ in gate_map_singleton: @@ -409,7 +411,7 @@ def mqt_to_qiskit(qc: QuantumComputation, *, set_layout: bool = False) -> Quantu p2v[virtual] = qubit_map[physical] final_layout = Layout().from_qubit_list(p2v, *circ.qregs) - circ._layout = TranspileLayout( # noqa: SLF001 + circ._layout = TranspileLayout( # ruff:ignore[private-member-access] initial_layout=initial_layout, input_qubit_mapping={qubit: idx for idx, qubit in qubit_map.items()}, final_layout=final_layout, diff --git a/python/mqt/core/plugins/qiskit/qiskit_to_mqt.py b/python/mqt/core/plugins/qiskit/qiskit_to_mqt.py index e76fa3373f..df08c38040 100644 --- a/python/mqt/core/plugins/qiskit/qiskit_to_mqt.py +++ b/python/mqt/core/plugins/qiskit/qiskit_to_mqt.py @@ -175,6 +175,7 @@ def qiskit_to_mqt(circ: QuantumCircuit) -> QuantumComputation: "rzz", "xx_minus_yy", "xx_plus_yy", + "rccx", "if_else", "reset", "barrier", @@ -317,6 +318,10 @@ def _emplace_operation( if name == "xx_plus_yy": return _add_two_target_operation(qc, OpType.xx_plus_yy, qargs, params, qubit_map) + if name == "rccx": + qc.append(StandardOperation(qubits, OpType.rccx)) + return [] + if name == "if_else": return _add_if_else_operation(qc, cast("IfElseOp", instr), qargs, cargs, qubit_map, clbit_map, cregs) diff --git a/python/mqt/core/plugins/qiskit/sampler.py b/python/mqt/core/plugins/qiskit/sampler.py index eb1c8832bf..b2bf1642f4 100644 --- a/python/mqt/core/plugins/qiskit/sampler.py +++ b/python/mqt/core/plugins/qiskit/sampler.py @@ -88,7 +88,7 @@ def run( # Use PrimitiveJob to handle asynchronous execution job = PrimitiveJob(self._run, coerced_pubs) - job._submit() # noqa: SLF001 + job._submit() # ruff:ignore[private-member-access] return job def _run(self, pubs: list[SamplerPub]) -> PrimitiveResult[SamplerPubResult]: diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f74d2e9394..8a8c6098a3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -132,6 +132,7 @@ if(MQT_CORE_INSTALL) install( FILES ${PROJECT_SOURCE_DIR}/cmake/AddMQTPythonBinding.cmake + ${PROJECT_SOURCE_DIR}/cmake/AddMQTQDMIDevice.cmake ${PROJECT_SOURCE_DIR}/cmake/Cache.cmake ${PROJECT_SOURCE_DIR}/cmake/FindGMP.cmake ${PROJECT_SOURCE_DIR}/cmake/PackageAddTest.cmake diff --git a/src/dd/GateMatrixDefinitions.cpp b/src/dd/GateMatrixDefinitions.cpp index 4db2716cc7..0c86a63d72 100644 --- a/src/dd/GateMatrixDefinitions.cpp +++ b/src/dd/GateMatrixDefinitions.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -250,4 +251,25 @@ TwoQubitGateMatrix opToTwoQubitGateMatrix(const qc::OpType t, throw std::invalid_argument("Invalid two-qubit gate type"); } } + +ThreeQubitGateMatrix +opToThreeQubitGateMatrix(const qc::OpType t, + const std::vector& /*params*/) { + switch (t) { + case qc::RCCX: { + ThreeQubitGateMatrix matrix{}; + for (size_t i = 0; i < THREE_QUBIT_GATE_DIM; ++i) { + matrix[i][i] = 1.; + } + matrix[5][5] = -1.; + matrix[6][6] = 0.; + matrix[7][7] = 0.; + matrix[6][7] = {0., -1.}; + matrix[7][6] = {0., 1.}; + return matrix; + } + default: + throw std::invalid_argument("Invalid three-qubit gate type"); + } +} } // namespace dd diff --git a/src/dd/Operations.cpp b/src/dd/Operations.cpp index 62345e2ad5..76a682b989 100644 --- a/src/dd/Operations.cpp +++ b/src/dd/Operations.cpp @@ -58,6 +58,15 @@ MatrixDD getStandardOperationDD(Package& dd, const qc::OpType type, return dd.makeTwoQubitGateDD(opToTwoQubitGateMatrix(type, params), controls, targets[0U], targets[1U]); } + if (qc::isThreeQubitGate(type)) { + if (targets.size() != 3) { + throw std::invalid_argument( + "Expected three target qubits for three-qubit gate"); + } + return dd.makeThreeQubitGateDD(opToThreeQubitGateMatrix(type, params), + controls, targets[0U], targets[1U], + targets[2U]); + } throw std::runtime_error("Unexpected operation type"); } @@ -85,6 +94,7 @@ MatrixDD getStandardOperationDD(const qc::StandardOperation& op, Package& dd, case qc::Z: case qc::SWAP: case qc::ECR: + case qc::RCCX: break; // operations that have an inverse gate with the same parameters case qc::iSWAP: diff --git a/src/dd/Package.cpp b/src/dd/Package.cpp index b169f8c776..ddfb8096f3 100644 --- a/src/dd/Package.cpp +++ b/src/dd/Package.cpp @@ -36,7 +36,10 @@ #include #include #include +#include #include +#include +#include #include #include #include @@ -181,6 +184,135 @@ dEdge Package::makeZeroDensityOperator(const std::size_t n) { return f; } +namespace { + +[[noreturn]] void throwGateQubitOutOfRange(const std::size_t nqubits) { + if (nqubits == 0U) { + throw std::runtime_error( + "Cannot construct a gate in a package with zero qubits."); + } + throw std::runtime_error{ + "Requested gate acting on qubit(s) with index larger than " + + std::to_string(nqubits - 1U) + + " while the package configuration only supports up to " + + std::to_string(nqubits) + + " qubits. Please allocate a larger package instance."}; +} + +[[noreturn]] void throwGateQubitsNotDistinct() { + throw std::runtime_error{ + "Requested gate has duplicate or overlapping control/target qubits."}; +} + +void ensureGateQubitsInRange(const std::size_t nqubits, + const qc::Controls& controls, + const std::initializer_list targets) { + if (nqubits == 0U || + std::ranges::any_of(controls, + [nqubits](const auto& c) { + return static_cast(c.qubit) >= nqubits; + }) || + std::ranges::any_of(targets, [nqubits](const Qubit target) { + return static_cast(target) >= nqubits; + })) { + throwGateQubitOutOfRange(nqubits); + } + + std::vector sortedTargets(targets.begin(), targets.end()); + std::ranges::sort(sortedTargets); + if (std::ranges::adjacent_find(sortedTargets) != sortedTargets.end()) { + throwGateQubitsNotDistinct(); + } + + if (std::ranges::any_of(controls, [&targets](const auto& control) { + return std::ranges::find(targets, control.qubit) != targets.end(); + })) { + throwGateQubitsNotDistinct(); + } +} + +template +void fillTerminalMatrix( + std::array, Dim>& em, + const std::array, Dim>, Dim>& mat) { + for (std::size_t row = 0; row < Dim; ++row) { + for (std::size_t col = 0; col < Dim; ++col) { + em[row][col] = mCachedEdge::terminal(mat[row][col]); + } + } +} + +void fillTerminalVector(std::array& em, + const GateMatrix& mat) { + for (std::size_t i = 0; i < NEDGE; ++i) { + em[i] = mCachedEdge::terminal(mat[i]); + } +} + +[[nodiscard]] mCachedEdge makeControlledNode(Package& dd, + const qc::Qubit controlQubit, + const qc::Control::Type type, + const mCachedEdge& gate, + const bool identity) { + std::array edges{mCachedEdge::zero(), mCachedEdge::zero(), + mCachedEdge::zero(), + mCachedEdge::zero()}; + const auto idEdge = identity ? mCachedEdge::one() : mCachedEdge::zero(); + if (type == qc::Control::Type::Neg) { + edges[0] = gate; + edges[3] = idEdge; + } else { + edges[0] = idEdge; + edges[3] = gate; + } + return dd.makeDDNode(static_cast(controlQubit), edges); +} + +/// Diagonal entries of a flattened 2×2 edge block (indices 0 and 3). +[[nodiscard]] constexpr bool isTwoByTwoDiagonal(const std::size_t i) { + return i == 0U || i == 3U; +} + +template +void wrapControlsUntil(Package& dd, qc::Controls::const_iterator& it, + const qc::Controls::const_iterator end, + const qc::Qubit bound, + std::array, Dim>& em) { + for (; it != end && it->qubit < bound; ++it) { + for (std::size_t row = 0; row < Dim; ++row) { + for (std::size_t col = 0; col < Dim; ++col) { + em[row][col] = makeControlledNode(dd, it->qubit, it->type, em[row][col], + row == col); + } + } + } +} + +void wrapControlsUntil(Package& dd, qc::Controls::const_iterator& it, + const qc::Controls::const_iterator end, + const qc::Qubit bound, + std::array& em) { + for (; it != end && it->qubit < bound; ++it) { + for (std::size_t i = 0; i < NEDGE; ++i) { + em[i] = makeControlledNode(dd, it->qubit, it->type, em[i], + isTwoByTwoDiagonal(i)); + } + } +} + +void wrapControlsAbove(Package& dd, qc::Controls::const_iterator& it, + const qc::Controls::const_iterator end, mCachedEdge& e) { + for (; it != end; ++it) { + e = makeControlledNode(dd, it->qubit, it->type, e, true); + } +} + +[[nodiscard]] mEdge toMatrixDD(Package& dd, const mCachedEdge& e) { + return {.p = e.p, .w = dd.cn.lookup(e.w)}; +} + +} // namespace + mEdge Package::makeGateDD(const GateMatrix& mat, const qc::Qubit target) { return makeGateDD(mat, qc::Controls{}, target); } @@ -190,66 +322,24 @@ mEdge Package::makeGateDD(const GateMatrix& mat, const qc::Control& control, } mEdge Package::makeGateDD(const GateMatrix& mat, const qc::Controls& controls, const qc::Qubit target) { - if (std::ranges::any_of(controls, - [this](const auto& c) { - return c.qubit > static_cast(nqubits - 1U); - }) || - target > static_cast(nqubits - 1U)) { - throw std::runtime_error{ - "Requested gate acting on qubit(s) with index larger than " + - std::to_string(nqubits - 1U) + - " while the package configuration only supports up to " + - std::to_string(nqubits) + - " qubits. Please allocate a larger package instance."}; - } + ensureGateQubitsInRange(nqubits, controls, {target}); + std::array em{}; - for (auto i = 0U; i < NEDGE; ++i) { - em[i] = mCachedEdge::terminal(mat[i]); - } + fillTerminalVector(em, mat); if (controls.empty()) { // Single qubit operation - const auto e = makeDDNode(static_cast(target), em); - return {e.p, cn.lookup(e.w)}; + return toMatrixDD(*this, makeDDNode(static_cast(target), em)); } auto it = controls.begin(); - auto edges = std::array{mCachedEdge::zero(), mCachedEdge::zero(), - mCachedEdge::zero(), mCachedEdge::zero()}; - - // process lines below target - for (; it != controls.end() && it->qubit < target; ++it) { - for (auto i1 = 0U; i1 < RADIX; ++i1) { - for (auto i2 = 0U; i2 < RADIX; ++i2) { - const auto i = (i1 * RADIX) + i2; - if (it->type == qc::Control::Type::Neg) { // neg. control - edges[0] = em[i]; - edges[3] = (i1 == i2) ? mCachedEdge::one() : mCachedEdge::zero(); - } else { // pos. control - edges[0] = (i1 == i2) ? mCachedEdge::one() : mCachedEdge::zero(); - edges[3] = em[i]; - } - em[i] = makeDDNode(static_cast(it->qubit), edges); - } - } - } + const auto endIt = controls.end(); + wrapControlsUntil(*this, it, endIt, target, em); // target line auto e = makeDDNode(static_cast(target), em); - - // process lines above target - for (; it != controls.end(); ++it) { - if (it->type == qc::Control::Type::Neg) { // neg. control - edges[0] = e; - edges[3] = mCachedEdge::one(); - e = makeDDNode(static_cast(it->qubit), edges); - } else { // pos. control - edges[0] = mCachedEdge::one(); - edges[3] = e; - e = makeDDNode(static_cast(it->qubit), edges); - } - } - return {e.p, cn.lookup(e.w)}; + wrapControlsAbove(*this, it, endIt, e); + return toMatrixDD(*this, e); } mEdge Package::makeTwoQubitGateDD(const TwoQubitGateMatrix& mat, const qc::Qubit target0, @@ -266,52 +356,15 @@ mEdge Package::makeTwoQubitGateDD(const TwoQubitGateMatrix& mat, const qc::Controls& controls, const qc::Qubit target0, const qc::Qubit target1) { - // sanity check - if (std::ranges::any_of(controls, - [this](const auto& c) { - return c.qubit > static_cast(nqubits - 1U); - }) || - target0 > static_cast(nqubits - 1U) || - target1 > static_cast(nqubits - 1U)) { - throw std::runtime_error{ - "Requested gate acting on qubit(s) with index larger than " + - std::to_string(nqubits - 1U) + - " while the package configuration only supports up to " + - std::to_string(nqubits) + - " qubits. Please allocate a larger package instance."}; - } + ensureGateQubitsInRange(nqubits, controls, {target0, target1}); - // create terminal edge matrix std::array, NEDGE> em{}; - for (auto i1 = 0U; i1 < NEDGE; i1++) { - const auto& matRow = mat.at(i1); - auto& emRow = em.at(i1); - for (auto i2 = 0U; i2 < NEDGE; i2++) { - emRow.at(i2) = mCachedEdge::terminal(matRow.at(i2)); - } - } + fillTerminalMatrix(em, mat); - // process lines below smaller target auto it = controls.begin(); + const auto endIt = controls.end(); const auto smallerTarget = std::min(target0, target1); - - auto edges = std::array{mCachedEdge::zero(), mCachedEdge::zero(), - mCachedEdge::zero(), mCachedEdge::zero()}; - - for (; it != controls.end() && it->qubit < smallerTarget; ++it) { - for (auto row = 0U; row < NEDGE; ++row) { - for (auto col = 0U; col < NEDGE; ++col) { - if (it->type == qc::Control::Type::Neg) { // negative control - edges[0] = em[row][col]; - edges[3] = (row == col) ? mCachedEdge::one() : mCachedEdge::zero(); - } else { // positive control - edges[0] = (row == col) ? mCachedEdge::one() : mCachedEdge::zero(); - edges[3] = em[row][col]; - } - em[row][col] = makeDDNode(static_cast(it->qubit), edges); - } - } - } + wrapControlsUntil(*this, it, endIt, smallerTarget, em); // process the smaller target by taking the 16 submatrices and appropriately // combining them into four DDs. @@ -339,41 +392,110 @@ mEdge Package::makeTwoQubitGateDD(const TwoQubitGateMatrix& mat, } } - // process lines between the two targets const auto largerTarget = std::max(target0, target1); - for (; it != controls.end() && it->qubit < largerTarget; ++it) { - for (auto i = 0U; i < NEDGE; ++i) { - if (it->type == qc::Control::Type::Neg) { // negative control - edges[0] = em0[i]; - edges[3] = - (i == 0 || i == 3) ? mCachedEdge::one() : mCachedEdge::zero(); - } else { // positive control - edges[0] = - (i == 0 || i == 3) ? mCachedEdge::one() : mCachedEdge::zero(); - edges[3] = em0[i]; - } - em0[i] = makeDDNode(static_cast(it->qubit), edges); - } - } + wrapControlsUntil(*this, it, endIt, largerTarget, em0); // process the larger target by combining the four DDs from the smaller // target auto e = makeDDNode(static_cast(largerTarget), em0); + wrapControlsAbove(*this, it, endIt, e); + return toMatrixDD(*this, e); +} +mEdge Package::makeThreeQubitGateDD(const ThreeQubitGateMatrix& mat, + const qc::Qubit target0, + const qc::Qubit target1, + const qc::Qubit target2) { + return makeThreeQubitGateDD(mat, qc::Controls{}, target0, target1, target2); +} +mEdge Package::makeThreeQubitGateDD(const ThreeQubitGateMatrix& mat, + const qc::Control& control, + const qc::Qubit target0, + const qc::Qubit target1, + const qc::Qubit target2) { + return makeThreeQubitGateDD(mat, qc::Controls{control}, target0, target1, + target2); +} +mEdge Package::makeThreeQubitGateDD(const ThreeQubitGateMatrix& mat, + const qc::Controls& controls, + const qc::Qubit target0, + const qc::Qubit target1, + const qc::Qubit target2) { + // Bottom-up construction analogous to makeTwoQubitGateDD: materialize the + // 8×8 as terminals in MSB-first order (targets[0] = high bit), sort targets + // by qubit index, then reduce 8×8 → 4×4 → 4 edges → root while inserting + // controls on the free lines between those levels. + ensureGateQubitsInRange(nqubits, controls, {target0, target1, target2}); + + std::array, + THREE_QUBIT_GATE_DIM> + em{}; + fillTerminalMatrix(em, mat); + + // process targets in ascending qubit order; matrix bits are MSB-first + // (2 -> target0, 1 -> target1, 0 -> target2) + std::array, 3> ordered{ + {{target0, 2}, {target1, 1}, {target2, 0}}}; + std::ranges::sort(ordered, {}, &std::pair::first); + const auto qLow = ordered[0].first; + const auto qMid = ordered[1].first; + const auto qHigh = ordered[2].first; + const auto bLow = ordered[0].second; + const auto bMid = ordered[1].second; + const auto bHigh = ordered[2].second; - // process lines above the larger target - for (; it != controls.end(); ++it) { - if (it->type == qc::Control::Type::Neg) { // negative control - edges[0] = e; - edges[3] = mCachedEdge::one(); - } else { // positive control - edges[0] = mCachedEdge::one(); - edges[3] = e; + auto it = controls.begin(); + const auto endIt = controls.end(); + wrapControlsUntil(*this, it, endIt, qLow, em); + + // process the lowest target: reduce 8×8 to a 4×4 over the remaining bits + // (index = bit(mid) + 2 * bit(high)) + std::array, NEDGE> emMid{}; + for (std::size_t rMH = 0; rMH < NEDGE; ++rMH) { + for (std::size_t cMH = 0; cMH < NEDGE; ++cMH) { + const auto rMidBit = rMH & 1U; + const auto rHighBit = rMH >> 1U; + const auto cMidBit = cMH & 1U; + const auto cHighBit = cMH >> 1U; + std::array local{}; + for (std::size_t i = 0; i < RADIX; ++i) { + for (std::size_t j = 0; j < RADIX; ++j) { + const auto rowIdx = + (i << bLow) | (rMidBit << bMid) | (rHighBit << bHigh); + const auto colIdx = + (j << bLow) | (cMidBit << bMid) | (cHighBit << bHigh); + local.at((i * RADIX) + j) = em.at(rowIdx).at(colIdx); + } + } + emMid.at(rMH).at(cMH) = makeDDNode(static_cast(qLow), local); } - e = makeDDNode(static_cast(it->qubit), edges); } - return {e.p, cn.lookup(e.w)}; + wrapControlsUntil(*this, it, endIt, qMid, emMid); + + // process the middle target: reduce 4×4 to four DDs over the highest bit + std::array emHigh{}; + for (std::size_t row = 0; row < RADIX; ++row) { + for (std::size_t col = 0; col < RADIX; ++col) { + std::array local{}; + for (std::size_t i = 0; i < RADIX; ++i) { + for (std::size_t j = 0; j < RADIX; ++j) { + local.at((i * RADIX) + j) = + emMid.at(i + (row * RADIX)).at(j + (col * RADIX)); + } + } + emHigh.at((row * RADIX) + col) = + makeDDNode(static_cast(qMid), local); + } + } + + wrapControlsUntil(*this, it, endIt, qHigh, emHigh); + + // process the highest target + auto e = makeDDNode(static_cast(qHigh), emHigh); + wrapControlsAbove(*this, it, endIt, e); + return toMatrixDD(*this, e); } + mEdge Package::makeDDFromMatrix(const CMat& matrix) { if (matrix.empty()) { return mEdge::one(); @@ -395,7 +517,7 @@ mEdge Package::makeDDFromMatrix(const CMat& matrix) { const auto level = static_cast(std::log2(length) - 1); const auto matrixDD = makeDDFromMatrix(matrix, level, 0, length, 0, width); - return {matrixDD.p, cn.lookup(matrixDD.w)}; + return {.p = matrixDD.p, .w = cn.lookup(matrixDD.w)}; } mCachedEdge Package::makeDDFromMatrix(const CMat& matrix, const Qubit level, const std::size_t rowStart, @@ -465,7 +587,7 @@ std::string Package::measureAll(vEdge& rootEdge, const bool collapse, std::string result(numberOfQubits, '0'); - std::uniform_real_distribution dist(0.0, 1.0L); + std::uniform_real_distribution dist(0.0, 1.0); for (auto i = numberOfQubits; i > 0; --i) { fp p0 = ComplexNumbers::mag2(cur.p->e.at(0).w); @@ -656,7 +778,7 @@ void Package::performCollapsingMeasurement(vEdge& rootEdge, const Qubit index, } vEdge Package::conjugate(const vEdge& a) { const auto r = conjugateRec(a); - return {r.p, cn.lookup(r.w)}; + return {.p = r.p, .w = cn.lookup(r.w)}; } vCachedEdge Package::conjugateRec(const vEdge& a) { if (a.isZeroTerminal()) { @@ -681,7 +803,7 @@ vCachedEdge Package::conjugateRec(const vEdge& a) { } mEdge Package::conjugateTranspose(const mEdge& a) { const auto r = conjugateTransposeRec(a); - return {r.p, cn.lookup(r.w)}; + return {.p = r.p, .w = cn.lookup(r.w)}; } mCachedEdge Package::conjugateTransposeRec(const mEdge& a) { if (a.isTerminal()) { // terminal case @@ -745,7 +867,7 @@ ComplexValue Package::innerProduct(const vEdge& x, const vEdge& y) { const auto w = std::max(x.p->v, y.p->v); // Overall normalization factor needs to be conjugated // before input into recursive private function - auto xCopy = vEdge{x.p, ComplexNumbers::conj(x.w)}; + auto xCopy = vEdge{.p = x.p, .w = ComplexNumbers::conj(x.w)}; return innerProduct(xCopy, y, w + 1U); } fp Package::fidelity(const vEdge& x, const vEdge& y) { @@ -855,7 +977,7 @@ fp Package::expectationValue(const mEdge& x, const vEdge& y) { mEdge Package::partialTrace(const mEdge& a, const std::vector& eliminate) { auto r = trace(a, eliminate, eliminate.size()); - return {r.p, cn.lookup(r.w)}; + return {.p = r.p, .w = cn.lookup(r.w)}; } bool Package::isCloseToIdentity(const mEdge& m, const fp tol, const std::vector& garbage, @@ -982,7 +1104,7 @@ mEdge Package::reduceAncillae(mEdge e, const std::vector& ancillary, mCachedEdge::zero()}); } } - const auto res = mEdge{g.p, cn.lookup(g.w * e.w)}; + const auto res = mEdge{.p = g.p, .w = cn.lookup(g.w * e.w)}; incRef(res); decRef(e); return res; @@ -1011,7 +1133,7 @@ vEdge Package::reduceGarbage(vEdge& e, const std::vector& garbage, if (normalizeWeights) { weight = weight.mag(); } - const auto res = vEdge{f.p, cn.lookup(weight)}; + const auto res = vEdge{.p = f.p, .w = cn.lookup(weight)}; incRef(res); decRef(e); return res; @@ -1075,7 +1197,7 @@ mEdge Package::reduceGarbage(const mEdge& e, const std::vector& garbage, if (normalizeWeights) { weight = weight.mag(); } - const auto res = mEdge{g.p, cn.lookup(weight)}; + const auto res = mEdge{.p = g.p, .w = cn.lookup(weight)}; incRef(res); decRef(e); diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index 4b42160f9b..fb3e7156a6 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -11,6 +11,7 @@ #include "fomac/FoMaC.hpp" #include "qdmi/common/Common.hpp" +#include "qdmi/driver/Driver.hpp" #include #include @@ -25,15 +26,32 @@ #include #include #include +#include #include #include #include +#include #include #include #include #include namespace fomac { +namespace { +[[nodiscard]] constexpr bool +isBinaryProgramFormat(const QDMI_Program_Format format) noexcept { + return format == QDMI_PROGRAM_FORMAT_QIRBASEMODULE || + format == QDMI_PROGRAM_FORMAT_QIRADAPTIVEMODULE || + format == QDMI_PROGRAM_FORMAT_QPY; +} + +[[nodiscard]] constexpr bool +hasNoGenericProgramPayload(const QDMI_Program_Format format) noexcept { + return format == QDMI_PROGRAM_FORMAT_CALIBRATION || + format == QDMI_PROGRAM_FORMAT_BATCHJOB; +} +} // namespace + size_t Site::getIndex() const { return queryProperty(QDMI_SITE_PROPERTY_INDEX); } @@ -134,10 +152,9 @@ std::optional> Operation::getSites() const { } std::vector returnedSites; returnedSites.reserve(qdmiSites->size()); - std::ranges::transform(*qdmiSites, std::back_inserter(returnedSites), - [device = device_](const QDMI_Site& site) -> Site { - return {device, site}; - }); + std::ranges::transform( + *qdmiSites, std::back_inserter(returnedSites), + [this](const QDMI_Site& site) -> Site { return {device_, site}; }); return returnedSites; } std::optional>> @@ -199,7 +216,7 @@ std::vector Device::getSites() const { sites.reserve(qdmiSites.size()); std::ranges::transform( qdmiSites, std::back_inserter(sites), - [this](const QDMI_Site& site) -> Site { return {this, site}; }); + [this](const QDMI_Site& site) -> Site { return {device_, site}; }); return sites; } @@ -227,7 +244,7 @@ std::vector Device::getOperations() const { operations.reserve(qdmiOperations.size()); std::ranges::transform( qdmiOperations, std::back_inserter(operations), - [this](const QDMI_Operation& op) -> Operation { return {this, op}; }); + [this](const QDMI_Operation& op) -> Operation { return {device_, op}; }); return operations; } @@ -245,8 +262,10 @@ Device::getCouplingMap() const { std::ranges::transform(*qdmiCouplingMap, std::back_inserter(couplingMap), [this](const std::pair& pair) -> std::pair { - return {Site{this, pair.first}, - Site{this, pair.second}}; + return { + Site{device_, pair.first}, + Site{device_, pair.second}, + }; }); return couplingMap; } @@ -289,7 +308,7 @@ std::vector Device::getSupportedProgramFormats() const { std::vector Device::getChildDevices() const { size_t size = 0; auto result = QDMI_device_query_device_property( - device_, QDMI_DEVICE_PROPERTY_CHILDDEVICES, 0, nullptr, &size); + device_.get(), QDMI_DEVICE_PROPERTY_CHILDDEVICES, 0, nullptr, &size); if (result == QDMI_ERROR_NOTSUPPORTED) { return {}; } @@ -301,7 +320,7 @@ std::vector Device::getChildDevices() const { std::vector handles(size / sizeof(QDMI_Device)); if (size != 0) { result = QDMI_device_query_device_property( - device_, QDMI_DEVICE_PROPERTY_CHILDDEVICES, size, + device_.get(), QDMI_DEVICE_PROPERTY_CHILDDEVICES, size, static_cast(handles.data()), nullptr); qdmi::throwIfError(result, "Querying child devices"); } @@ -310,7 +329,9 @@ std::vector Device::getChildDevices() const { devices.reserve(handles.size()); std::ranges::transform( handles, std::back_inserter(devices), - [](QDMI_Device_impl_d* const handle) { return Device(handle); }); + [this](QDMI_Device_impl_d* const handle) { + return Device(std::shared_ptr(device_, handle)); + }); return devices; } @@ -321,18 +342,46 @@ Job Device::submitJob(const std::string& program, const std::optional& custom3, const std::optional& custom4, const std::optional& custom5) const { + if (isBinaryProgramFormat(format)) { + throw std::invalid_argument( + "Binary program formats require exact-byte submission"); + } + if (hasNoGenericProgramPayload(format)) { + throw std::invalid_argument( + "Calibration and batch jobs do not use a generic program payload"); + } + + const auto bytes = std::as_bytes( + std::span(program.c_str(), static_cast(program.size() + 1))); + return submitJob(bytes, format, numShots, custom1, custom2, custom3, custom4, + custom5); +} + +Job Device::submitJob(const std::span program, + const QDMI_Program_Format format, const size_t numShots, + const std::optional& custom1, + const std::optional& custom2, + const std::optional& custom3, + const std::optional& custom4, + const std::optional& custom5) const { + if (hasNoGenericProgramPayload(format)) { + throw std::invalid_argument( + "Calibration and batch jobs do not use a generic program payload"); + } + QDMI_Job job = nullptr; - qdmi::throwIfError(QDMI_device_create_job(device_, &job), "Creating job"); - Job jobWrapper{job}; + qdmi::throwIfError(QDMI_device_create_job(device_.get(), &job), + "Creating job"); + Job jobWrapper{job, device_}; qdmi::throwIfError(QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_PROGRAMFORMAT, sizeof(format), &format), "Setting program format"); - qdmi::throwIfError( - QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_PROGRAM, - program.size() + 1, program.c_str()), - "Setting program"); + qdmi::throwIfError(QDMI_job_set_parameter(jobWrapper, + QDMI_JOB_PARAMETER_PROGRAM, + program.size(), program.data()), + "Setting program"); qdmi::throwIfError(QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_SHOTSNUM, sizeof(numShots), &numShots), @@ -402,6 +451,16 @@ void Job::cancel() const { qdmi::throwIfError(QDMI_job_cancel(job_.get()), "Cancelling job"); } +auto Job::operator=(Job&& other) noexcept -> Job& { + if (this != &other) { + // Release the current job while its owning device session is still alive. + job_.reset(); + device_ = std::move(other.device_); + job_ = std::move(other.job_); + } + return *this; +} + std::string Job::getId() const { size_t size = 0; qdmi::throwIfError(QDMI_job_query_property(job_.get(), QDMI_JOB_PROPERTY_ID, @@ -423,21 +482,39 @@ QDMI_Program_Format Job::getProgramFormat() const { return format; } -std::string Job::getProgram() const { +std::vector Job::getProgramBytes() const { size_t size = 0; qdmi::throwIfError(QDMI_job_query_property(job_.get(), QDMI_JOB_PROPERTY_PROGRAM, 0, nullptr, &size), "Querying program size"); - std::string program(size - 1, '\0'); - qdmi::throwIfError(QDMI_job_query_property(job_.get(), - QDMI_JOB_PROPERTY_PROGRAM, size, - program.data(), nullptr), - "Querying program"); + std::vector program(size); + if (size != 0) { + qdmi::throwIfError(QDMI_job_query_property(job_.get(), + QDMI_JOB_PROPERTY_PROGRAM, size, + program.data(), nullptr), + "Querying program"); + } return program; } +std::string Job::getProgram() const { + const auto format = getProgramFormat(); + if (isBinaryProgramFormat(format)) { + throw std::invalid_argument( + "Cannot decode a binary program as a string; use getProgramBytes()"); + } + + const auto program = getProgramBytes(); + if (program.empty() || program.back() != std::byte{0}) { + throw std::invalid_argument( + "Cannot decode program as a null-terminated string; use " + "getProgramBytes() for binary payloads"); + } + return {reinterpret_cast(program.data()), program.size() - 1}; +} + size_t Job::getNumShots() const { size_t numShots = 0; qdmi::throwIfError( @@ -685,6 +762,11 @@ Device Session::createSessionlessDevice(QDMI_Device device) { return Device(device); } +Device Session::openDevice(const std::string_view id, + const qdmi::DeviceSessionConfig& overrides) { + return Device(qdmi::Driver::get().openFresh(id, overrides)); +} + Session::Session(const SessionConfig& config) { session_ = [] { QDMI_Session session = nullptr; @@ -720,7 +802,7 @@ Session::Session(const SessionConfig& config) { if (config.authFile) { if (!std::filesystem::exists(*config.authFile)) { throw std::runtime_error("Authentication file does not exist: " + - *config.authFile); + config.authFile->string()); } } // Validate URL format for authUrl @@ -757,7 +839,10 @@ Session::Session(const SessionConfig& config) { // Set session parameters setParameter(config.token, QDMI_SESSION_PARAMETER_TOKEN); - setParameter(config.authFile, QDMI_SESSION_PARAMETER_AUTHFILE); + if (config.authFile) { + const std::optional authFile = config.authFile->string(); + setParameter(authFile, QDMI_SESSION_PARAMETER_AUTHFILE); + } setParameter(config.authUrl, QDMI_SESSION_PARAMETER_AUTHURL); setParameter(config.username, QDMI_SESSION_PARAMETER_USERNAME); setParameter(config.password, QDMI_SESSION_PARAMETER_PASSWORD); diff --git a/src/ir/QuantumComputation.cpp b/src/ir/QuantumComputation.cpp index e0b63f17b0..10736a67d2 100644 --- a/src/ir/QuantumComputation.cpp +++ b/src/ir/QuantumComputation.cpp @@ -980,6 +980,13 @@ void QuantumComputation::checkQubitRange(const Qubit qubit0, const Qubit qubit1, checkQubitRange(qubit1); } +void QuantumComputation::checkQubitRange(const Qubit qubit0, const Qubit qubit1, + const Qubit qubit2, + const Controls& controls) const { + checkQubitRange(qubit0, qubit1, controls); + checkQubitRange(qubit2); +} + void QuantumComputation::checkQubitRange( const std::vector& qubits) const { for (const auto& qubit : qubits) { @@ -1528,6 +1535,27 @@ DEFINE_TWO_TARGET_TWO_PARAMETER_OPERATION(xx_plus_yy, theta, beta) #undef DEFINE_TWO_TARGET_TWO_PARAMETER_OPERATION +#define DEFINE_THREE_TARGET_OPERATION(op) \ + void QuantumComputation::op(const Qubit target0, const Qubit target1, \ + const Qubit target2) { \ + mc##op(Controls{}, target0, target1, target2); \ + } \ + void QuantumComputation::c##op(const Control& control, const Qubit target0, \ + const Qubit target1, const Qubit target2) { \ + mc##op(Controls{control}, target0, target1, target2); \ + } \ + void QuantumComputation::mc##op(const Controls& controls, \ + const Qubit target0, const Qubit target1, \ + const Qubit target2) { \ + checkQubitRange(target0, target1, target2, controls); \ + emplace_back( \ + controls, Targets{target0, target1, target2}, opTypeFromString(#op)); \ + } + +DEFINE_THREE_TARGET_OPERATION(rccx) + +#undef DEFINE_THREE_TARGET_OPERATION + void QuantumComputation::measure(const Qubit qubit, const std::size_t bit) { checkQubitRange(qubit); checkBitRange(bit); diff --git a/src/ir/operations/OpType.cpp b/src/ir/operations/OpType.cpp index 8e6f4288e3..9d912c5d64 100644 --- a/src/ir/operations/OpType.cpp +++ b/src/ir/operations/OpType.cpp @@ -123,6 +123,7 @@ constexpr std::array OP_NAME_TO_TYPE{ NameToType{.name = "phase", .type = P}, NameToType{.name = "prx", .type = R}, NameToType{.name = "r", .type = R}, + NameToType{.name = "rccx", .type = RCCX}, NameToType{.name = "reset", .type = Reset}, NameToType{.name = "rx", .type = RX}, NameToType{.name = "rxx", .type = RXX}, diff --git a/src/ir/operations/StandardOperation.cpp b/src/ir/operations/StandardOperation.cpp index d8a59c1403..46da22b8b0 100644 --- a/src/ir/operations/StandardOperation.cpp +++ b/src/ir/operations/StandardOperation.cpp @@ -301,7 +301,7 @@ void StandardOperation::dumpOpenQASM2( "work with this library.\n"; } - // safe the numbers of controls as a prefix to the operation name + // save the numbers of controls as a prefix to the operation name op << std::string(controls.size(), 'c'); const bool isSpecialGate = type == Peres || type == Peresdg; @@ -448,6 +448,9 @@ void StandardOperation::dumpGateType( case XXplusYY: op << "xx_plus_yy(" << parameter[0] << "," << parameter[1] << ")"; break; + case RCCX: + op << "rccx"; + break; case SWAP: op << "swap"; break; @@ -595,6 +598,7 @@ void StandardOperation::invert() { case H: case SWAP: case ECR: + case RCCX: case Barrier: break; // gates where we just update parameters diff --git a/src/qasm3/Parser.cpp b/src/qasm3/Parser.cpp index 04ae35473b..411cd168ac 100644 --- a/src/qasm3/Parser.cpp +++ b/src/qasm3/Parser.cpp @@ -276,6 +276,9 @@ void Parser::parseInclude() { if (in->fail()) { if (filename == "stdgates.inc") { // stdgates.inc has already been included implicitly, so we just return + if (includeDebugInfo) { + includeDebugInfo = includeDebugInfo->parent; + } return; } if (filename == "qelib1.inc") { diff --git a/src/qdmi/devices/CMakeLists.txt b/src/qdmi/devices/CMakeLists.txt index 8977e78924..a5aac6457c 100644 --- a/src/qdmi/devices/CMakeLists.txt +++ b/src/qdmi/devices/CMakeLists.txt @@ -6,8 +6,12 @@ # # Licensed under the MIT License -add_subdirectory(dd) -add_subdirectory(sc) +if(BUILD_MQT_CORE_QDMI_DDSIM_DEVICE) + add_subdirectory(dd) +endif() +if(BUILD_MQT_CORE_QDMI_SC_DEVICE) + add_subdirectory(sc) +endif() add_subdirectory(na) set(MQT_CORE_TARGETS diff --git a/src/qdmi/devices/dd/CMakeLists.txt b/src/qdmi/devices/dd/CMakeLists.txt index bfebf8a92a..966028aaba 100644 --- a/src/qdmi/devices/dd/CMakeLists.txt +++ b/src/qdmi/devices/dd/CMakeLists.txt @@ -40,12 +40,12 @@ if(NOT TARGET ${TARGET_NAME}) # Add link libraries target_link_libraries(${TARGET_NAME} PRIVATE MQT::CoreDD MQT::CoreQASM MQT::CoreCircuitOptimizer MQT::CoreQDMICommon spdlog::spdlog) + if(BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR) + target_link_libraries(${TARGET_NAME} PRIVATE MQT::CoreQIRJIT MQT::CoreQIRRuntime) + target_compile_definitions(${TARGET_NAME} PRIVATE BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR) + endif() - # Make QDMI version available and ensure symbols are exported when building the library - target_compile_definitions(${TARGET_NAME} PRIVATE QDMI_VERSION="${QDMI_VERSION}" - ${QDMI_PREFIX}_QDMI_device_EXPORTS) - - # Add to list of MQT Core targets + mqt_configure_qdmi_device(${TARGET_NAME} ID mqt.ddsim.default PREFIX ${QDMI_PREFIX}) list(APPEND MQT_CORE_TARGETS ${TARGET_NAME}) endif() diff --git a/src/qdmi/devices/dd/Device.cpp b/src/qdmi/devices/dd/Device.cpp index 4fc828b3e4..2e90af27e9 100644 --- a/src/qdmi/devices/dd/Device.cpp +++ b/src/qdmi/devices/dd/Device.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -41,8 +42,21 @@ #include #include #include +#include #include #include +#include +#include + +#ifdef BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR +#include "qir/jit/Session.hpp" +#include "qir/runtime/Runtime.hpp" + +#include +#include + +#include +#endif namespace { constexpr uintptr_t OFFSET = 0x10000U; @@ -114,6 +128,7 @@ constexpr std::array OPERATIONS{ OperationInfo{.name = "rzx", .numSites = 2, .numParams = 1}, OperationInfo{.name = "xx_minus_yy", .numSites = 2, .numParams = 2}, OperationInfo{.name = "xx_plus_yy", .numSites = 2, .numParams = 2}, + OperationInfo{.name = "rccx", .numSites = 3, .numParams = 0}, OperationInfo{.name = "measure", .numSites = 1, .numParams = 0}, OperationInfo{.name = "reset", .numSites = 1, .numParams = 0}, OperationInfo{ @@ -346,7 +361,14 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::setParameter( return QDMI_ERROR_INVALIDARGUMENT; } if (format != QDMI_PROGRAM_FORMAT_QASM2 && - format != QDMI_PROGRAM_FORMAT_QASM3) { + format != QDMI_PROGRAM_FORMAT_QASM3 +#ifdef BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR + && format != QDMI_PROGRAM_FORMAT_QIRBASEMODULE && + format != QDMI_PROGRAM_FORMAT_QIRBASESTRING && + format != QDMI_PROGRAM_FORMAT_QIRADAPTIVEMODULE && + format != QDMI_PROGRAM_FORMAT_QIRADAPTIVESTRING +#endif + ) { return QDMI_ERROR_NOTSUPPORTED; } format_ = format; @@ -354,7 +376,28 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::setParameter( return QDMI_SUCCESS; case QDMI_DEVICE_JOB_PARAMETER_PROGRAM: if (value != nullptr) { - program_ = std::string(static_cast(value), size - 1); + const bool isTextProgramFormat = + format_ == QDMI_PROGRAM_FORMAT_QASM2 || + format_ == QDMI_PROGRAM_FORMAT_QASM3 || + format_ == QDMI_PROGRAM_FORMAT_QIRBASESTRING || + format_ == QDMI_PROGRAM_FORMAT_QIRADAPTIVESTRING; + if (isTextProgramFormat) { + // Text payloads include the trailing '\0' in `size`. + // Strip it so it is not counted in the stored string's size. + const std::span text{static_cast(value), size}; + if (text.empty() || text.back() != '\0') { + return QDMI_ERROR_INVALIDARGUMENT; + } + const auto contents = text.first(text.size() - 1); + if (std::ranges::find(contents, '\0') != contents.end()) { + return QDMI_ERROR_INVALIDARGUMENT; + } + program_ = std::string(contents.begin(), contents.end()); + } else { + // Binary payloads are stored exactly as received. + const std::span bytes(static_cast(value), size); + program_ = std::vector(bytes.begin(), bytes.end()); + } } return QDMI_SUCCESS; case QDMI_DEVICE_JOB_PARAMETER_SHOTSNUM: @@ -380,50 +423,139 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::queryProperty( ADD_SINGLE_VALUE_PROPERTY(QDMI_DEVICE_JOB_PROPERTY_PROGRAMFORMAT, QDMI_Program_Format, format_, prop, size, value, sizeRet) - ADD_STRING_PROPERTY(QDMI_DEVICE_JOB_PROPERTY_PROGRAM, program_.c_str(), prop, + if (std::holds_alternative(program_)) { + const auto& text = std::get(program_); + ADD_STRING_PROPERTY(QDMI_DEVICE_JOB_PROPERTY_PROGRAM, text.c_str(), prop, + size, value, sizeRet) + } else { + const auto& bytes = std::get>(program_); + ADD_LIST_PROPERTY(QDMI_DEVICE_JOB_PROPERTY_PROGRAM, std::byte, bytes, prop, size, value, sizeRet) + } ADD_SINGLE_VALUE_PROPERTY(QDMI_DEVICE_JOB_PROPERTY_SHOTSNUM, size_t, numShots_, prop, size, value, sizeRet) return QDMI_ERROR_NOTSUPPORTED; } +auto MQT_DDSIM_QDMI_Device_Job_impl_d::submitProgramAsync( + std::function body) -> QDMI_STATUS { + jobHandle_ = std::async(std::launch::async, [this, body = std::move(body)]() { + qdmi::dd::Device::get().increaseRunningJobs(); + status_.store(QDMI_JOB_STATUS_RUNNING); + try { + body(); + status_.store(QDMI_JOB_STATUS_DONE); + } catch (const std::exception& e) { + status_.store(QDMI_JOB_STATUS_FAILED); + std::cerr << "Error: " << e.what() << '\n'; + } + qdmi::dd::Device::get().decreaseRunningJobs(); + }); + return QDMI_SUCCESS; +} +auto MQT_DDSIM_QDMI_Device_Job_impl_d::submitQASMProgram() -> QDMI_STATUS { + return numShots_ > 0 ? submitQASMProgramSampling() + : submitQASMProgramStateExtraction(); +} +auto MQT_DDSIM_QDMI_Device_Job_impl_d::submitQASMProgramSampling() + -> QDMI_STATUS { + return submitProgramAsync([this]() { + const auto& text = std::get(program_); + const auto qc = qasm3::Importer::imports(text); + counts_ = dd::sample(qc, numShots_); + }); +} +auto MQT_DDSIM_QDMI_Device_Job_impl_d::submitQASMProgramStateExtraction() + -> QDMI_STATUS { + return submitProgramAsync([this]() { + const auto& text = std::get(program_); + auto qc = qasm3::Importer::imports(text); + qc::CircuitOptimizer::removeFinalMeasurements(qc); + const auto nQubits = qc.getNqubits(); + dd_ = std::make_unique(nQubits); + stateVecDD_ = dd::simulate(qc, dd::makeZeroState(nQubits, *dd_), *dd_); + }); +} +#ifdef BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR +auto MQT_DDSIM_QDMI_Device_Job_impl_d::submitQIRProgram() -> QDMI_STATUS { + return numShots_ > 0 ? submitQIRProgramSampling() + : submitQIRProgramStateExtraction(); +} +auto MQT_DDSIM_QDMI_Device_Job_impl_d::submitQIRProgramSampling() + -> QDMI_STATUS { + return submitProgramAsync([this]() { + auto& runtime = qir::Runtime::getInstance(); + auto irBytes = std::visit( + [](const auto& p) { + return llvm::StringRef(reinterpret_cast(p.data()), + p.size()); + }, + program_); + auto jitSession = qir::JitSession(irBytes, "QDMI job"); + runtime.outputProgramHeader(); + for (size_t i = 0; i < numShots_; ++i) { + runtime.reset(); + runtime.outputShotStart(); + const auto rc = jitSession.run(); + runtime.outputShotEnd(); + if (rc != 0) { + throw std::runtime_error( + llvm::formatv("QIR program failed with error: {}", rc)); + } + // Update the measurement counts. + ++counts_[runtime.getMeasurements()]; + } + }); +} +auto MQT_DDSIM_QDMI_Device_Job_impl_d::submitQIRProgramStateExtraction() + -> QDMI_STATUS { + // State extraction strips measurement calls from the IR, which only + // preserves semantics for QIR Base Profile (measurements are terminal there). + // Adaptive Profile has measurement-dependent control flow, so stripping would + // silently change the program's meaning. + if (format_ != QDMI_PROGRAM_FORMAT_QIRBASEMODULE && + format_ != QDMI_PROGRAM_FORMAT_QIRBASESTRING) { + return QDMI_ERROR_NOTSUPPORTED; + } + return submitProgramAsync([this]() { + auto& runtime = qir::Runtime::getInstance(); + runtime.reset(); + auto irBytes = std::visit( + [](const auto& p) { + return llvm::StringRef(reinterpret_cast(p.data()), + p.size()); + }, + program_); + auto jitSession = + qir::JitSession(irBytes, "QDMI job", qir::Execution::StateExtraction); + if (const auto rc = jitSession.run(); rc != 0) { + throw std::runtime_error( + llvm::formatv("QIR program failed with error: {}", rc)); + } + auto state = runtime.takeState(); + dd_ = std::move(state.dd); + stateVecDD_ = state.edge; + }); +} +#endif auto MQT_DDSIM_QDMI_Device_Job_impl_d::submit() -> QDMI_STATUS { if (status_.load() != QDMI_JOB_STATUS_CREATED) { return QDMI_ERROR_BADSTATE; } status_.store(QDMI_JOB_STATUS_SUBMITTED); - if (numShots_ > 0) { - jobHandle_ = std::async(std::launch::async, [this]() { - qdmi::dd::Device::get().increaseRunningJobs(); - status_.store(QDMI_JOB_STATUS_RUNNING); - try { - const auto qc = qasm3::Importer::imports(program_); - counts_ = dd::sample(qc, numShots_); - status_.store(QDMI_JOB_STATUS_DONE); - } catch (const std::exception& e) { - status_.store(QDMI_JOB_STATUS_FAILED); - std::cerr << "Error: " << e.what() << '\n'; - } - qdmi::dd::Device::get().decreaseRunningJobs(); - }); - } else { - jobHandle_ = std::async(std::launch::async, [this]() { - try { - qdmi::dd::Device::get().increaseRunningJobs(); - status_.store(QDMI_JOB_STATUS_RUNNING); - auto qc = qasm3::Importer::imports(program_); - qc::CircuitOptimizer::removeFinalMeasurements(qc); - const auto nqubits = qc.getNqubits(); - dd_ = std::make_unique(nqubits); - stateVecDD_ = dd::simulate(qc, dd::makeZeroState(nqubits, *dd_), *dd_); - status_.store(QDMI_JOB_STATUS_DONE); - } catch (const std::exception& e) { - status_.store(QDMI_JOB_STATUS_FAILED); - std::cerr << "Error: " << e.what() << '\n'; - } - qdmi::dd::Device::get().decreaseRunningJobs(); - }); - } - return QDMI_SUCCESS; + if (format_ == QDMI_PROGRAM_FORMAT_QASM2 || + format_ == QDMI_PROGRAM_FORMAT_QASM3) { + return submitQASMProgram(); + } +#ifdef BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR + if (format_ == QDMI_PROGRAM_FORMAT_QIRBASEMODULE || + format_ == QDMI_PROGRAM_FORMAT_QIRBASESTRING || + format_ == QDMI_PROGRAM_FORMAT_QIRADAPTIVEMODULE || + format_ == QDMI_PROGRAM_FORMAT_QIRADAPTIVESTRING) { + return submitQIRProgram(); + } +#endif + // Format is validated against the allowed set at setParameter time. + qdmi::unreachable(); } auto MQT_DDSIM_QDMI_Device_Job_impl_d::cancel() -> QDMI_STATUS { const auto s = status_.load(); diff --git a/src/qdmi/devices/na/CMakeLists.txt b/src/qdmi/devices/na/CMakeLists.txt index fb77c9164c..b8c4781969 100644 --- a/src/qdmi/devices/na/CMakeLists.txt +++ b/src/qdmi/devices/na/CMakeLists.txt @@ -40,6 +40,13 @@ if(NOT TARGET ${TARGET_NAME}) RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") endif() +if(NOT BUILD_MQT_CORE_QDMI_NA_DEVICE) + set(MQT_CORE_TARGETS + ${MQT_CORE_TARGETS} + PARENT_SCOPE) + return() +endif() + # Set target name set(TARGET_NAME mqt-core-qdmi-na-device-generator) @@ -123,11 +130,7 @@ if(NOT TARGET ${TARGET_NAME}) # add link libraries target_link_libraries(${TARGET_NAME} PRIVATE MQT::CoreQDMICommon spdlog::spdlog) - # Make QDMI version available and ensure symbols are properly exported when building the library - target_compile_definitions(${TARGET_NAME} PRIVATE QDMI_VERSION="${QDMI_VERSION}" - ${QDMI_PREFIX}_QDMI_device_EXPORTS) - - # add to list of MQT core targets + mqt_configure_qdmi_device(${TARGET_NAME} ID mqt.na.default PREFIX ${QDMI_PREFIX}) list(APPEND MQT_CORE_TARGETS ${TARGET_NAME}) endif() diff --git a/src/qdmi/devices/sc/CMakeLists.txt b/src/qdmi/devices/sc/CMakeLists.txt index d6650f8799..22e78bfdb9 100644 --- a/src/qdmi/devices/sc/CMakeLists.txt +++ b/src/qdmi/devices/sc/CMakeLists.txt @@ -120,11 +120,7 @@ if(NOT TARGET ${TARGET_NAME}) # add link libraries target_link_libraries(${TARGET_NAME} PRIVATE MQT::CoreQDMICommon spdlog::spdlog) - # Make QDMI version available and ensure symbols are properly exported when building the library - target_compile_definitions(${TARGET_NAME} PRIVATE QDMI_VERSION="${QDMI_VERSION}" - ${QDMI_PREFIX}_QDMI_device_EXPORTS) - - # add to list of MQT core targets + mqt_configure_qdmi_device(${TARGET_NAME} ID mqt.sc.default PREFIX ${QDMI_PREFIX}) list(APPEND MQT_CORE_TARGETS ${TARGET_NAME}) endif() diff --git a/src/qdmi/driver/CMakeLists.txt b/src/qdmi/driver/CMakeLists.txt index 3712a4b386..a83cddd967 100644 --- a/src/qdmi/driver/CMakeLists.txt +++ b/src/qdmi/driver/CMakeLists.txt @@ -13,7 +13,7 @@ if(NOT TARGET ${TARGET_NAME}) add_mqt_core_library(${TARGET_NAME} ALIAS_NAME QDMIDriver) # Add sources to target - target_sources(${TARGET_NAME} PRIVATE Driver.cpp) + target_sources(${TARGET_NAME} PRIVATE DeviceRegistry.cpp Driver.cpp) # Add headers using file sets target_sources(${TARGET_NAME} PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_CORE_INCLUDE_BUILD_DIR} @@ -23,41 +23,24 @@ if(NOT TARGET ${TARGET_NAME}) target_link_libraries( ${TARGET_NAME} PUBLIC qdmi::qdmi MQT::CoreQDMICommon - PRIVATE qdmi::qdmi_project_warnings spdlog::spdlog ${CMAKE_DL_LIBS}) + PRIVATE qdmi::qdmi_project_warnings nlohmann_json::nlohmann_json spdlog::spdlog + ${CMAKE_DL_LIBS}) + target_include_directories(${TARGET_NAME} SYSTEM + PRIVATE ${PROJECT_SOURCE_DIR}/vendor/tomlplusplus) - add_dependencies(${TARGET_NAME} MQT::CoreQDMINaDevice MQT::CoreQDMIScDevice - MQT::CoreQDMI_DDSIM_Device) - target_compile_definitions( - ${TARGET_NAME} - PRIVATE - "DYN_DEV_LIBS=std::pair{\"$\", \"MQT_NA\"}, std::pair{\"$\", \"MQT_SC\"}, std::pair{\"$\", \"MQT_DDSIM\"}" - ) + mqt_get_qdmi_device_targets(QDMI_DEVICE_TARGETS) # Ensure the driver can find the device libraries at runtime - if(WIN32) - # On Windows, we need to copy the device DLLs to the library directory - add_custom_command( - TARGET ${TARGET_NAME} - PRE_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ - $) - add_custom_command( - TARGET ${TARGET_NAME} - PRE_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ - $) - add_custom_command( - TARGET ${TARGET_NAME} - PRE_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ - $) - else() - target_link_options( - ${TARGET_NAME} - INTERFACE - $> - $> - $>) + if(QDMI_DEVICE_TARGETS) + if(WIN32) + mqt_copy_qdmi_runtime(${TARGET_NAME} ${QDMI_DEVICE_TARGETS}) + else() + add_dependencies(${TARGET_NAME} ${QDMI_DEVICE_TARGETS}) + foreach(device IN LISTS QDMI_DEVICE_TARGETS) + target_link_options(${TARGET_NAME} INTERFACE + $>) + endforeach() + endif() endif() # add to list of MQT core targets diff --git a/src/qdmi/driver/DeviceRegistry.cpp b/src/qdmi/driver/DeviceRegistry.cpp new file mode 100644 index 0000000000..bc3537a683 --- /dev/null +++ b/src/qdmi/driver/DeviceRegistry.cpp @@ -0,0 +1,523 @@ +/* + * 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 + */ + +#include "DeviceRegistry.hpp" + +#include "qdmi/driver/Driver.hpp" + +#include // NOLINT(misc-include-cleaner) +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +namespace qdmi::detail { +namespace { +using Json = nlohmann::json; // NOLINT(misc-include-cleaner) + +struct SessionPatch { + std::optional baseUrl; + std::optional token; + std::optional authFile; + std::optional authUrl; + std::optional username; + std::optional password; + std::optional custom1; + std::optional custom2; + std::optional custom3; + std::optional custom4; + std::optional custom5; +}; + +struct DefinitionPatch { + std::string id; + std::optional library; + std::optional prefix; + std::optional enabled; + SessionPatch session; + std::filesystem::path source; +}; + +[[nodiscard]] auto sourceLabel(const std::filesystem::path& source, + const std::string_view path) -> std::string { + return source.string() + ":" + std::string(path); +} + +void requireObject(const Json& value, const std::filesystem::path& source, + const std::string_view path) { + if (!value.is_object()) { + throw std::invalid_argument(sourceLabel(source, path) + + " must be an object"); + } +} + +void rejectUnknownKeys(const Json& value, + const std::initializer_list allowed, + const std::filesystem::path& source, + const std::string_view path) { + const std::set known(allowed); + for (const auto& [key, unused] : value.items()) { + static_cast(unused); + if (!known.contains(key)) { + throw std::invalid_argument(sourceLabel(source, path) + + " contains unknown key '" + key + "'"); + } + } +} + +[[nodiscard]] auto optionalString(const Json& value, const std::string& key, + const std::filesystem::path& source, + const std::string& path) + -> std::optional { + const auto it = value.find(key); + if (it == value.end()) { + return std::nullopt; + } + if (!it->is_string()) { + throw std::invalid_argument(sourceLabel(source, path + "." + key) + + " must be a string"); + } + return it->get(); +} + +[[nodiscard]] auto resolvePath(std::filesystem::path path, + const std::filesystem::path& base) + -> std::filesystem::path { + if (path.is_relative()) { + path = base / path; + } + return path.lexically_normal(); +} + +[[nodiscard]] auto absolutePath(const std::filesystem::path& path) + -> std::filesystem::path { + if (path.empty()) { + return {}; + } + return std::filesystem::absolute(path).lexically_normal(); +} + +[[nodiscard]] auto +parseSessionPatch(const Json& value, const std::filesystem::path& source, + const std::string& path, const std::filesystem::path& base) + -> SessionPatch { + requireObject(value, source, path); + rejectUnknownKeys(value, + {"base-url", "token", "auth-file", "auth-url", "username", + "password", "custom1", "custom2", "custom3", "custom4", + "custom5"}, + source, path); + SessionPatch patch; + patch.baseUrl = optionalString(value, "base-url", source, path); + patch.token = optionalString(value, "token", source, path); + patch.authUrl = optionalString(value, "auth-url", source, path); + patch.username = optionalString(value, "username", source, path); + patch.password = optionalString(value, "password", source, path); + patch.custom1 = optionalString(value, "custom1", source, path); + patch.custom2 = optionalString(value, "custom2", source, path); + patch.custom3 = optionalString(value, "custom3", source, path); + patch.custom4 = optionalString(value, "custom4", source, path); + patch.custom5 = optionalString(value, "custom5", source, path); + if (auto authFile = optionalString(value, "auth-file", source, path)) { + patch.authFile = resolvePath(*authFile, base); + } + return patch; +} + +[[nodiscard]] auto +parseDevicePatch(const Json& value, const std::filesystem::path& source, + const std::string& path, const std::filesystem::path& base) + -> DefinitionPatch { + requireObject(value, source, path); + rejectUnknownKeys(value, {"id", "library", "prefix", "enabled", "session"}, + source, path); + const auto id = optionalString(value, "id", source, path); + if (!id || id->empty()) { + throw std::invalid_argument(sourceLabel(source, path + ".id") + + " must be a non-empty string"); + } + DefinitionPatch patch; + patch.id = *id; + patch.source = source; + if (auto library = optionalString(value, "library", source, path)) { + patch.library = resolvePath(*library, base); + } + patch.prefix = optionalString(value, "prefix", source, path); + if (const auto it = value.find("enabled"); it != value.end()) { + if (!it->is_boolean()) { + throw std::invalid_argument(sourceLabel(source, path + ".enabled") + + " must be a boolean"); + } + patch.enabled = it->get(); + } + if (const auto it = value.find("session"); it != value.end()) { + patch.session = parseSessionPatch(*it, source, path + ".session", base); + } + return patch; +} + +[[nodiscard]] auto parseConfiguration(const Json& root, + const std::filesystem::path& source, + const std::filesystem::path& base) + -> std::vector { + requireObject(root, source, "$"); + rejectUnknownKeys(root, {"schema-version", "qdmi"}, source, "$"); + const auto version = root.find("schema-version"); + if (version == root.end() || !version->is_number_integer() || + version->get() != 1) { + throw std::invalid_argument(sourceLabel(source, "$.schema-version") + + " must be the integer 1"); + } + const auto qdmiConfig = root.find("qdmi"); + if (qdmiConfig == root.end()) { + return {}; + } + requireObject(*qdmiConfig, source, "$.qdmi"); + rejectUnknownKeys(*qdmiConfig, {"devices"}, source, "$.qdmi"); + const auto devices = qdmiConfig->find("devices"); + if (devices == qdmiConfig->end()) { + return {}; + } + if (!devices->is_array()) { + throw std::invalid_argument(sourceLabel(source, "$.qdmi.devices") + + " must be an array"); + } + std::set ids; + std::vector patches; + patches.reserve(devices->size()); + for (size_t i = 0; i < devices->size(); ++i) { + auto patch = + parseDevicePatch((*devices)[i], source, + "$.qdmi.devices[" + std::to_string(i) + "]", base); + if (!ids.emplace(patch.id).second) { + throw std::invalid_argument(sourceLabel(source, "$.qdmi.devices") + + " contains duplicate id '" + patch.id + "'"); + } + patches.emplace_back(std::move(patch)); + } + return patches; +} + +[[nodiscard]] auto readJson(const std::filesystem::path& path) -> Json { + std::ifstream stream(path); + if (!stream) { + throw std::runtime_error("Cannot open QDMI configuration file: " + + path.string()); + } + try { + return Json::parse(stream); + } catch (const Json::parse_error& error) { + throw std::invalid_argument(path.string() + + ": invalid JSON: " + error.what()); + } +} + +[[nodiscard]] auto readPyproject(const std::filesystem::path& path) + -> std::optional { + try { + const auto table = toml::parse_file(path.string()); + const auto* qdmiTable = table["tool"]["qdmi"].as_table(); + if (qdmiTable == nullptr) { + return std::nullopt; + } + std::ostringstream formatted; + formatted << toml::json_formatter{*qdmiTable}; + return Json{{"schema-version", 1}, {"qdmi", Json::parse(formatted.str())}}; + } catch (const toml::parse_error& error) { + throw std::invalid_argument( + path.string() + ": invalid TOML: " + std::string(error.description())); + } +} + +template +void mergeOptional(std::optional& target, const std::optional& source) { + if (source) { + target = source; + } +} + +void mergeSession(SessionPatch& target, const SessionPatch& source) { + mergeOptional(target.baseUrl, source.baseUrl); + mergeOptional(target.token, source.token); + mergeOptional(target.authFile, source.authFile); + mergeOptional(target.authUrl, source.authUrl); + mergeOptional(target.username, source.username); + mergeOptional(target.password, source.password); + mergeOptional(target.custom1, source.custom1); + mergeOptional(target.custom2, source.custom2); + mergeOptional(target.custom3, source.custom3); + mergeOptional(target.custom4, source.custom4); + mergeOptional(target.custom5, source.custom5); +} + +void mergePatch(DefinitionPatch& target, const DefinitionPatch& source) { + mergeOptional(target.library, source.library); + mergeOptional(target.prefix, source.prefix); + mergeOptional(target.enabled, source.enabled); + mergeSession(target.session, source.session); + target.source = source.source; +} + +[[nodiscard]] auto moduleDirectory() -> std::filesystem::path { +#ifdef _WIN32 + HMODULE module = nullptr; + if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast(&moduleDirectory), + &module) == 0) { + return {}; + } + std::wstring buffer(MAX_PATH, L'\0'); + while (true) { + const auto size = GetModuleFileNameW(module, buffer.data(), + static_cast(buffer.size())); + if (size == 0) { + return {}; + } + if (size < buffer.size()) { + buffer.resize(size); + return std::filesystem::path(buffer).parent_path(); + } + buffer.resize(buffer.size() * 2); + } +#else + Dl_info info{}; + if (dladdr(reinterpret_cast(&moduleDirectory), &info) == 0 || + info.dli_fname == nullptr) { + return {}; + } + return std::filesystem::path(info.dli_fname).parent_path(); +#endif +} + +[[nodiscard]] auto environment(const char* name) -> std::optional { +#ifdef _WIN32 + char* raw = nullptr; + size_t size = 0; + if (_dupenv_s(&raw, &size, name) != 0 || raw == nullptr) { + return std::nullopt; + } + const std::unique_ptr value(raw, &std::free); + if (*value == '\0') { + return std::nullopt; + } + return std::string(value.get()); +#else + if (const auto* value = std::getenv(name); + value != nullptr && *value != '\0') { + return std::string(value); + } + return std::nullopt; +#endif +} + +void appendIfFile(std::vector& files, + const std::filesystem::path& path) { + const auto absolute = absolutePath(path); + if (absolute.empty()) { + return; + } + std::error_code error; + if (std::filesystem::is_regular_file(absolute, error)) { + files.emplace_back(absolute); + } +} + +void appendFragments(std::vector& files, + const std::filesystem::path& directory) { + const auto absolute = absolutePath(directory); + if (absolute.empty()) { + return; + } + std::error_code error; + if (!std::filesystem::is_directory(absolute, error)) { + return; + } + std::vector found; + for (const auto& entry : std::filesystem::directory_iterator(absolute)) { + if (entry.is_regular_file() && + entry.path().filename().string().ends_with(".qdmi.json")) { + found.emplace_back(entry.path()); + } + } + std::ranges::sort(found); + files.insert(files.end(), found.begin(), found.end()); +} + +[[nodiscard]] auto nearestProjectConfiguration(std::filesystem::path directory) + -> std::optional { + while (!directory.empty()) { + auto dedicated = directory / "qdmi.json"; + if (std::filesystem::is_regular_file(dedicated)) { + return dedicated; + } + auto pyproject = directory / "pyproject.toml"; + if (std::filesystem::is_regular_file(pyproject)) { + if (readPyproject(pyproject)) { + return pyproject; + } + } + const auto parent = directory.parent_path(); + if (parent == directory) { + break; + } + directory = parent; + } + return std::nullopt; +} + +[[nodiscard]] auto discoverFiles() -> std::vector { + std::vector files; + const auto root = moduleDirectory(); + appendFragments(files, root); + appendFragments(files, root / "bin"); + appendFragments(files, root / "lib"); + appendFragments(files, root / "mqt-core" / "qdmi"); + appendFragments(files, root / "qdmi"); + + std::optional explicitFile; + if (auto value = environment("MQT_CORE_QDMI_CONFIG_FILE")) { + explicitFile = *value; + } + if (explicitFile) { + const auto resolved = + resolvePath(*explicitFile, std::filesystem::current_path()); + if (!std::filesystem::is_regular_file(resolved)) { + throw std::runtime_error("Explicit QDMI configuration file does not " + "exist: " + + resolved.string()); + } + files.emplace_back(resolved); + return files; + } + +#ifdef _WIN32 + if (auto programData = environment("PROGRAMDATA")) { + appendIfFile(files, std::filesystem::path(*programData) / "mqt-core" / + "qdmi.json"); + } + if (auto appData = environment("APPDATA")) { + appendIfFile(files, + std::filesystem::path(*appData) / "mqt-core" / "qdmi.json"); + } +#else + appendIfFile(files, "/etc/mqt-core/qdmi.json"); + if (auto xdg = environment("XDG_CONFIG_HOME")) { + appendIfFile(files, std::filesystem::path(*xdg) / "mqt-core" / "qdmi.json"); + } else if (auto home = environment("HOME")) { + appendIfFile(files, std::filesystem::path(*home) / ".config" / "mqt-core" / + "qdmi.json"); + } +#endif + if (auto project = + nearestProjectConfiguration(std::filesystem::current_path())) { + files.emplace_back(std::move(*project)); + } + return files; +} + +[[nodiscard]] auto materialize(const DefinitionPatch& patch) + -> std::optional { + if (!patch.enabled.value_or(true)) { + return std::nullopt; + } + if (!patch.library || patch.library->empty()) { + throw std::invalid_argument(patch.source.string() + ": enabled device '" + + patch.id + "' is missing library"); + } + if (!patch.prefix || patch.prefix->empty()) { + throw std::invalid_argument(patch.source.string() + ": enabled device '" + + patch.id + "' is missing prefix"); + } + qdmi::DeviceDefinition definition; + definition.id = patch.id; + definition.library = *patch.library; + definition.prefix = *patch.prefix; + definition.session.baseUrl = patch.session.baseUrl; + definition.session.token = patch.session.token; + if (patch.session.authFile) { + definition.session.authFile = patch.session.authFile; + } + definition.session.authUrl = patch.session.authUrl; + definition.session.username = patch.session.username; + definition.session.password = patch.session.password; + definition.session.custom1 = patch.session.custom1; + definition.session.custom2 = patch.session.custom2; + definition.session.custom3 = patch.session.custom3; + definition.session.custom4 = patch.session.custom4; + definition.session.custom5 = patch.session.custom5; + return definition; +} + +} // namespace + +DeviceRegistry::DeviceRegistry() { + std::map merged; + const auto mergePatches = [&merged](std::vector patches) { + for (auto& patch : patches) { + if (auto it = merged.find(patch.id); it != merged.end()) { + mergePatch(it->second, patch); + } else { + merged.emplace(patch.id, std::move(patch)); + } + } + }; + + for (const auto& file : discoverFiles()) { + if (file.filename() == "pyproject.toml") { + if (auto config = readPyproject(file)) { + mergePatches(parseConfiguration(*config, file, file.parent_path())); + } + } else { + mergePatches( + parseConfiguration(readJson(file), file, file.parent_path())); + } + } + const auto inlineBase = std::filesystem::current_path(); + if (auto inlineJson = environment("MQT_CORE_QDMI_CONFIG_JSON")) { + try { + mergePatches(parseConfiguration( + Json::parse(*inlineJson), "", inlineBase)); + } catch (const Json::parse_error& error) { + throw std::invalid_argument( + std::string(": invalid JSON: ") + + error.what()); + } + } + for (auto& [unused, patch] : merged) { + static_cast(unused); + if (!patch.enabled.value_or(true)) { + disabledIds_.emplace_back(std::move(patch.id)); + } else if (auto definition = materialize(patch)) { + definitions_.emplace_back(std::move(*definition)); + } + } +} + +} // namespace qdmi::detail diff --git a/src/qdmi/driver/DeviceRegistry.hpp b/src/qdmi/driver/DeviceRegistry.hpp new file mode 100644 index 0000000000..83061f2f62 --- /dev/null +++ b/src/qdmi/driver/DeviceRegistry.hpp @@ -0,0 +1,38 @@ +/* + * 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 + */ + +#pragma once + +#include "qdmi/driver/Driver.hpp" + +#include +#include + +namespace qdmi::detail { + +/// Discovers configured QDMI devices without loading their libraries. +class DeviceRegistry { +public: + DeviceRegistry(); + + [[nodiscard]] const std::vector& definitions() const { + return definitions_; + } + + [[nodiscard]] const std::vector& disabledIds() const { + return disabledIds_; + } + +private: + std::vector definitions_; + std::vector disabledIds_; +}; + +} // namespace qdmi::detail diff --git a/src/qdmi/driver/Driver.cpp b/src/qdmi/driver/Driver.cpp index 2562f338c3..374d5d76b2 100644 --- a/src/qdmi/driver/Driver.cpp +++ b/src/qdmi/driver/Driver.cpp @@ -10,6 +10,7 @@ #include "qdmi/driver/Driver.hpp" +#include "DeviceRegistry.hpp" #include "qdmi/common/Common.hpp" #include @@ -17,24 +18,27 @@ #include #include -#include #include #include #include #include +#include +#include +#include #include +#include #include #include #include #include +#include +#include #include #include #include #ifdef _WIN32 #include - -#include #else #include #endif // _WIN32 @@ -74,15 +78,13 @@ namespace { /// directory if no path is specified. [[nodiscard]] auto loadDeviceLibrary(const std::string& libName) -> HMODULE { const auto requested = std::filesystem::path(libName); - - if (requested.has_parent_path()) { - // A directory component was supplied: Directly load the library. - return LoadLibraryW(requested.wstring().c_str()); - } - - // Bare filename: resolve relative to the driver's own directory so that - // builtin device DLLs installed next to the driver are found reliably. - const auto path = getDriverDirectory() / requested; + // Bare filenames are resolved relative to the Driver. Configured paths are + // already absolute or relative to their declaring file. + const auto path = requested.has_parent_path() + ? requested + : getDriverDirectory() / requested; + // Search beside the device DLL for its dependencies. This is required for + // device implementations such as DDSIM in an installed Python wheel. return LoadLibraryExW(path.wstring().c_str(), nullptr, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); @@ -145,12 +147,14 @@ DynamicDeviceLibrary::DynamicDeviceLibrary(const std::string& libName, LOAD_DYNAMIC_SYMBOL(device_session_query_site_property) LOAD_DYNAMIC_SYMBOL(device_session_query_operation_property) // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) - } catch (const std::exception&) { + // Initialize the device library only after every required symbol is + // available. + throwIfError(device_initialize(), "Failed to initialize device library"); + } catch (...) { DL_CLOSE(libHandle_); + libHandle_ = nullptr; throw; } - // initialize the device - device_initialize(); } DynamicDeviceLibrary::~DynamicDeviceLibrary() { @@ -164,6 +168,66 @@ DynamicDeviceLibrary::~DynamicDeviceLibrary() { } } +namespace { +struct DynamicLibraryCache { + std::mutex mutex; + std::map, + std::weak_ptr> + libraries; +}; + +[[nodiscard]] auto dynamicLibraryCache() -> DynamicLibraryCache& { + static DynamicLibraryCache cache; + return cache; +} + +[[nodiscard]] auto getDynamicDeviceLibrary(const std::string& libName, + const std::string& prefix) + -> std::shared_ptr { + auto& cache = dynamicLibraryCache(); + const std::scoped_lock lock(cache.mutex); + std::error_code error; + auto canonicalPath = std::filesystem::weakly_canonical( + std::filesystem::absolute(std::filesystem::path(libName), error), error); + if (error) { + canonicalPath = std::filesystem::path(libName).lexically_normal(); + } + const auto key = std::pair{canonicalPath.string(), prefix}; + if (const auto library = cache.libraries[key].lock()) { + return library; + } + auto library = std::make_shared(libName, prefix); + cache.libraries[key] = library; + return library; +} + +template +void applyOverride(std::optional& value, + const std::optional& overrideValue) { + if (overrideValue) { + value = overrideValue; + } +} + +[[nodiscard]] auto mergeSessionConfig(const DeviceSessionConfig& defaults, + const DeviceSessionConfig& overrides) + -> DeviceSessionConfig { + auto merged = defaults; + applyOverride(merged.baseUrl, overrides.baseUrl); + applyOverride(merged.token, overrides.token); + applyOverride(merged.authFile, overrides.authFile); + applyOverride(merged.authUrl, overrides.authUrl); + applyOverride(merged.username, overrides.username); + applyOverride(merged.password, overrides.password); + applyOverride(merged.custom1, overrides.custom1); + applyOverride(merged.custom2, overrides.custom2); + applyOverride(merged.custom3, overrides.custom3); + applyOverride(merged.custom4, overrides.custom4); + applyOverride(merged.custom5, overrides.custom5); + return merged; +} +} // namespace + #undef DL_OPEN #undef DL_SYM #undef DL_CLOSE @@ -205,7 +269,10 @@ QDMI_Device_impl_d::QDMI_Device_impl_d( setParameter(config.baseUrl, QDMI_DEVICE_SESSION_PARAMETER_BASEURL); setParameter(config.token, QDMI_DEVICE_SESSION_PARAMETER_TOKEN); - setParameter(config.authFile, QDMI_DEVICE_SESSION_PARAMETER_AUTHFILE); + if (config.authFile) { + const std::optional authFile = config.authFile->string(); + setParameter(authFile, QDMI_DEVICE_SESSION_PARAMETER_AUTHFILE); + } setParameter(config.authUrl, QDMI_DEVICE_SESSION_PARAMETER_AUTHURL); setParameter(config.username, QDMI_DEVICE_SESSION_PARAMETER_USERNAME); setParameter(config.password, QDMI_DEVICE_SESSION_PARAMETER_PASSWORD); @@ -379,6 +446,17 @@ namespace { } } // namespace +QDMI_Session_impl_d::QDMI_Session_impl_d( + const std::vector>& devices) { + devices_.reserve(devices.size()); + std::ranges::transform(devices, std::back_inserter(devices_), + [](const auto& device) { return device.get(); }); +} + +QDMI_Session_impl_d::QDMI_Session_impl_d( + const std::vector& devices) + : devices_(devices) {} + QDMI_Job_impl_d::~QDMI_Job_impl_d() { device_->getLibrary().device_job_free(deviceJob_); } @@ -481,14 +559,14 @@ auto QDMI_Session_impl_d::querySessionProperty(QDMI_Session_Property prop, } if (prop == QDMI_SESSION_PROPERTY_DEVICES) { if (value != nullptr) { - if (size < devices_->size() * sizeof(QDMI_Device)) { + if (size < devices_.size() * sizeof(QDMI_Device)) { return QDMI_ERROR_INVALIDARGUMENT; } - memcpy(value, static_cast(devices_->data()), - devices_->size() * sizeof(QDMI_Device)); + memcpy(value, static_cast(devices_.data()), + devices_.size() * sizeof(QDMI_Device)); } if (sizeRet != nullptr) { - *sizeRet = devices_->size() * sizeof(QDMI_Device); + *sizeRet = devices_.size() * sizeof(QDMI_Device); } return QDMI_SUCCESS; } @@ -496,31 +574,138 @@ auto QDMI_Session_impl_d::querySessionProperty(QDMI_Session_Property prop, } namespace qdmi { +namespace { +void validateDefinition(const DeviceDefinition& definition) { + if (definition.id.empty()) { + throw std::invalid_argument("Device definition ID must not be empty"); + } + if (definition.library.empty()) { + throw std::invalid_argument("Device definition library must not be empty"); + } + if (definition.prefix.empty()) { + throw std::invalid_argument("Device definition prefix must not be empty"); + } +} +} // namespace + +auto Driver::get() -> Driver& { + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + static auto* instance = new Driver(); + return *instance; +} + Driver::Driver() { - for (const auto& [lib, prefix] : std::array{DYN_DEV_LIBS}) { - try { - addDynamicDeviceLibrary(lib, prefix); - } catch (const std::exception& ex) { - SPDLOG_WARN("Skipping builtin QDMI device library '{}': {}", lib, - ex.what()); + const detail::DeviceRegistry registry; + disabledDeviceIds_.insert(registry.disabledIds().begin(), + registry.disabledIds().end()); + for (const auto& definition : registry.definitions()) { + registerDevice(definition); + clientDefinitionIds_.emplace_back(definition.id); + } +} + +void Driver::registerDevice(DeviceDefinition definition, const bool replace) { + validateDefinition(definition); + if (disabledDeviceIds_.contains(definition.id)) { + if (!replace) { + throw std::invalid_argument("QDMI device ID '" + definition.id + + "' is disabled by configuration"); } + disabledDeviceIds_.erase(definition.id); + } + const auto existing = + std::ranges::find(definitions_, definition.id, &DeviceDefinition::id); + if (existing == definitions_.end()) { + definitions_.emplace_back(std::move(definition)); + return; } + if (!replace) { + throw std::invalid_argument("QDMI device ID '" + definition.id + + "' is already registered"); + } + if (openedDevices_.contains(definition.id)) { + throw std::runtime_error("Cannot replace opened QDMI device ID '" + + definition.id + "'"); + } + *existing = std::move(definition); } -auto Driver::addDynamicDeviceLibrary(const std::string& libName, - const std::string& prefix, - const DeviceSessionConfig& config) - -> QDMI_Device { +auto Driver::registerDeviceIfAbsent(DeviceDefinition definition) -> bool { + validateDefinition(definition); + if (disabledDeviceIds_.contains(definition.id) || + std::ranges::find(definitions_, definition.id, &DeviceDefinition::id) != + definitions_.end()) { + return false; + } + definitions_.emplace_back(std::move(definition)); + return true; +} + +auto Driver::open(const std::string_view id) -> QDMI_Device { + if (disabledDeviceIds_.contains(std::string(id))) { + throw std::runtime_error("QDMI device ID '" + std::string(id) + + "' is disabled by configuration"); + } + if (const auto opened = openedDevices_.find(std::string(id)); + opened != openedDevices_.end()) { + return opened->second; + } + const auto definition = + std::ranges::find(definitions_, id, &DeviceDefinition::id); + if (definition == definitions_.end()) { + throw std::out_of_range("Unknown QDMI device ID '" + std::string(id) + "'"); + } devices_.emplace_back(std::make_unique( - std::make_shared(libName, prefix), config)); - return devices_.back().get(); + getDynamicDeviceLibrary(definition->library.string(), definition->prefix), + definition->session)); + auto* const device = devices_.back().get(); + openedDevices_.emplace(definition->id, device); + return device; +} + +auto Driver::openFresh(const std::string_view id, + const DeviceSessionConfig& overrides) + -> std::shared_ptr { + if (disabledDeviceIds_.contains(std::string(id))) { + throw std::runtime_error("QDMI device ID '" + std::string(id) + + "' is disabled by configuration"); + } + const auto definition = + std::ranges::find(definitions_, id, &DeviceDefinition::id); + if (definition == definitions_.end()) { + throw std::out_of_range("Unknown QDMI device ID '" + std::string(id) + "'"); + } + return std::make_shared( + getDynamicDeviceLibrary(definition->library.string(), definition->prefix), + mergeSessionConfig(definition->session, overrides)); +} + +void Driver::materializeClientCatalog() { + if (clientCatalogMaterialized_) { + return; + } + clientCatalogMaterialized_ = true; + for (const auto& id : clientDefinitionIds_) { + try { + clientDevices_.emplace_back(open(id)); + } catch (const std::exception& ex) { + const auto definition = + std::ranges::find(definitions_, id, &DeviceDefinition::id); + const auto library = definition == definitions_.end() + ? std::string("") + : definition->library.string(); + SPDLOG_WARN("Skipping configured QDMI device '{}' from '{}': {}", id, + library, ex.what()); + } + } } auto Driver::sessionAlloc(QDMI_Session* session) -> int { if (session == nullptr) { return QDMI_ERROR_INVALIDARGUMENT; } - auto uniqueSession = std::make_unique(devices_); + materializeClientCatalog(); + auto uniqueSession = std::make_unique(clientDevices_); *session = sessions_.emplace(uniqueSession.get(), std::move(uniqueSession)) .first->first; return QDMI_SUCCESS; diff --git a/src/qir/CMakeLists.txt b/src/qir/CMakeLists.txt index 4a8b8ba413..2c60f4191d 100644 --- a/src/qir/CMakeLists.txt +++ b/src/qir/CMakeLists.txt @@ -6,6 +6,10 @@ # # Licensed under the MIT License +if(BUILD_MQT_CORE_QIR_RUNNER OR BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR) + add_subdirectory(jit) +endif() + add_subdirectory(runtime) if(BUILD_MQT_CORE_QIR_RUNNER) diff --git a/src/qir/jit/CMakeLists.txt b/src/qir/jit/CMakeLists.txt new file mode 100644 index 0000000000..6795fc58c1 --- /dev/null +++ b/src/qir/jit/CMakeLists.txt @@ -0,0 +1,45 @@ +# 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 + +set(TARGET_NAME ${MQT_CORE_TARGET_NAME}-qir-jit) + +if(NOT TARGET ${TARGET_NAME}) + # Add QIRJIT library + add_mqt_core_library(${TARGET_NAME} ALIAS_NAME QIRJIT) + + # Add sources to target + target_sources(${TARGET_NAME} PRIVATE Session.cpp IRRewriter.cpp) + + # Add headers using file sets + target_sources( + ${TARGET_NAME} + PUBLIC FILE_SET + HEADERS + BASE_DIRS + ${MQT_CORE_INCLUDE_BUILD_DIR} + FILES + ${MQT_CORE_INCLUDE_BUILD_DIR}/qir/jit/Session.hpp + ${MQT_CORE_INCLUDE_BUILD_DIR}/qir/jit/IRRewriter.hpp) + + # Get the LLVM native target libraries + llvm_map_components_to_libnames( + llvm_native_libs + codegen + core + executionengine + irreader + native + orcdebugging + orcjit + orctargetprocess + support + targetparser) + + # Add link libraries + target_link_libraries(${TARGET_NAME} PRIVATE MQT::CoreQIRRuntime ${llvm_native_libs}) +endif() diff --git a/src/qir/jit/IRRewriter.cpp b/src/qir/jit/IRRewriter.cpp new file mode 100644 index 0000000000..e7a98cb1e3 --- /dev/null +++ b/src/qir/jit/IRRewriter.cpp @@ -0,0 +1,60 @@ +/* + * 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 + */ + +#include "qir/jit/IRRewriter.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace qir { + +static bool isStripTarget(const llvm::CallInst& call) { + const auto* callee = call.getCalledFunction(); + if (callee == nullptr) { + return false; + } + const auto name = callee->getName(); + return std::ranges::any_of( + STRIP_TARGETS, [&](llvm::StringRef target) { return name == target; }); +} + +std::size_t stripMeasurementRelatedCalls(llvm::Module& m) { + std::size_t erased = 0; + for (auto& fn : m) { + for (auto& bb : fn) { + for (auto& inst : llvm::make_early_inc_range(bb)) { + auto* call = llvm::dyn_cast(&inst); + if (call == nullptr || !isStripTarget(*call)) { + continue; + } + // `m` and `measure` return a `Result*`. + // Replace uses of `m` and `measure` return values with a null before + // erasure so they do not dangle. + if (!call->getType()->isVoidTy() && !call->use_empty()) { + call->replaceAllUsesWith( + llvm::Constant::getNullValue(call->getType())); + } + call->eraseFromParent(); + ++erased; + } + } + } + return erased; +} + +} // namespace qir diff --git a/src/qir/jit/Session.cpp b/src/qir/jit/Session.cpp new file mode 100644 index 0000000000..947f3758ea --- /dev/null +++ b/src/qir/jit/Session.cpp @@ -0,0 +1,430 @@ +/* + * 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 + */ + +#include "qir/jit/Session.hpp" + +#include "qir/jit/IRRewriter.hpp" +#include "qir/runtime/QIR.h" +#include "qir/runtime/Runtime.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEBUG_TYPE "mqt-core-qir-jit" + +namespace qir { + +/// Returns the function marked with the `entry_point` attribute, +/// or @c nullptr if no such function is defined. +static const llvm::Function* getEntryPointFunction(const llvm::Module& m) { + const auto it = std::ranges::find_if(m, [](const llvm::Function& fn) { + return fn.hasFnAttribute("entry_point"); + }); + return it != m.end() ? &*it : nullptr; +} + +/// Read the `output_labeling_schema` attribute from the program's entry point. +/// Returns @c Labeled when the attribute is absent or its value is not +/// exactly `ordered` (spec default). +static Runtime::OutputSchema readOutputSchema(const llvm::Module& m) { + if (const auto* fn = getEntryPointFunction(m); fn != nullptr) { + if (const auto attr = fn->getFnAttribute("output_labeling_schema"); + attr.isValid() && attr.getValueAsString() == "ordered") { + return Runtime::OutputSchema::Ordered; + } + } + return Runtime::OutputSchema::Labeled; +} + +static void exitOnLazyCallThroughFailure() { exit(1); } + +static int mingwNoopMain() { + // Cygwin and MinGW insert calls from the main function to the runtime + // function __main. The __main function is responsible for setting up main's + // environment (e.g. running static constructors), however this is not needed + // when running under lli: the executor process will have run non-JIT ctors, + // and ORC will take care of running JIT'd ctors. To avoid a missing symbol + // error we just implement __main as a no-op. + return 0; +} + +// Try to enable debugger support for the given instance. +// This always returns success, but prints a warning if it's not able to enable +// debugger support. +static llvm::Error tryEnableDebugSupport(llvm::orc::LLJIT& jit) { + if (auto err = enableDebuggerSupport(jit)) { + [[maybe_unused]] const std::string errMsg = toString(std::move(err)); + // NOLINTNEXTLINE(cppcoreguidelines-avoid-do-while) + LLVM_DEBUG(llvm::dbgs() << DEBUG_TYPE ": " << errMsg << "\n"); + } + return llvm::Error::success(); +} + +static llvm::Expected +getThreadSafeModuleOrError(std::unique_ptr llvmModule, + const llvm::SMDiagnostic& err, + llvm::orc::ThreadSafeContext tsCtx) { + if (!llvmModule) { + std::string errMsg; + { + llvm::raw_string_ostream errMsgStream(errMsg); + err.print(DEBUG_TYPE, errMsgStream); + } + return llvm::make_error(std::move(errMsg), + llvm::inconvertibleErrorCode()); + } + return llvm::orc::ThreadSafeModule(std::move(llvmModule), std::move(tsCtx)); +} + +llvm::Expected +JitSession::loadModuleFromFile(const llvm::StringRef irPath) { + llvm::SMDiagnostic err; + auto m = tsCtx_.withContextDo( + [&](llvm::LLVMContext* ctx) { return parseIRFile(irPath, err, *ctx); }); + return getThreadSafeModuleOrError(std::move(m), err, tsCtx_); +} + +llvm::Expected +JitSession::loadModuleFromMemory(const llvm::StringRef irBytes, + const llvm::StringRef bufferName) { + llvm::SMDiagnostic err; + auto buffer = llvm::MemoryBuffer::getMemBuffer( + irBytes, bufferName, + /*RequiresNullTerminator=*/false); // bitcode isn't null-terminated + auto m = tsCtx_.withContextDo([&](llvm::LLVMContext* ctx) { + return parseIR(buffer->getMemBufferRef(), err, *ctx); + }); + return getThreadSafeModuleOrError(std::move(m), err, tsCtx_); +} + +JitSession::JitSession(const llvm::StringRef inputFile, const Execution mode) { + initialize(loadModuleFromFile(inputFile), mode); +} + +JitSession::JitSession(const llvm::StringRef irBytes, + const llvm::StringRef bufferName, const Execution mode) { + initialize(loadModuleFromMemory(irBytes, bufferName), mode); +} + +JitSession::~JitSession() { deinitialize(); } + +int JitSession::run(llvm::ArrayRef args, + llvm::StringRef progName) const { + // Manual in-process execution with RuntimeDyld. + return llvm::orc::runAsMain(mainFn_, args, progName); +} + +namespace { +std::vector> manualSymbols; +} // namespace + +#define REGISTER_SYMBOL(name) \ + llvm::sys::DynamicLibrary::AddSymbol(#name, \ + reinterpret_cast(&(name))); \ + manualSymbols.emplace_back(#name, reinterpret_cast(&(name))); + +void JitSession::registerRuntimeSymbols() { + static std::once_flag flag; + std::call_once(flag, []() { + REGISTER_SYMBOL(__quantum__rt__result_get_zero); + REGISTER_SYMBOL(__quantum__rt__result_get_one); + REGISTER_SYMBOL(__quantum__rt__result_equal); + REGISTER_SYMBOL(__quantum__rt__result_update_reference_count); + REGISTER_SYMBOL(__quantum__rt__array_create_1d); + REGISTER_SYMBOL(__quantum__rt__array_get_size_1d); + REGISTER_SYMBOL(__quantum__rt__array_get_element_ptr_1d); + REGISTER_SYMBOL(__quantum__rt__array_update_reference_count); + REGISTER_SYMBOL(__quantum__rt__qubit_allocate); + REGISTER_SYMBOL(__quantum__rt__qubit_allocate_array); + REGISTER_SYMBOL(__quantum__rt__qubit_release); + REGISTER_SYMBOL(__quantum__rt__qubit_release_array); + REGISTER_SYMBOL(__quantum__qis__x__body); + REGISTER_SYMBOL(__quantum__qis__y__body); + REGISTER_SYMBOL(__quantum__qis__z__body); + REGISTER_SYMBOL(__quantum__qis__h__body); + REGISTER_SYMBOL(__quantum__qis__s__body); + REGISTER_SYMBOL(__quantum__qis__s__adj); + REGISTER_SYMBOL(__quantum__qis__sx__body); + REGISTER_SYMBOL(__quantum__qis__sx__adj); + REGISTER_SYMBOL(__quantum__qis__sqrtx__body); + REGISTER_SYMBOL(__quantum__qis__sqrtx__adj); + REGISTER_SYMBOL(__quantum__qis__t__body); + REGISTER_SYMBOL(__quantum__qis__t__adj); + REGISTER_SYMBOL(__quantum__qis__r__body); + REGISTER_SYMBOL(__quantum__qis__prx__body); + REGISTER_SYMBOL(__quantum__qis__rx__body); + REGISTER_SYMBOL(__quantum__qis__ry__body); + REGISTER_SYMBOL(__quantum__qis__rz__body); + REGISTER_SYMBOL(__quantum__qis__p__body); + REGISTER_SYMBOL(__quantum__qis__rxx__body); + REGISTER_SYMBOL(__quantum__qis__ryy__body); + REGISTER_SYMBOL(__quantum__qis__rzz__body); + REGISTER_SYMBOL(__quantum__qis__rzx__body); + REGISTER_SYMBOL(__quantum__qis__u__body); + REGISTER_SYMBOL(__quantum__qis__u3__body); + REGISTER_SYMBOL(__quantum__qis__u2__body); + REGISTER_SYMBOL(__quantum__qis__u1__body); + REGISTER_SYMBOL(__quantum__qis__cu1__body); + REGISTER_SYMBOL(__quantum__qis__cu3__body); + REGISTER_SYMBOL(__quantum__qis__cnot__body); + REGISTER_SYMBOL(__quantum__qis__cx__body); + REGISTER_SYMBOL(__quantum__qis__cy__body); + REGISTER_SYMBOL(__quantum__qis__cz__body); + REGISTER_SYMBOL(__quantum__qis__ch__body); + REGISTER_SYMBOL(__quantum__qis__swap__body); + REGISTER_SYMBOL(__quantum__qis__cswap__body); + REGISTER_SYMBOL(__quantum__qis__crz__body); + REGISTER_SYMBOL(__quantum__qis__cry__body); + REGISTER_SYMBOL(__quantum__qis__crx__body); + REGISTER_SYMBOL(__quantum__qis__cp__body); + REGISTER_SYMBOL(__quantum__qis__ccx__body); + REGISTER_SYMBOL(__quantum__qis__ccy__body); + REGISTER_SYMBOL(__quantum__qis__ccz__body); + REGISTER_SYMBOL(__quantum__qis__m__body); + REGISTER_SYMBOL(__quantum__qis__measure__body); + REGISTER_SYMBOL(__quantum__qis__mz__body); + REGISTER_SYMBOL(__quantum__qis__reset__body); + REGISTER_SYMBOL(__quantum__rt__initialize); + REGISTER_SYMBOL(__quantum__rt__read_result); + REGISTER_SYMBOL(__quantum__rt__result_record_output); + REGISTER_SYMBOL(__quantum__rt__bool_record_output); + REGISTER_SYMBOL(__quantum__rt__int_record_output); + REGISTER_SYMBOL(__quantum__rt__float_record_output); + REGISTER_SYMBOL(__quantum__rt__tuple_record_output); + REGISTER_SYMBOL(__quantum__rt__array_record_output); + }); +} + +#undef REGISTER_SYMBOL + +void JitSession::initNativeTargets() { + static std::once_flag flag; + std::call_once(flag, []() { + static const llvm::codegen::RegisterCodeGenFlags CGF; + + // If we have a native target, initialize it to ensure it is linked in and + // usable by the JIT. + llvm::InitializeNativeTarget(); + llvm::InitializeNativeTargetAsmPrinter(); + llvm::InitializeNativeTargetAsmParser(); + }); +} + +void JitSession::initialize( + llvm::Expected llvmModule, + const Execution mode) { + if (!llvmModule) { + throw std::runtime_error(llvm::toString(llvmModule.takeError())); + } + module_ = std::move(*llvmModule); + + // Configure the runtime's labeling schema from the program's entry point. + module_.withModuleDo([](const llvm::Module& m) { + Runtime::getInstance().setOutputSchema(readOutputSchema(m)); + }); + + // In StateExtraction mode, strip QIR measurement and result-management calls + // so the runtime's quantum state remains intact after main returns. + if (mode == Execution::StateExtraction) { + module_.withModuleDo( + [](llvm::Module& m) { stripMeasurementRelatedCalls(m); }); + } + + registerRuntimeSymbols(); + initNativeTargets(); + + // Get TargetTriple and DataLayout from the main module if they're explicitly + // set. + std::optional tt; + std::optional dl; + module_.withModuleDo([&](llvm::Module& m) { + if (!m.getTargetTriple().empty()) { + tt = m.getTargetTriple(); + } + if (!m.getDataLayout().isDefault()) { + dl = m.getDataLayout(); + } + }); + + // Configure the lazy JIT builder. + llvm::orc::LLLazyJITBuilder builder; + + // Use the module's target triple if set, otherwise detect the host's. + auto host = llvm::orc::JITTargetMachineBuilder::detectHost(); + if (!host) { + throw std::runtime_error(llvm::toString(host.takeError())); + } + builder.setJITTargetMachineBuilder( + tt ? llvm::orc::JITTargetMachineBuilder(*tt) : *host); + + // Cache the resolved triple; apply the module's explicit data layout if any. + tt = builder.getJITTargetMachineBuilder()->getTargetTriple(); + if (dl) { + builder.setDataLayout(dl); + } + + // Optional architecture override from the -march codegen flag. + if (!llvm::codegen::getMArch().empty()) { + builder.getJITTargetMachineBuilder()->getTargetTriple().setArchName( + llvm::codegen::getMArch()); + } + + // Apply CPU, features, relocation model, and code model from codegen flags. + builder.getJITTargetMachineBuilder() + ->setCPU(llvm::codegen::getCPUStr()) + .addFeatures(llvm::codegen::getFeatureList()) + .setRelocationModel(llvm::codegen::getExplicitRelocModel()) + .setCodeModel(llvm::codegen::getExplicitCodeModel()); + + // Link process symbols. + builder.setLinkProcessSymbolsByDefault(true); + + // Set up the in-process execution session and lazy call-through manager. + auto pc = llvm::orc::SelfExecutorProcessControl::Create(); + if (!pc) { + throw std::runtime_error(llvm::toString(pc.takeError())); + } + auto es = std::make_unique(std::move(*pc)); + builder.setLazyCallthroughManager( + std::make_unique( + *es, llvm::orc::ExecutorAddr(), nullptr)); + builder.setExecutionSession(std::move(es)); + + // Abort on lazy compilation failure. + builder.setLazyCompileFailureAddr( + llvm::orc::ExecutorAddr::fromPtr(exitOnLazyCallThroughFailure)); + + // Enable debugging of JIT'd code (only works on JITLink for ELF and MachO). + builder.setPrePlatformSetup(tryEnableDebugSupport); + + // Build the JIT. + auto expectedJit = builder.create(); + if (!expectedJit) { + throw std::runtime_error(llvm::toString(expectedJit.takeError())); + } + jit_ = std::move(*expectedJit); + + // Register QIR runtime symbols. + auto& jd = jit_->getMainJITDylib(); + llvm::orc::SymbolMap hostSymbols; + for (const auto& [name, ptr] : manualSymbols) { + hostSymbols[jit_->mangleAndIntern(name)] = { + llvm::orc::ExecutorAddr::fromPtr(ptr), llvm::JITSymbolFlags::Exported}; + } + if (auto err = jd.define(llvm::orc::absoluteSymbols(hostSymbols))) { + throw std::runtime_error(llvm::toString(std::move(err))); + } + + // DynamicLibrarySearchGenerator + auto gen = llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess( + jit_->getDataLayout().getGlobalPrefix()); + if (!gen) { + throw std::runtime_error(llvm::toString(gen.takeError())); + } + jit_->getMainJITDylib().addGenerator(std::move(*gen)); + + // GDB listener (no error path) + auto* objLayer = &jit_->getObjLinkingLayer(); + if (auto* rtDyldObjLayer = + dyn_cast(objLayer)) { + rtDyldObjLayer->registerJITEventListener( + *llvm::JITEventListener::createGDBRegistrationListener()); + } + + // If this is a Mingw or Cygwin executor then we need to alias __main to + // orc_rt_int_void_return_0. + if (jit_->getTargetTriple().isOSCygMing()) { + auto& workaroundJD = jit_->getProcessSymbolsJITDylib() + ? *jit_->getProcessSymbolsJITDylib() + : jit_->getMainJITDylib(); + if (auto err = workaroundJD.define(llvm::orc::absoluteSymbols( + {{jit_->mangleAndIntern("__main"), + {llvm::orc::ExecutorAddr::fromPtr(mingwNoopMain), + llvm::JITSymbolFlags::Exported}}}))) { + throw std::runtime_error(llvm::toString(std::move(err))); + } + } + + // Regular modules are greedy: They materialize as a whole and trigger + // materialization for all required symbols recursively. Lazy modules go + // through partitioning, and they replace outgoing calls with reexport stubs + // that resolve on call-through. + auto addModule = [&](llvm::orc::JITDylib& jdlib, + llvm::orc::ThreadSafeModule m) { + return jit_->addIRModule(jdlib, std::move(m)); + }; + + // Add the main module. + if (auto err = addModule(jit_->getMainJITDylib(), std::move(module_))) { + throw std::runtime_error(llvm::toString(std::move(err))); + } + + // Run any static constructors. + if (auto err = jit_->initialize(jit_->getMainJITDylib())) { + throw std::runtime_error(llvm::toString(std::move(err))); + } + + // Resolve the main function. + auto mainAddr = jit_->lookup("main"); + if (!mainAddr) { + throw std::runtime_error(llvm::toString(mainAddr.takeError())); + } + mainFn_ = mainAddr->toPtr(); +} + +void JitSession::deinitialize() const { + if (!jit_) { + return; + } + if (auto err = jit_->deinitialize(jit_->getMainJITDylib())) { + llvm::errs() << "JitSession deinitialize failed: " + << llvm::toString(std::move(err)) << "\n"; + } +} + +} // namespace qir diff --git a/src/qir/runner/CMakeLists.txt b/src/qir/runner/CMakeLists.txt index ea9564b7fa..f52b85bbfb 100644 --- a/src/qir/runner/CMakeLists.txt +++ b/src/qir/runner/CMakeLists.txt @@ -11,12 +11,8 @@ set(TARGET_NAME ${MQT_CORE_TARGET_NAME}-qir-runner) if(NOT TARGET ${TARGET_NAME}) add_llvm_tool(${TARGET_NAME} Runner.cpp DEPENDS intrinsics_gen EXPORT_SYMBOLS) - # Get the native target libraries - llvm_map_components_to_libnames(llvm_native_libs native) - # Add link libraries - target_link_libraries(${TARGET_NAME} PRIVATE MQT::CoreQIRRuntime LLVMOrcDebugging - ${llvm_native_libs}) + target_link_libraries(${TARGET_NAME} PRIVATE MQT::CoreQIRJIT MQT::CoreQIRRuntime) # Set versioning information set_target_properties(${TARGET_NAME} PROPERTIES VERSION ${PROJECT_VERSION} EXPORT_NAME diff --git a/src/qir/runner/Runner.cpp b/src/qir/runner/Runner.cpp index 9335c0f590..b6626cec41 100644 --- a/src/qir/runner/Runner.cpp +++ b/src/qir/runner/Runner.cpp @@ -8,48 +8,17 @@ * Licensed under the MIT License */ -#include "qir/runtime/QIR.h" +#include "qir/jit/Session.hpp" +#include "qir/runtime/Runtime.hpp" -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include #include #include -#include -#include -#include -#include -#include -#include -#include +#include #include #include -#include -#include #define DEBUG_TYPE "mqt-core-qir-runner" @@ -64,255 +33,23 @@ static llvm::cl::list static llvm::ExitOnError ExitOnError; -static void exitOnLazyCallThroughFailure() { exit(1); } - -static llvm::Expected -loadModule(const llvm::StringRef path, llvm::orc::ThreadSafeContext tsCtx) { - llvm::SMDiagnostic err; - auto m = tsCtx.withContextDo( - [&](llvm::LLVMContext* ctx) { return parseIRFile(path, err, *ctx); }); - if (!m) { - std::string errMsg; - { - llvm::raw_string_ostream errMsgStream(errMsg); - err.print(DEBUG_TYPE, errMsgStream); - } - return llvm::make_error(std::move(errMsg), - llvm::inconvertibleErrorCode()); - } - - return llvm::orc::ThreadSafeModule(std::move(m), std::move(tsCtx)); -} - -static int mingwNoopMain() { - // Cygwin and MinGW insert calls from the main function to the runtime - // function __main. The __main function is responsible for setting up main's - // environment (e.g. running static constructors), however this is not needed - // when running under lli: the executor process will have run non-JIT ctors, - // and ORC will take care of running JIT'd ctors. To avoid a missing symbol - // error we just implement __main as a no-op. - return 0; -} - -// Try to enable debugger support for the given instance. -// This always returns success, but prints a warning if it's not able to enable -// debugger support. -static llvm::Error tryEnableDebugSupport(llvm::orc::LLJIT& jit) { - if (auto err = enableDebuggerSupport(jit)) { - [[maybe_unused]] const std::string errMsg = toString(std::move(err)); - // NOLINTNEXTLINE(cppcoreguidelines-avoid-do-while) - LLVM_DEBUG(llvm::dbgs() << DEBUG_TYPE ": " << errMsg << "\n"); - } - return llvm::Error::success(); -} - -static std::vector> manualSymbols; - -static int runOrcJIT() { - // Start setting up the JIT environment. - - // Parse the main module. - const llvm::orc::ThreadSafeContext tsCtx( - std::make_unique()); - auto mainModule = ExitOnError(loadModule(InputFile, tsCtx)); - - // Get TargetTriple and DataLayout from the main module if they're explicitly - // set. - std::optional tt; - std::optional dl; - mainModule.withModuleDo([&](llvm::Module& m) { - if (!m.getTargetTriple().empty()) { - tt = m.getTargetTriple(); - } - if (!m.getDataLayout().isDefault()) { - dl = m.getDataLayout(); - } - }); - - llvm::orc::LLLazyJITBuilder builder; - - builder.setJITTargetMachineBuilder( - tt ? llvm::orc::JITTargetMachineBuilder(*tt) - : ExitOnError(llvm::orc::JITTargetMachineBuilder::detectHost())); - - tt = builder.getJITTargetMachineBuilder()->getTargetTriple(); - if (dl) { - builder.setDataLayout(dl); - } - - if (!llvm::codegen::getMArch().empty()) { - builder.getJITTargetMachineBuilder()->getTargetTriple().setArchName( - llvm::codegen::getMArch()); - } - - builder.getJITTargetMachineBuilder() - ->setCPU(llvm::codegen::getCPUStr()) - .addFeatures(llvm::codegen::getFeatureList()) - .setRelocationModel(llvm::codegen::getExplicitRelocModel()) - .setCodeModel(llvm::codegen::getExplicitCodeModel()); - - // Link process symbols. - builder.setLinkProcessSymbolsByDefault(true); - - auto es = std::make_unique( - ExitOnError(llvm::orc::SelfExecutorProcessControl::Create())); - builder.setLazyCallthroughManager( - std::make_unique( - *es, llvm::orc::ExecutorAddr(), nullptr)); - builder.setExecutionSession(std::move(es)); - - builder.setLazyCompileFailureAddr( - llvm::orc::ExecutorAddr::fromPtr(exitOnLazyCallThroughFailure)); - - // Enable debugging of JIT'd code (only works on JITLink for ELF and MachO). - builder.setPrePlatformSetup(tryEnableDebugSupport); - - const auto jit = ExitOnError(builder.create()); - - auto& jd = jit->getMainJITDylib(); - llvm::orc::SymbolMap hostSymbols; - for (const auto& [name, ptr] : manualSymbols) { - hostSymbols[jit->mangleAndIntern(name)] = { - llvm::orc::ExecutorAddr::fromPtr(ptr), llvm::JITSymbolFlags::Exported}; - } - ExitOnError(jd.define(llvm::orc::absoluteSymbols(hostSymbols))); - - jit->getMainJITDylib().addGenerator(ExitOnError( - llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess( - jit->getDataLayout().getGlobalPrefix()))); - - auto* objLayer = &jit->getObjLinkingLayer(); - if (auto* rtDyldObjLayer = - dyn_cast(objLayer)) { - rtDyldObjLayer->registerJITEventListener( - *llvm::JITEventListener::createGDBRegistrationListener()); - } - - // If this is a Mingw or Cygwin executor then we need to alias __main to - // orc_rt_int_void_return_0. - if (jit->getTargetTriple().isOSCygMing()) { - auto& workaroundJD = jit->getProcessSymbolsJITDylib() - ? *jit->getProcessSymbolsJITDylib() - : jit->getMainJITDylib(); - ExitOnError(workaroundJD.define(llvm::orc::absoluteSymbols( - {{jit->mangleAndIntern("__main"), - {llvm::orc::ExecutorAddr::fromPtr(mingwNoopMain), - llvm::JITSymbolFlags::Exported}}}))); - } - - // Regular modules are greedy: They materialize as a whole and trigger - // materialization for all required symbols recursively. Lazy modules go - // through partitioning and they replace outgoing calls with reexport stubs - // that resolve on call-through. - auto addModule = [&](llvm::orc::JITDylib& jd, llvm::orc::ThreadSafeModule m) { - return jit->addIRModule(jd, std::move(m)); - }; - - // Add the main module. - ExitOnError(addModule(jit->getMainJITDylib(), std::move(mainModule))); - - // Run any static constructors. - ExitOnError(jit->initialize(jit->getMainJITDylib())); - - // Resolve and run the main function. - const auto mainAddr = ExitOnError(jit->lookup("main")); - - // Manual in-process execution with RuntimeDyld. - using mainFnTy = int(int, char**); - auto mainFn = mainAddr.toPtr(); - const int result = - llvm::orc::runAsMain(mainFn, InputArgv, llvm::StringRef(InputFile)); - - // Run destructors. - ExitOnError(jit->deinitialize(jit->getMainJITDylib())); - - return result; -} - -#define REGISTER_SYMBOL(name) \ - llvm::sys::DynamicLibrary::AddSymbol(#name, \ - reinterpret_cast(&(name))); \ - manualSymbols.emplace_back(#name, reinterpret_cast(&(name))); - auto main(int argc, char* argv[]) -> int { const llvm::InitLLVM session(argc, argv); - if (const std::span args(argv, argc); args.size() > 1) { ExitOnError.setBanner(std::string(args[0]) + ": "); } - - // If we have a native target, initialize it to ensure it is linked in and - // usable by the JIT. - llvm::InitializeNativeTarget(); - llvm::InitializeNativeTargetAsmPrinter(); - llvm::InitializeNativeTargetAsmParser(); - llvm::cl::ParseCommandLineOptions(argc, argv, "qir interpreter & dynamic compiler\n"); - REGISTER_SYMBOL(__quantum__rt__result_get_zero); - REGISTER_SYMBOL(__quantum__rt__result_get_one); - REGISTER_SYMBOL(__quantum__rt__result_equal); - REGISTER_SYMBOL(__quantum__rt__result_update_reference_count); - REGISTER_SYMBOL(__quantum__rt__array_create_1d); - REGISTER_SYMBOL(__quantum__rt__array_get_size_1d); - REGISTER_SYMBOL(__quantum__rt__array_get_element_ptr_1d); - REGISTER_SYMBOL(__quantum__rt__array_update_reference_count); - REGISTER_SYMBOL(__quantum__rt__qubit_allocate); - REGISTER_SYMBOL(__quantum__rt__qubit_allocate_array); - REGISTER_SYMBOL(__quantum__rt__qubit_release); - REGISTER_SYMBOL(__quantum__rt__qubit_release_array); - REGISTER_SYMBOL(__quantum__qis__x__body); - REGISTER_SYMBOL(__quantum__qis__y__body); - REGISTER_SYMBOL(__quantum__qis__z__body); - REGISTER_SYMBOL(__quantum__qis__h__body); - REGISTER_SYMBOL(__quantum__qis__s__body); - REGISTER_SYMBOL(__quantum__qis__sdg__body); - REGISTER_SYMBOL(__quantum__qis__sx__body); - REGISTER_SYMBOL(__quantum__qis__sxdg__body); - REGISTER_SYMBOL(__quantum__qis__sqrtx__body); - REGISTER_SYMBOL(__quantum__qis__sqrtxdg__body); - REGISTER_SYMBOL(__quantum__qis__t__body); - REGISTER_SYMBOL(__quantum__qis__tdg__body); - REGISTER_SYMBOL(__quantum__qis__r__body); - REGISTER_SYMBOL(__quantum__qis__prx__body); - REGISTER_SYMBOL(__quantum__qis__rx__body); - REGISTER_SYMBOL(__quantum__qis__ry__body); - REGISTER_SYMBOL(__quantum__qis__rz__body); - REGISTER_SYMBOL(__quantum__qis__p__body); - REGISTER_SYMBOL(__quantum__qis__rxx__body); - REGISTER_SYMBOL(__quantum__qis__ryy__body); - REGISTER_SYMBOL(__quantum__qis__rzz__body); - REGISTER_SYMBOL(__quantum__qis__rzx__body); - REGISTER_SYMBOL(__quantum__qis__u__body); - REGISTER_SYMBOL(__quantum__qis__u3__body); - REGISTER_SYMBOL(__quantum__qis__u2__body); - REGISTER_SYMBOL(__quantum__qis__u1__body); - REGISTER_SYMBOL(__quantum__qis__cu1__body); - REGISTER_SYMBOL(__quantum__qis__cu3__body); - REGISTER_SYMBOL(__quantum__qis__cnot__body); - REGISTER_SYMBOL(__quantum__qis__cx__body); - REGISTER_SYMBOL(__quantum__qis__cy__body); - REGISTER_SYMBOL(__quantum__qis__cz__body); - REGISTER_SYMBOL(__quantum__qis__ch__body); - REGISTER_SYMBOL(__quantum__qis__swap__body); - REGISTER_SYMBOL(__quantum__qis__cswap__body); - REGISTER_SYMBOL(__quantum__qis__crz__body); - REGISTER_SYMBOL(__quantum__qis__cry__body); - REGISTER_SYMBOL(__quantum__qis__crx__body); - REGISTER_SYMBOL(__quantum__qis__cp__body); - REGISTER_SYMBOL(__quantum__qis__ccx__body); - REGISTER_SYMBOL(__quantum__qis__ccy__body); - REGISTER_SYMBOL(__quantum__qis__ccz__body); - REGISTER_SYMBOL(__quantum__qis__m__body); - REGISTER_SYMBOL(__quantum__qis__measure__body); - REGISTER_SYMBOL(__quantum__qis__mz__body); - REGISTER_SYMBOL(__quantum__qis__reset__body); - REGISTER_SYMBOL(__quantum__rt__initialize); - REGISTER_SYMBOL(__quantum__rt__read_result); - REGISTER_SYMBOL(__quantum__rt__result_record_output); - - return runOrcJIT(); + try { + auto jitSession = qir::JitSession(llvm::StringRef(InputFile)); + auto& runtime = qir::Runtime::getInstance(); + runtime.outputProgramHeader(); + runtime.outputShotStart(); + const auto rc = jitSession.run(InputArgv, InputFile); + runtime.outputShotEnd(); + return rc; + } catch (const std::exception& e) { + ExitOnError(llvm::createStringError(e.what())); + } } - -#undef REGISTER_SYMBOL diff --git a/src/qir/runtime/CMakeLists.txt b/src/qir/runtime/CMakeLists.txt index aef86c8b01..84f65ae266 100644 --- a/src/qir/runtime/CMakeLists.txt +++ b/src/qir/runtime/CMakeLists.txt @@ -9,7 +9,7 @@ set(TARGET_NAME ${MQT_CORE_TARGET_NAME}-qir-runtime) if(NOT TARGET ${TARGET_NAME}) - # Add QIRBackend library + # Add QIRRuntime library add_mqt_core_library(${TARGET_NAME} ALIAS_NAME QIRRuntime) # Add sources to target diff --git a/src/qir/runtime/QIR.cpp b/src/qir/runtime/QIR.cpp index dc7eef132e..1d756a063d 100644 --- a/src/qir/runtime/QIR.cpp +++ b/src/qir/runtime/QIR.cpp @@ -15,7 +15,6 @@ #include #include -#include #include extern "C" { @@ -146,7 +145,7 @@ void __quantum__qis__s__body(Qubit* qubit) { runtime.apply(qubit); } -void __quantum__qis__sdg__body(Qubit* qubit) { +void __quantum__qis__s__adj(Qubit* qubit) { auto& runtime = qir::Runtime::getInstance(); runtime.apply(qubit); } @@ -156,7 +155,7 @@ void __quantum__qis__sx__body(Qubit* qubit) { runtime.apply(qubit); } -void __quantum__qis__sxdg__body(Qubit* qubit) { +void __quantum__qis__sx__adj(Qubit* qubit) { auto& runtime = qir::Runtime::getInstance(); runtime.apply(qubit); } @@ -166,7 +165,7 @@ void __quantum__qis__sqrtx__body(Qubit* qubit) { runtime.apply(qubit); } -void __quantum__qis__sqrtxdg__body(Qubit* qubit) { +void __quantum__qis__sqrtx__adj(Qubit* qubit) { auto& runtime = qir::Runtime::getInstance(); runtime.apply(qubit); } @@ -176,7 +175,7 @@ void __quantum__qis__t__body(Qubit* qubit) { runtime.apply(qubit); } -void __quantum__qis__tdg__body(Qubit* qubit) { +void __quantum__qis__t__adj(Qubit* qubit) { auto& runtime = qir::Runtime::getInstance(); runtime.apply(qubit); } @@ -381,8 +380,32 @@ bool __quantum__rt__read_result(Result* result) { } void __quantum__rt__result_record_output(Result* result, const char* label) { - std::cout << label << ": " << (__quantum__rt__read_result(result) ? 1 : 0) - << "\n"; + const bool bit = __quantum__rt__read_result(result); + auto& runtime = qir::Runtime::getInstance(); + runtime.outputResult(bit, label); + // Accumulate new measurement bit. + runtime.appendMeasurementBit(bit); +} + +void __quantum__rt__bool_record_output(bool value, const char* label) { + qir::Runtime::getInstance().outputBool(value, label); +} + +void __quantum__rt__int_record_output(int64_t value, const char* label) { + qir::Runtime::getInstance().outputInt(value, label); +} + +void __quantum__rt__float_record_output(double value, const char* label) { + qir::Runtime::getInstance().outputFloat(value, label); +} + +void __quantum__rt__tuple_record_output(int64_t elementCount, + const char* label) { + qir::Runtime::getInstance().outputTuple(elementCount, label); +} + +void __quantum__rt__array_record_output(int64_t size, const char* label) { + qir::Runtime::getInstance().outputArray(size, label); } } // extern "C" diff --git a/src/qir/runtime/Runtime.cpp b/src/qir/runtime/Runtime.cpp index 0ca6a70c6b..8b2555b2a9 100644 --- a/src/qir/runtime/Runtime.cpp +++ b/src/qir/runtime/Runtime.cpp @@ -21,11 +21,15 @@ #include #include #include +#include #include #include +#include #include #include #include +#include +#include #include #include #include @@ -47,14 +51,6 @@ Runtime& Runtime::getInstance() { } auto Runtime::reset() -> void { addressMode = AddressMode::UNKNOWN; - currentMaxQubitAddress = MIN_DYN_QUBIT_ADDRESS; - currentMaxQubitId = 0; - currentMaxResultAddress = MIN_DYN_RESULT_ADDRESS; - numQubitsInQState = 0; - dd->decRef(qState); - dd->garbageCollect(); - qState = dd::vEdge::one(); - mt.seed(generateRandomSeed()); qRegister.clear(); rRegister.clear(); // NOLINTBEGIN(performance-no-int-to-ptr) @@ -63,6 +59,12 @@ auto Runtime::reset() -> void { rRegister.emplace(reinterpret_cast(RESULT_ONE_ADDRESS), ResultStruct{.refcount = 0, .r = true}); // NOLINTEND(performance-no-int-to-ptr) + measurements.clear(); + currentMaxQubitAddress = MIN_DYN_QUBIT_ADDRESS; + currentMaxQubitId = 0; + currentMaxResultAddress = MIN_DYN_RESULT_ADDRESS; + qState.reset(); + mt.seed(generateRandomSeed()); } Runtime::Runtime() : Runtime(generateRandomSeed()) {} @@ -70,9 +72,7 @@ Runtime::Runtime() : Runtime(generateRandomSeed()) {} Runtime::Runtime(const uint64_t randomSeed) : addressMode(AddressMode::UNKNOWN), currentMaxQubitAddress(MIN_DYN_QUBIT_ADDRESS), currentMaxQubitId(0), - currentMaxResultAddress(MIN_DYN_RESULT_ADDRESS), numQubitsInQState(0), - dd(std::make_unique()), qState(dd::vEdge::one()), - mt(randomSeed) { + currentMaxResultAddress(MIN_DYN_RESULT_ADDRESS), mt(randomSeed) { qRegister = std::unordered_map(); rRegister = std::unordered_map(); // NOLINTBEGIN(performance-no-int-to-ptr) @@ -84,32 +84,33 @@ Runtime::Runtime(const uint64_t randomSeed) } auto Runtime::enlargeState(const std::uint64_t maxQubit) -> void { - if (maxQubit >= numQubitsInQState) { - const auto d = maxQubit - numQubitsInQState + 1; - qubitPermutation.resize(numQubitsInQState + d); - std::iota(qubitPermutation.begin() + - static_cast::difference_type>( - numQubitsInQState), - qubitPermutation.end(), numQubitsInQState); - numQubitsInQState += static_cast(d); - - // resize the DD package only if necessary - if (dd->qubits() < numQubitsInQState) { - dd->resize(numQubitsInQState); + if (maxQubit >= qState.numQubits) { + const auto d = maxQubit - qState.numQubits + 1; + qubitPermutation.resize(qState.numQubits + d); + std::iota(qubitPermutation.begin() + qState.numQubits, + qubitPermutation.end(), qState.numQubits); + qState.numQubits += static_cast(d); + + // Resize the DD package only if necessary. + if (qState.dd->qubits() < qState.numQubits) { + qState.dd->resize(qState.numQubits); } - // if the state is terminal, we need to create a new node - if (qState.isTerminal()) { - qState = makeZeroState(d, *dd); + // If the state is terminal, we need to create a new node. + if (qState.edge.isTerminal()) { + qState.edge = makeZeroState(d, *qState.dd); return; } - // enlarge state - for (auto q = qState.p->v; q < numQubitsInQState; ++q) { - auto old = qState; - qState = dd->makeDDNode(q + 1U, std::array{qState, dd::vEdge::zero()}); - dd->incRef(qState); - dd->decRef(old); + // Enlarge state. + // Each iteration adds one level above the current root, raising root.v by + // one. After the loop, root.v == numQubits - 1. + for (auto q = qState.edge.p->v; q + 1 < qState.numQubits; ++q) { + auto old = qState.edge; + qState.edge = qState.dd->makeDDNode( + q + 1U, std::array{qState.edge, dd::vEdge::zero()}); + qState.dd->incRef(qState.edge); + qState.dd->decRef(old); } } } @@ -164,4 +165,90 @@ auto Runtime::equal(Result* result1, Result* result2) -> bool { return deref(result1).r == deref(result2).r; } +auto Runtime::appendMeasurementBit(bool result) -> void { + measurements.push_back(result ? '1' : '0'); +} + +auto Runtime::getMeasurements() const -> const std::string& { + return measurements; +} + +auto Runtime::takeState() -> QState { + QState ret = std::move(qState); + reset(); + return ret; +} + +auto Runtime::getOstream() const -> std::ostream& { return *os; } + +auto Runtime::setOstream(std::ostream& other) -> void { os = &other; } + +auto Runtime::resetOstream() -> void { os = &std::cout; } + +void Runtime::outputType(const char* type, std::string_view value, + const char* label) const { + *os << "OUTPUT\t" << type << "\t" << value; + if (label != nullptr && outputSchema == OutputSchema::Labeled) { + *os << "\t" << label; + } + *os << "\n"; +} + +auto Runtime::outputResult(bool value, const char* label) const -> void { + outputType("RESULT", value ? "1" : "0", label); +} + +auto Runtime::outputBool(bool value, const char* label) const -> void { + outputType("BOOL", value ? "true" : "false", label); +} + +auto Runtime::outputInt(int64_t value, const char* label) const -> void { + outputType("INT", std::to_string(value), label); +} + +auto Runtime::outputFloat(double value, const char* label) const -> void { + // Use std::ostringstream rather than std::to_string. + // std::to_string formats with six digits after the decimal point and + // can print 0.000000 for very small numbers. + // std::ostringstream uses six significant digits by default and + // outputs very small numbers with scientific notation. + std::ostringstream oss; + oss << value; + outputType("DOUBLE", oss.str(), label); +} + +auto Runtime::outputTuple(int64_t elementCount, const char* label) const + -> void { + outputType("TUPLE", std::to_string(elementCount), label); +} + +auto Runtime::outputArray(int64_t elementCount, const char* label) const + -> void { + outputType("ARRAY", std::to_string(elementCount), label); +} + +auto Runtime::outputProgramHeader() const -> void { + *os << "HEADER\tschema_id\t" << outputSchema << "\n"; + *os << "HEADER\tschema_version\t2.1\n"; +} + +auto Runtime::outputShotStart() const -> void { + *os << "START\n"; + *os << "METADATA\toutput_labeling_schema\t" << outputSchema << "\n"; +} + +auto Runtime::outputShotEnd() const -> void { *os << "END\t0\n"; } + +auto Runtime::getOutputSchema() const -> OutputSchema { return outputSchema; } + +auto Runtime::setOutputSchema(OutputSchema schema) -> void { + outputSchema = schema; +} + +auto operator<<(std::ostream& os, const Runtime::OutputSchema schema) + -> std::ostream& { + return os << (schema == Runtime::OutputSchema::Labeled ? "labeled" + : "ordered"); +} + } // namespace qir diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index 519fde6fd8..ff09231a35 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -257,6 +257,74 @@ void FunctionalityConstruction::addDcx(ZXDiagram& diag, const Qubit qubit1, addCnot(diag, qubit2, qubit1, qubits); } +void FunctionalityConstruction::addRccx(ZXDiagram& diag, const Qubit qubit0, + const Qubit qubit1, const Qubit qubit2, + std::vector& qubits) { + addZSpider(diag, qubit2, qubits, PiExpression(), EdgeType::Hadamard); + addZSpider(diag, qubit2, qubits, PiExpression(PiRational(1, 4))); + addCnot(diag, qubit1, qubit2, qubits); + addZSpider(diag, qubit2, qubits, PiExpression(PiRational(-1, 4))); + addCnot(diag, qubit0, qubit2, qubits); + addZSpider(diag, qubit2, qubits, PiExpression(PiRational(1, 4))); + addCnot(diag, qubit1, qubit2, qubits); + addZSpider(diag, qubit2, qubits, PiExpression(PiRational(-1, 4))); + addZSpider(diag, qubit2, qubits, PiExpression(), EdgeType::Hadamard); +} + +void FunctionalityConstruction::addCrccx(ZXDiagram& diag, const Qubit control, + const Qubit qubit0, const Qubit qubit1, + const Qubit qubit2, + std::vector& qubits) { + const std::vector controls{control}; + addRz(diag, PiExpression(PiRational(1, 2)), qubit2, qubits); + addRx(diag, PiExpression(PiRational(1, 2)), qubit2, qubits); + addCphase(diag, PiExpression(PiRational(1, 1)), control, qubit2, qubits); + addRx(diag, PiExpression(-PiRational(1, 2)), qubit2, qubits); + addRz(diag, PiExpression(-PiRational(1, 2)), qubit2, qubits); + addMcphase(diag, PiExpression(PiRational(1, 4)), controls, qubit2, qubits); + addMcx(diag, {control, qubit1}, qubit2, qubits); + addMcphase(diag, PiExpression(PiRational(-1, 4)), controls, qubit2, qubits); + addMcx(diag, {control, qubit0}, qubit2, qubits); + addMcphase(diag, PiExpression(PiRational(1, 4)), controls, qubit2, qubits); + addMcx(diag, {control, qubit1}, qubit2, qubits); + addMcphase(diag, PiExpression(PiRational(-1, 4)), controls, qubit2, qubits); + addRz(diag, PiExpression(PiRational(1, 2)), qubit2, qubits); + addRx(diag, PiExpression(PiRational(1, 2)), qubit2, qubits); + addCphase(diag, PiExpression(PiRational(1, 1)), control, qubit2, qubits); + addRx(diag, PiExpression(-PiRational(1, 2)), qubit2, qubits); + addRz(diag, PiExpression(-PiRational(1, 2)), qubit2, qubits); +} + +void FunctionalityConstruction::addMcrccx( + ZXDiagram& diag, const std::vector& controls, const Qubit qubit0, + const Qubit qubit1, const Qubit qubit2, std::vector& qubits) { + if (controls.size() == 1) { + addCrccx(diag, controls.front(), qubit0, qubit1, qubit2, qubits); + return; + } + addMcrz(diag, PiExpression(PiRational(1, 2)), controls, qubit2, qubits); + addMcrx(diag, PiExpression(PiRational(1, 2)), controls, qubit2, qubits); + addMcz(diag, controls, qubit2, qubits); + addMcrx(diag, PiExpression(-PiRational(1, 2)), controls, qubit2, qubits); + addMcrz(diag, PiExpression(-PiRational(1, 2)), controls, qubit2, qubits); + addMcphase(diag, PiExpression(PiRational(1, 4)), controls, qubit2, qubits); + auto mergedControls = controls; + mergedControls.emplace_back(qubit1); + addMcx(diag, mergedControls, qubit2, qubits); + addMcphase(diag, PiExpression(PiRational(-1, 4)), controls, qubit2, qubits); + mergedControls.back() = qubit0; + addMcx(diag, mergedControls, qubit2, qubits); + addMcphase(diag, PiExpression(PiRational(1, 4)), controls, qubit2, qubits); + mergedControls.back() = qubit1; + addMcx(diag, mergedControls, qubit2, qubits); + addMcphase(diag, PiExpression(PiRational(-1, 4)), controls, qubit2, qubits); + addMcrz(diag, PiExpression(PiRational(1, 2)), controls, qubit2, qubits); + addMcrx(diag, PiExpression(PiRational(1, 2)), controls, qubit2, qubits); + addMcz(diag, controls, qubit2, qubits); + addMcrx(diag, PiExpression(-PiRational(1, 2)), controls, qubit2, qubits); + addMcrz(diag, PiExpression(-PiRational(1, 2)), controls, qubit2, qubits); +} + void FunctionalityConstruction::addXXplusYY( ZXDiagram& diag, const PiExpression& theta, const PiExpression& beta, const Qubit qubit0, const Qubit qubit1, std::vector& qubits, @@ -486,6 +554,27 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, return it + 1; } + if (op->getType() == qc::OpType::RCCX) { + const auto qubit0 = static_cast(p.at(op->getTargets()[0])); + const auto qubit1 = static_cast(p.at(op->getTargets()[1])); + const auto qubit2 = static_cast(p.at(op->getTargets()[2])); + if (!op->isControlled()) { + addRccx(diag, qubit0, qubit1, qubit2, qubits); + } else if (op->getNcontrols() == 1) { + const auto control = + static_cast(p.at(op->getControls().begin()->qubit)); + addCrccx(diag, control, qubit0, qubit1, qubit2, qubits); + } else { + std::vector controls; + controls.reserve(op->getNcontrols()); + for (const auto& ctrl : op->getControls()) { + controls.emplace_back(static_cast(p.at(ctrl.qubit))); + } + addMcrccx(diag, controls, qubit0, qubit1, qubit2, qubits); + } + return it + 1; + } + if (!op->isControlled()) { // single qubit gates const auto target = static_cast(p.at(op->getTargets().front())); @@ -982,10 +1071,13 @@ bool FunctionalityConstruction::transformableToZX(const qc::Operation* op) { case qc::OpType::ECR: case qc::OpType::XXplusYY: case qc::OpType::XXminusYY: + case qc::OpType::RCCX: return true; default: return false; } + } else if (op->getType() == qc::OpType::RCCX) { + return true; } else if (op->getNcontrols() == 1 && op->getNtargets() == 1) { switch (op->getType()) { // TODO: any gate can be controlled case qc::OpType::X: diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2e3ab6a543..62b858f277 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -14,9 +14,11 @@ add_subdirectory(dd) add_subdirectory(ir) add_subdirectory(na) add_subdirectory(zx) -add_subdirectory(qdmi) -add_subdirectory(fomac) add_subdirectory(qir) +add_subdirectory(qdmi) +if(BUILD_MQT_CORE_QDMI_NA_DEVICE) + add_subdirectory(fomac) +endif() # copy test circuits to build directory file(COPY ${PROJECT_SOURCE_DIR}/test/circuits DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/test/circuits/AdaptiveRecordOutputs.ll b/test/circuits/AdaptiveRecordOutputs.ll new file mode 100644 index 0000000000..f7df3ed0bf --- /dev/null +++ b/test/circuits/AdaptiveRecordOutputs.ll @@ -0,0 +1,102 @@ +; ModuleID = 'Adaptive module implementing a 3-qubit Hamming weight' +source_filename = "AdaptiveRecordOutputs.ll" + +%Qubit = type opaque +%Result = type opaque + +@r0_lbl = internal constant [3 x i8] c"r0\00" +@r1_lbl = internal constant [3 x i8] c"r1\00" +@r2_lbl = internal constant [3 x i8] c"r2\00" +@outputs_lbl = internal constant [8 x i8] c"outputs\00" +@measurements_lbl = internal constant [15 x i8] c" measurements\00" +@m0_lbl = internal constant [7 x i8] c" m0\00" +@m1_lbl = internal constant [7 x i8] c" m1\00" +@m2_lbl = internal constant [7 x i8] c" m2\00" +@weight_lbl = internal constant [17 x i8] c" hamming_weight\00" +@mean_lbl = internal constant [7 x i8] c" mean\00" + +define i32 @main() #0 { +entry: + call void @__quantum__rt__initialize(i8* null) + %q0 = call %Qubit* @__quantum__rt__qubit_allocate() + %q1 = call %Qubit* @__quantum__rt__qubit_allocate() + %q2 = call %Qubit* @__quantum__rt__qubit_allocate() + call void @__quantum__qis__h__body(%Qubit* %q0) + call void @__quantum__qis__h__body(%Qubit* %q1) + call void @__quantum__qis__h__body(%Qubit* %q2) + %r0 = call %Result* @__quantum__qis__m__body(%Qubit* %q0) + %r1 = call %Result* @__quantum__qis__m__body(%Qubit* %q1) + %r2 = call %Result* @__quantum__qis__m__body(%Qubit* %q2) + %b0 = call i1 @__quantum__rt__read_result(%Result* %r0) + %b1 = call i1 @__quantum__rt__read_result(%Result* %r1) + %b2 = call i1 @__quantum__rt__read_result(%Result* %r2) + + ; Classical compute: Hamming weight and its mean. + %c0 = zext i1 %b0 to i64 + %c1 = zext i1 %b1 to i64 + %c2 = zext i1 %b2 to i64 + %sum01 = add i64 %c0, %c1 + %weight = add i64 %sum01, %c2 + %weight_f = sitofp i64 %weight to double + %num_qubits_f = uitofp i64 3 to double + %mean_f = fdiv double %weight_f, %num_qubits_f + + call void @__quantum__rt__qubit_release(%Qubit* %q0) + call void @__quantum__rt__qubit_release(%Qubit* %q1) + call void @__quantum__rt__qubit_release(%Qubit* %q2) + + ; Record the raw measurement bits (these feed the histogram bucketing key). + call void @__quantum__rt__result_record_output(%Result* %r0, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @r0_lbl, i32 0, i32 0)) + call void @__quantum__rt__result_record_output(%Result* %r1, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @r1_lbl, i32 0, i32 0)) + call void @__quantum__rt__result_record_output(%Result* %r2, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @r2_lbl, i32 0, i32 0)) + + ; Output: tuple of 3 elements (array of 3 bools, int count, float mean). + call void @__quantum__rt__tuple_record_output(i64 3, i8* getelementptr inbounds ([8 x i8], [8 x i8]* @outputs_lbl, i32 0, i32 0)) + call void @__quantum__rt__array_record_output(i64 3, i8* getelementptr inbounds ([13 x i8], [13 x i8]* @measurements_lbl, i32 0, i32 0)) + call void @__quantum__rt__bool_record_output(i1 %b0, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @m0_lbl, i32 0, i32 0)) + call void @__quantum__rt__bool_record_output(i1 %b1, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @m1_lbl, i32 0, i32 0)) + call void @__quantum__rt__bool_record_output(i1 %b2, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @m2_lbl, i32 0, i32 0)) + call void @__quantum__rt__int_record_output(i64 %weight, i8* getelementptr inbounds ([15 x i8], [15 x i8]* @weight_lbl, i32 0, i32 0)) + call void @__quantum__rt__float_record_output(double %mean_f, i8* getelementptr inbounds ([5 x i8], [5 x i8]* @mean_lbl, i32 0, i32 0)) + + call void @__quantum__rt__result_update_reference_count(%Result* %r0, i32 -1) + call void @__quantum__rt__result_update_reference_count(%Result* %r1, i32 -1) + call void @__quantum__rt__result_update_reference_count(%Result* %r2, i32 -1) + ret i32 0 +} + +declare void @__quantum__qis__h__body(%Qubit*) + +declare %Result* @__quantum__qis__m__body(%Qubit*) #1 + +declare i1 @__quantum__rt__read_result(%Result*) + +declare void @__quantum__rt__initialize(i8*) + +declare %Qubit* @__quantum__rt__qubit_allocate() + +declare void @__quantum__rt__qubit_release(%Qubit*) + +declare void @__quantum__rt__result_record_output(%Result*, i8*) + +declare void @__quantum__rt__tuple_record_output(i64, i8*) + +declare void @__quantum__rt__array_record_output(i64, i8*) + +declare void @__quantum__rt__bool_record_output(i1, i8*) + +declare void @__quantum__rt__int_record_output(i64, i8*) + +declare void @__quantum__rt__float_record_output(double, i8*) + +declare void @__quantum__rt__result_update_reference_count(%Result*, i32) + +attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="3" "required_num_results"="3" } +attributes #1 = { "irreversible" } + +!llvm.module.flags = !{!0, !1, !2, !3} + +!0 = !{i32 1, !"qir_major_version", i32 2} +!1 = !{i32 7, !"qir_minor_version", i32 1} +!2 = !{i32 1, !"dynamic_qubit_management", i1 true} +!3 = !{i32 1, !"dynamic_result_management", i1 true} diff --git a/test/circuits/BellPairAdaptive.ll b/test/circuits/BellPairAdaptive.ll new file mode 100644 index 0000000000..5bf8ce3b77 --- /dev/null +++ b/test/circuits/BellPairAdaptive.ll @@ -0,0 +1,61 @@ +; ModuleID = 'Adaptive module implementing Bell-pair correlation via classical correction' +source_filename = "BellPairAdaptive.ll" + +%Qubit = type opaque +%Result = type opaque + +@0 = internal constant [3 x i8] c"r0\00" +@1 = internal constant [3 x i8] c"r1\00" + +define i32 @main() #0 { +entry: + call void @__quantum__rt__initialize(i8* null) + %q0 = call %Qubit* @__quantum__rt__qubit_allocate() + %q1 = call %Qubit* @__quantum__rt__qubit_allocate() + call void @__quantum__qis__h__body(%Qubit* %q0) + %r0 = call %Result* @__quantum__qis__m__body(%Qubit* %q0) + %b = call i1 @__quantum__rt__read_result(%Result* %r0) + br i1 %b, label %correct, label %record + +correct: + call void @__quantum__qis__x__body(%Qubit* %q1) + br label %record + +record: + %r1 = call %Result* @__quantum__qis__m__body(%Qubit* %q1) + call void @__quantum__rt__qubit_release(%Qubit* %q0) + call void @__quantum__rt__qubit_release(%Qubit* %q1) + call void @__quantum__rt__result_record_output(%Result* %r0, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @0, i32 0, i32 0)) + call void @__quantum__rt__result_record_output(%Result* %r1, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @1, i32 0, i32 0)) + call void @__quantum__rt__result_update_reference_count(%Result* %r0, i32 -1) + call void @__quantum__rt__result_update_reference_count(%Result* %r1, i32 -1) + ret i32 0 +} + +declare void @__quantum__qis__h__body(%Qubit*) + +declare void @__quantum__qis__x__body(%Qubit*) + +declare %Result* @__quantum__qis__m__body(%Qubit*) #1 + +declare i1 @__quantum__rt__read_result(%Result*) + +declare void @__quantum__rt__initialize(i8*) + +declare %Qubit* @__quantum__rt__qubit_allocate() + +declare void @__quantum__rt__qubit_release(%Qubit*) + +declare void @__quantum__rt__result_record_output(%Result*, i8*) + +declare void @__quantum__rt__result_update_reference_count(%Result*, i32) + +attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="2" "required_num_results"="2" } +attributes #1 = { "irreversible" } + +!llvm.module.flags = !{!0, !1, !2, !3} + +!0 = !{i32 1, !"qir_major_version", i32 2} +!1 = !{i32 7, !"qir_minor_version", i32 1} +!2 = !{i32 1, !"dynamic_qubit_management", i1 true} +!3 = !{i32 1, !"dynamic_result_management", i1 true} diff --git a/test/qir/BellPairDynamic.ll b/test/circuits/BellPairDynamic.ll similarity index 80% rename from test/qir/BellPairDynamic.ll rename to test/circuits/BellPairDynamic.ll index 15696c4816..0b07f89b7d 100644 --- a/test/qir/BellPairDynamic.ll +++ b/test/circuits/BellPairDynamic.ll @@ -1,5 +1,5 @@ -; ModuleID = 'bell' -source_filename = "bell" +; ModuleID = 'Dynamic module implementing Bell pair' +source_filename = "BellPairDynamic.ll" %Qubit = type opaque %Result = type opaque @@ -10,8 +10,8 @@ source_filename = "bell" define i32 @main() #0 { entry: call void @__quantum__rt__initialize(i8* null) - %q0 = call %Qubit* @__quantum__rt__qubit_allocate(); - %q1 = call %Qubit* @__quantum__rt__qubit_allocate(); + %q0 = call %Qubit* @__quantum__rt__qubit_allocate() + %q1 = call %Qubit* @__quantum__rt__qubit_allocate() call void @__quantum__qis__h__body(%Qubit* %q0) call void @__quantum__qis__cnot__body(%Qubit* %q0, %Qubit* %q1) %r0 = call %Result* @__quantum__qis__m__body(%Qubit* %q0) @@ -46,7 +46,7 @@ attributes #1 = { "irreversible" } !llvm.module.flags = !{!0, !1, !2, !3} -!0 = !{i32 1, !"qir_major_version", i32 1} -!1 = !{i32 7, !"qir_minor_version", i32 0} -!2 = !{i32 1, !"dynamic_qubit_management", i1 false} -!3 = !{i32 1, !"dynamic_result_management", i1 false} +!0 = !{i32 1, !"qir_major_version", i32 2} +!1 = !{i32 7, !"qir_minor_version", i32 1} +!2 = !{i32 1, !"dynamic_qubit_management", i1 true} +!3 = !{i32 1, !"dynamic_result_management", i1 true} diff --git a/test/qir/BellPairStatic.ll b/test/circuits/BellPairStatic.ll similarity index 89% rename from test/qir/BellPairStatic.ll rename to test/circuits/BellPairStatic.ll index eb98b571ce..910eb1397e 100644 --- a/test/qir/BellPairStatic.ll +++ b/test/circuits/BellPairStatic.ll @@ -1,5 +1,5 @@ -; ModuleID = 'bell' -source_filename = "bell" +; ModuleID = 'Static module implementing Bell pair' +source_filename = "BellPairStatic.ll" %Qubit = type opaque %Result = type opaque @@ -34,7 +34,7 @@ attributes #1 = { "irreversible" } !llvm.module.flags = !{!0, !1, !2, !3} -!0 = !{i32 1, !"qir_major_version", i32 1} -!1 = !{i32 7, !"qir_minor_version", i32 0} +!0 = !{i32 1, !"qir_major_version", i32 2} +!1 = !{i32 7, !"qir_minor_version", i32 1} !2 = !{i32 1, !"dynamic_qubit_management", i1 false} !3 = !{i32 1, !"dynamic_result_management", i1 false} diff --git a/test/qir/GHZ4Dynamic.ll b/test/circuits/GHZ4Dynamic.ll similarity index 97% rename from test/qir/GHZ4Dynamic.ll rename to test/circuits/GHZ4Dynamic.ll index 6586c03588..f61c0041b5 100644 --- a/test/qir/GHZ4Dynamic.ll +++ b/test/circuits/GHZ4Dynamic.ll @@ -84,7 +84,7 @@ attributes #1 = { "irreversible" } !llvm.module.flags = !{!0, !1, !2, !3} -!0 = !{i32 1, !"qir_major_version", i32 1} -!1 = !{i32 7, !"qir_minor_version", i32 0} +!0 = !{i32 1, !"qir_major_version", i32 2} +!1 = !{i32 7, !"qir_minor_version", i32 1} !2 = !{i32 1, !"dynamic_qubit_management", i1 false} !3 = !{i32 1, !"dynamic_result_management", i1 false} diff --git a/test/dd/test_dd_functionality.cpp b/test/dd/test_dd_functionality.cpp index ae3d8a0967..0ed4c13b5e 100644 --- a/test/dd/test_dd_functionality.cpp +++ b/test/dd/test_dd_functionality.cpp @@ -64,7 +64,7 @@ INSTANTIATE_TEST_SUITE_P( Parameters, DDFunctionality, testing::Values(GPhase, I, H, X, Y, Z, S, Sdg, T, Tdg, SX, SXdg, V, Vdg, U, U2, P, R, RX, RY, RZ, Peres, Peresdg, SWAP, iSWAP, iSWAPdg, - DCX, ECR, RXX, RYY, RZZ, RZX, XXminusYY, XXplusYY), + DCX, ECR, RXX, RYY, RZZ, RZX, RCCX, XXminusYY, XXplusYY), [](const testing::TestParamInfo& inf) { const auto gate = inf.param; return toString(gate); @@ -116,6 +116,9 @@ TEST_P(DDFunctionality, StandardOpBuildInverseBuild) { op = StandardOperation(Controls{}, 0, 1, gate, std::vector{dist(mt), dist(mt)}); break; + case RCCX: + op = StandardOperation(Targets{0, 1, 2}, gate); + break; default: op = StandardOperation(0, gate); } @@ -173,6 +176,9 @@ TEST_P(DDFunctionality, ControlledStandardOpBuildInverseBuild) { op = StandardOperation(Controls{0}, 1, 2, gate, std::vector{dist(mt), dist(mt)}); break; + case RCCX: + op = StandardOperation(Controls{0}, Targets{1, 2, 3}, gate); + break; default: op = StandardOperation(0, 1, gate); } @@ -232,6 +238,9 @@ TEST_P(DDFunctionality, ControlledStandardNegOpBuildInverseBuild) { op = StandardOperation(Controls{0_nc}, 1, 2, gate, std::vector{dist(mt), dist(mt)}); break; + case RCCX: + op = StandardOperation(Controls{0_nc}, Targets{1, 2, 3}, gate); + break; default: op = StandardOperation(Controls{0_nc}, 1, gate); } @@ -281,6 +290,8 @@ TEST_F(DDFunctionality, BuildCircuit) { qc.cxx_minus_yy(theta, beta, 2, 0, 1); qc.xx_plus_yy(theta, beta, 0, 1); qc.cxx_plus_yy(theta, beta, 2, 0, 1); + qc.rccx(0, 1, 2); + qc.crccx(3, 0, 1, 2); qc.r(theta, beta, 0); qc.cr(theta, beta, 2, 0); qc.mcr(theta, beta, {2, 3}, 0); @@ -289,6 +300,8 @@ TEST_F(DDFunctionality, BuildCircuit) { qc.mcr(-theta, beta, {2, 3}, 0); qc.cr(-theta, beta, 2, 0); qc.r(-theta, beta, 0); + qc.crccx(3, 0, 1, 2); + qc.rccx(0, 1, 2); qc.cxx_plus_yy(-theta, beta, 2, 0, 1); qc.xx_plus_yy(-theta, beta, 0, 1); qc.cxx_minus_yy(-theta, beta, 2, 0, 1); diff --git a/test/dd/test_dd_noise_functionality.cpp b/test/dd/test_dd_noise_functionality.cpp index 7d7724a8de..4d5561f702 100644 --- a/test/dd/test_dd_noise_functionality.cpp +++ b/test/dd/test_dd_noise_functionality.cpp @@ -199,7 +199,7 @@ TEST_F(DDNoiseFunctionalityTest, testingMeasure) { fabs(tmp2["000"] - 1) < tolerance || fabs(tmp2["001"] - 1) < tolerance || fabs(tmp2["010"] - 1) < tolerance || fabs(tmp2["011"] - 1) < tolerance || fabs(tmp2["100"] - 1) < tolerance || fabs(tmp2["101"] - 1) < tolerance || - fabs(tmp2["111"] - 1) < tolerance); + fabs(tmp2["110"] - 1) < tolerance || fabs(tmp2["111"] - 1) < tolerance); } TEST_F(DDNoiseFunctionalityTest, StochSimulateAdder4TrackAPD) { diff --git a/test/dd/test_package.cpp b/test/dd/test_package.cpp index bcacd5bdea..f296e545fb 100644 --- a/test/dd/test_package.cpp +++ b/test/dd/test_package.cpp @@ -292,6 +292,10 @@ TEST(DDPackageTest, InvalidStandardOperation) { {qc::Targets{}, qc::SWAP}, {qc::Targets{0}, qc::SWAP}, {qc::Targets{0, 1, 2}, qc::SWAP}, + {qc::Targets{}, qc::RCCX}, + {qc::Targets{0}, qc::RCCX}, + {qc::Targets{0, 1}, qc::RCCX}, + {qc::Targets{0, 1, 2, 3}, qc::RCCX}, {qc::Targets{0, 1}, qc::OpTypeEnd}}}; for (const auto& [targets, type] : invalidOps) { ASSERT_THROW(getDD(qc::StandardOperation(targets, type), *dd), @@ -301,6 +305,8 @@ TEST(DDPackageTest, InvalidStandardOperation) { ASSERT_THROW(opToSingleQubitGateMatrix(qc::OpTypeEnd), std::invalid_argument); ASSERT_THROW(opToTwoQubitGateMatrix(qc::I), std::invalid_argument); ASSERT_THROW(opToTwoQubitGateMatrix(qc::OpTypeEnd), std::invalid_argument); + ASSERT_THROW(opToThreeQubitGateMatrix(qc::I), std::invalid_argument); + ASSERT_THROW(opToThreeQubitGateMatrix(qc::OpTypeEnd), std::invalid_argument); } TEST(DDPackageTest, PrintNoneGateType) { @@ -921,6 +927,48 @@ TEST(DDPackageTest, InvalidMakeBasisStateAndGate) { EXPECT_THROW(getDD(qc::StandardOperation(3, qc::X), *dd), std::runtime_error); } +TEST(DDPackageTest, RejectsGateConstructionInEmptyPackage) { + auto dd = std::make_unique(0U); + EXPECT_THROW(dd->makeGateDD(opToSingleQubitGateMatrix(qc::X), 0U), + std::runtime_error); + EXPECT_THROW(dd->makeTwoQubitGateDD(opToTwoQubitGateMatrix(qc::SWAP), 0U, 0U), + std::runtime_error); + EXPECT_THROW( + dd->makeThreeQubitGateDD(opToThreeQubitGateMatrix(qc::RCCX), 0U, 0U, 0U), + std::runtime_error); +} + +TEST(DDPackageTest, RejectsOverlappingGateQubits) { + auto dd = std::make_unique(5U); + const auto rccxMatrix = opToThreeQubitGateMatrix(qc::RCCX); + + // Duplicate two-qubit targets + EXPECT_THROW(dd->makeTwoQubitGateDD(opToTwoQubitGateMatrix(qc::SWAP), 1U, 1U), + std::runtime_error); + + // Control coincides with single-qubit target + EXPECT_THROW( + dd->makeGateDD(opToSingleQubitGateMatrix(qc::X), qc::Controls{{1}}, 1U), + std::runtime_error); + + // Duplicate three-qubit targets (cases skipped by RCCXGateDDConstruction) + EXPECT_THROW(dd->makeThreeQubitGateDD(rccxMatrix, 0U, 0U, 1U), + std::runtime_error); + EXPECT_THROW(dd->makeThreeQubitGateDD(rccxMatrix, 0U, 1U, 0U), + std::runtime_error); + EXPECT_THROW(dd->makeThreeQubitGateDD(rccxMatrix, 2U, 1U, 2U), + std::runtime_error); + EXPECT_THROW(dd->makeThreeQubitGateDD(rccxMatrix, 1U, 1U, 1U), + std::runtime_error); + + // Extra control coincides with an RCCX target + EXPECT_THROW( + dd->makeThreeQubitGateDD(rccxMatrix, qc::Controls{{1}}, 0U, 1U, 2U), + std::runtime_error); + EXPECT_THROW(dd->makeThreeQubitGateDD(rccxMatrix, qc::Control{0}, 0U, 1U, 2U), + std::runtime_error); +} + TEST(DDPackageTest, PackageReset) { auto dd = std::make_unique(1); @@ -2498,6 +2546,92 @@ TEST(DDPackageTest, XXPlusYYGateDDConstruction) { } } +TEST(DDPackageTest, RCCXGateDDConstruction) { + constexpr auto nrQubits = 5U; + const auto dd = std::make_unique(nrQubits); + const auto rccxMatrix = opToThreeQubitGateMatrix(qc::RCCX); + + const auto rccxDecomposition = [&](const qc::Controls& extra, + const Qubit control0, const Qubit control1, + const Qubit target) { + const auto withCtrl = [&](const Qubit ctrl) { + qc::Controls controls = extra; + controls.emplace(ctrl); + return controls; + }; + auto gateDD = getDD(qc::StandardOperation(extra, target, qc::H), *dd); + gateDD = dd->multiply( + getDD(qc::StandardOperation(extra, target, qc::T), *dd), gateDD); + gateDD = dd->multiply( + getDD(qc::StandardOperation(withCtrl(control1), target, qc::X), *dd), + gateDD); + gateDD = dd->multiply( + getDD(qc::StandardOperation(extra, target, qc::Tdg), *dd), gateDD); + gateDD = dd->multiply( + getDD(qc::StandardOperation(withCtrl(control0), target, qc::X), *dd), + gateDD); + gateDD = dd->multiply( + getDD(qc::StandardOperation(extra, target, qc::T), *dd), gateDD); + gateDD = dd->multiply( + getDD(qc::StandardOperation(withCtrl(control1), target, qc::X), *dd), + gateDD); + gateDD = dd->multiply( + getDD(qc::StandardOperation(extra, target, qc::Tdg), *dd), gateDD); + return dd->multiply(getDD(qc::StandardOperation(extra, target, qc::H), *dd), + gateDD); + }; + + for (Qubit control0 = 0; control0 < nrQubits; ++control0) { + for (Qubit control1 = 0; control1 < nrQubits; ++control1) { + if (control0 == control1) { + continue; + } + for (Qubit target = 0; target < nrQubits; ++target) { + if (target == control0 || target == control1) { + continue; + } + + // Bare RCCX via the no-control overload. + const auto bareOverload = + dd->makeThreeQubitGateDD(rccxMatrix, control0, control1, target); + const auto bareControls = dd->makeThreeQubitGateDD( + rccxMatrix, qc::Controls{}, control0, control1, target); + EXPECT_EQ(bareOverload, bareControls); + EXPECT_EQ(bareOverload, + rccxDecomposition({}, control0, control1, target)); + + // Bare RCCX and RCCX with one extra positive/negative control. + std::vector controlSets{{}}; + for (Qubit extra = 0; extra < nrQubits; ++extra) { + if (extra == control0 || extra == control1 || extra == target) { + continue; + } + controlSets.push_back({{extra, qc::Control::Type::Pos}}); + controlSets.push_back({{extra, qc::Control::Type::Neg}}); + } + + for (const auto& controls : controlSets) { + const auto rccxGateDD = + getDD(qc::StandardOperation( + controls, {control0, control1, target}, qc::RCCX), + *dd); + EXPECT_EQ(rccxGateDD, + rccxDecomposition(controls, control0, control1, target)); + + // Single-control overload matches Controls{control}. + if (controls.size() == 1U) { + const auto& control = *controls.begin(); + EXPECT_EQ(dd->makeThreeQubitGateDD(rccxMatrix, control, control0, + control1, target), + dd->makeThreeQubitGateDD(rccxMatrix, controls, control0, + control1, target)); + } + } + } + } + } +} + TEST(DDPackageTest, InnerProductTopNodeConjugation) { // Test comes from experimental results // 2 qubit state is rotated Rxx(-2) equivalent to diff --git a/test/fomac/CMakeLists.txt b/test/fomac/CMakeLists.txt index dd6542126f..5d435c3927 100644 --- a/test/fomac/CMakeLists.txt +++ b/test/fomac/CMakeLists.txt @@ -10,23 +10,5 @@ set(TARGET_NAME mqt-core-fomac-test) if(TARGET MQT::CoreFoMaC) package_add_test(${TARGET_NAME} MQT::CoreFoMaC test_fomac.cpp) - - if(WIN32) - # On Windows, we need to copy the device DLLs to the test directory - add_custom_command( - TARGET ${TARGET_NAME} - PRE_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ - $) - add_custom_command( - TARGET ${TARGET_NAME} - PRE_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ - $) - add_custom_command( - TARGET ${TARGET_NAME} - PRE_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ - $) - endif() + mqt_copy_qdmi_runtime(${TARGET_NAME}) endif() diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index f04edf2fd8..3922479f6d 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -346,6 +346,8 @@ TEST(FoMaCTest, DevicePropertyToString) { "MIN ATOM DISTANCE"); EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS), "SUPPORTED PROGRAM FORMATS"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_CHILDDEVICES), + "CHILD DEVICES"); EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_MAX), "MAX"); EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_CUSTOM1), "CUSTOM1"); EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_CUSTOM2), "CUSTOM2"); @@ -358,6 +360,11 @@ TEST(FoMaCTest, SessionPropertyToString) { EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PROPERTY_DEVICES), "DEVICES"); } +TEST(FoMaCTest, DeviceSessionParameterToString) { + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_SESSION_PARAMETER_CHILDDEVICE), + "CHILD DEVICE"); +} + TEST(FoMaCTest, ThrowIfError) { EXPECT_NO_THROW(qdmi::throwIfError(QDMI_SUCCESS, "Test")); EXPECT_NO_THROW(qdmi::throwIfError(QDMI_WARN_GENERAL, "Test")); @@ -732,6 +739,21 @@ c = measure q;)"; EXPECT_EQ(job.check(), QDMI_JOB_STATUS_DONE); } +TEST_F(DDSimulatorDeviceTest, SubmitJobRejectsIncompatiblePayloadKinds) { + const std::string textProgram = "OPENQASM 3.0;"; + constexpr std::array bytes{std::byte{0}}; + + EXPECT_THROW(std::ignore = device.submitJob( + textProgram, QDMI_PROGRAM_FORMAT_QIRBASEMODULE, 0), + std::invalid_argument); + EXPECT_THROW(std::ignore = device.submitJob( + textProgram, QDMI_PROGRAM_FORMAT_CALIBRATION, 0), + std::invalid_argument); + EXPECT_THROW(std::ignore = + device.submitJob(bytes, QDMI_PROGRAM_FORMAT_BATCHJOB, 0), + std::invalid_argument); +} + TEST_F(DDSimulatorDeviceTest, SubmitJobCustomSupportedTypes) { constexpr auto qasm3Program = "OPENQASM 3.0;"; @@ -1092,7 +1114,7 @@ TEST(AuthenticationTest, SessionConstructionWithAuthFile) { } SessionConfig config2; - config2.authFile = tmpPath.string(); + config2.authFile = tmpPath; EXPECT_NO_THROW({ const Session session(config2); }); // Clean up @@ -1222,6 +1244,34 @@ TEST(AuthenticationTest, SessionMultipleInstances) { EXPECT_EQ(devices1.size(), devices2.size()); } +TEST(DeviceOwnershipTest, SiteKeepsFreshSessionAlive) { + const auto site = [] { + auto device = Session::openDevice("mqt.na.default"); + return device.getSites().front(); + }(); + + EXPECT_EQ(site.getIndex(), 0); +} + +TEST(DeviceOwnershipTest, OperationKeepsFreshSessionAlive) { + const auto operation = [] { + auto device = Session::openDevice("mqt.na.default"); + return device.getOperations().front(); + }(); + + EXPECT_FALSE(operation.getName().empty()); +} + +TEST(DeviceOwnershipTest, SiteFromOperationKeepsFreshSessionAlive) { + const auto site = [] { + auto device = Session::openDevice("mqt.na.default"); + const auto operation = device.getOperations().front(); + return operation.getSites().value().front(); + }(); + + EXPECT_TRUE(site.isZone()); +} + namespace { // Helper function to get all devices for parameterized tests auto getDevices() -> std::vector { diff --git a/test/ir/test_io.cpp b/test/ir/test_io.cpp index 03ab35e81b..55f937212f 100644 --- a/test/ir/test_io.cpp +++ b/test/ir/test_io.cpp @@ -11,6 +11,7 @@ #include "ir/Definitions.hpp" #include "ir/QuantumComputation.hpp" #include "ir/operations/CompoundOperation.hpp" +#include "ir/operations/Control.hpp" #include "ir/operations/IfElseOperation.hpp" #include "ir/operations/NonUnitaryOperation.hpp" #include "ir/operations/OpType.hpp" @@ -31,10 +32,13 @@ #include #include #include +#include #include namespace { +using namespace qc::literals; + class IO : public testing::Test { protected: void TearDown() override {} @@ -62,6 +66,41 @@ void compareFiles(const std::string& file1, const std::string& file2) { ASSERT_EQ(str1, str2); } +void expectRccxSourceRoundTrip(const std::string& source, + const bool openQASM3) { + const auto imported = qasm3::Importer::imports(source); + ASSERT_EQ(imported.getNops(), 1U); + ASSERT_EQ(imported.front()->getType(), qc::RCCX); + EXPECT_EQ(imported.front()->getNcontrols(), 0U); + + std::ostringstream oss; + imported.dumpOpenQASM(oss, openQASM3); + EXPECT_EQ(oss.str(), source); +} + +void expectRccxExport(const qc::QuantumComputation& circuit, + const bool openQASM3, const std::string_view snippet, + const std::size_t expectedNops, + const std::size_t expectedNcontrols, + const std::size_t opIndex = 0, + const std::string_view extraSnippet = {}, + const bool expectNegControl = false) { + const auto qasm = circuit.toQASM(openQASM3); + EXPECT_NE(qasm.find(snippet), std::string::npos); + if (!extraSnippet.empty()) { + EXPECT_NE(qasm.find(extraSnippet), std::string::npos); + } + + const auto roundTrip = qasm3::Importer::imports(qasm); + ASSERT_EQ(roundTrip.getNops(), expectedNops); + const auto& op = roundTrip.at(opIndex); + EXPECT_EQ(op->getType(), qc::RCCX); + EXPECT_EQ(op->getNcontrols(), expectedNcontrols); + if (expectNegControl) { + EXPECT_EQ(op->getControls().begin()->type, qc::Control::Type::Neg); + } +} + } // namespace TEST_F(IO, importAndDumpQASM) { @@ -489,17 +528,58 @@ TEST_F(IO, NativeTwoQubitGateImportAndExport) { } } -TEST_F(IO, UseQelib1Gate) { - qc = qasm3::Importer::imports("include \"qelib1.inc\";" - "qreg q[3];" - "rccx q[0], q[1], q[2];"); - std::cout << qc << "\n"; - EXPECT_EQ(qc.getNqubits(), 3U); - EXPECT_EQ(qc.getNops(), 1U); +TEST_F(IO, RccxUncontrolledOpenQASMRoundTrip) { + expectRccxSourceRoundTrip("// i 0 1 2\n" + "// o 0 1 2\n" + "OPENQASM 2.0;\n" + "include \"qelib1.inc\";\n" + "qreg q[3];\n" + "rccx q[0], q[1], q[2];\n", + false); + expectRccxSourceRoundTrip("// i 0 1 2\n" + "// o 0 1 2\n" + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "qubit[3] q;\n" + "rccx q[0], q[1], q[2];\n", + true); +} + +TEST_F(IO, RccxControlledOpenQASMRoundTrip) { + qc.addQubitRegister(4); + qc.crccx(0, 1, 2, 3); + expectRccxExport(qc, false, "crccx q[0], q[1], q[2], q[3];", 1, 1); + expectRccxExport(qc, true, "ctrl @ rccx q[0], q[1], q[2], q[3];", 1, 1); + + qc.reset(); + qc.addQubitRegister(4); + qc.crccx(0_nc, 1, 2, 3); + // OpenQASM 2 encodes negative controls via X conjugation around crccx. + expectRccxExport(qc, false, "crccx q[0], q[1], q[2], q[3];", 3, 1, 1, + "x q[0];\n"); + expectRccxExport(qc, true, "negctrl @ rccx q[0], q[1], q[2], q[3];", 1, 1, 0, + {}, true); + + qc.reset(); + qc.addQubitRegister(5); + qc.mcrccx({0, 1}, 2, 3, 4); + expectRccxExport(qc, false, "ccrccx q[0], q[1], q[2], q[3], q[4];", 1, 2); + expectRccxExport(qc, true, "ctrl(2) @ rccx q[0], q[1], q[2], q[3], q[4];", 1, + 2); +} + +TEST_F(IO, Rc3xFromQelib1IsCompoundNotControlledRccx) { + qc = qasm3::Importer::imports("OPENQASM 2.0;\n" + "include \"qelib1.inc\";\n" + "qreg q[4];\n" + "rc3x q[0], q[1], q[2], q[3];\n"); + ASSERT_EQ(qc.getNops(), 1U); EXPECT_EQ(qc.front()->getType(), qc::Compound); - const auto& op = dynamic_cast(qc.front().get()); - ASSERT_NE(op, nullptr); - EXPECT_EQ(op->size(), 9U); + EXPECT_EQ(qc.front()->getNcontrols(), 0U); + const auto* compound = + dynamic_cast(qc.front().get()); + ASSERT_NE(compound, nullptr); + EXPECT_GT(compound->size(), 1U); } TEST_F(IO, ParameterizedGateDefinition) { diff --git a/test/ir/test_qfr_functionality.cpp b/test/ir/test_qfr_functionality.cpp index f6f3e2b51e..57fe252aba 100644 --- a/test/ir/test_qfr_functionality.cpp +++ b/test/ir/test_qfr_functionality.cpp @@ -326,6 +326,9 @@ TEST_F(QFRFunctionality, gateShortCutsAndCloning) { qc.xx_plus_yy(PI, PI, 0, 1); qc.cxx_plus_yy(PI, PI, 2, 0, 1); qc.mcxx_plus_yy(PI, PI, {2, 3_nc}, 0, 1); + qc.rccx(0, 1, 2); + qc.crccx(3_nc, 0, 1, 2); + qc.mcrccx({3_nc, 4}, 0, 1, 2); qc.measure(0, 0); qc.measure({1, 2}, {1, 2}); qc.barrier(0); @@ -889,6 +892,11 @@ TEST_F(QFRFunctionality, invertStandardOpParamChange) { op.invert(); const auto expectedTargets = Targets{1U, 0U}; ASSERT_EQ(op.getTargets(), expectedTargets); + + auto rccxOp = StandardOperation(Targets{0U, 1U, 2U}, OpType::RCCX); + rccxOp.invert(); + ASSERT_EQ(rccxOp.getType(), OpType::RCCX); + ASSERT_EQ(rccxOp.getTargets(), (Targets{0U, 1U, 2U})); } TEST_F(QFRFunctionality, invertCompoundOperation) { diff --git a/test/na/CMakeLists.txt b/test/na/CMakeLists.txt index fa743e93b1..017ad42d0d 100644 --- a/test/na/CMakeLists.txt +++ b/test/na/CMakeLists.txt @@ -12,4 +12,6 @@ if(TARGET MQT::CoreNA) target_link_libraries(mqt-core-na-test PRIVATE MQT::CoreQASM) endif() -add_subdirectory(fomac) +if(BUILD_MQT_CORE_QDMI_NA_DEVICE) + add_subdirectory(fomac) +endif() diff --git a/test/na/fomac/CMakeLists.txt b/test/na/fomac/CMakeLists.txt index b30219f250..05228857df 100644 --- a/test/na/fomac/CMakeLists.txt +++ b/test/na/fomac/CMakeLists.txt @@ -14,23 +14,5 @@ if(TARGET MQT::CoreNAFoMaC) # Set the device json path target_compile_definitions(${TARGET_NAME} PRIVATE NA_DEVICE_JSON="${PROJECT_SOURCE_DIR}/json/na/device.json") - - if(WIN32) - # On Windows, we need to copy the device DLLs to the test directory - add_custom_command( - TARGET ${TARGET_NAME} - PRE_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ - $) - add_custom_command( - TARGET ${TARGET_NAME} - PRE_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ - $) - add_custom_command( - TARGET ${TARGET_NAME} - PRE_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ - $) - endif() + mqt_copy_qdmi_runtime(${TARGET_NAME}) endif() diff --git a/test/python/fomac/test_fomac.py b/test/python/fomac/test_fomac.py index d3d01a4d28..40b287d5a5 100644 --- a/test/python/fomac/test_fomac.py +++ b/test/python/fomac/test_fomac.py @@ -16,7 +16,17 @@ import pytest -from mqt.core.fomac import CustomProperty, Device, Job, ProgramFormat, Session, add_dynamic_device_library +from mqt.core.fomac import ( + CustomProperty, + Device, + DeviceDefinition, + Job, + ProgramFormat, + Session, + open_device, + register_device, + register_device_if_absent, +) CustomValueType = type[str] | type[bool] | type[int] | type[float] | type[bytes] @@ -461,10 +471,87 @@ def test_device_submit_job_returns_valid_job(ddsim_device: Device) -> None: assert job.program_format == ProgramFormat.QASM3 # The program should be preserved assert job.program == qasm3_program + assert job.program_bytes == qasm3_program.encode() + b"\0" # Num shots should match request assert job.num_shots == 100 +def test_program_format_includes_batch_job() -> None: + """Expose every standard QDMI program format.""" + assert ProgramFormat.BATCH_JOB.value == 9 + + +@pytest.mark.parametrize("program", [b"OPENQASM 3.0;", b"OPENQASM 3.0;\0garbage\0", "OPENQASM 3.0;\0garbage"]) +def test_device_rejects_invalid_text_payloads(ddsim_device: Device, program: str | bytes) -> None: + """Reject payloads that do not satisfy QDMI's text contract.""" + with pytest.raises(ValueError, match=r"Setting program: Invalid argument\."): + ddsim_device.submit_job(program, ProgramFormat.QASM3, num_shots=1) + + +def test_device_rejects_text_for_binary_format(ddsim_device: Device) -> None: + """Require exact byte submission for known binary formats.""" + with pytest.raises(ValueError, match="require exact-byte submission"): + ddsim_device.submit_job("not bitcode", ProgramFormat.QIR_BASE_MODULE, num_shots=1) + + +@pytest.mark.parametrize("program_format", [ProgramFormat.CALIBRATION, ProgramFormat.BATCH_JOB]) +def test_device_rejects_formats_without_generic_payload(ddsim_device: Device, program_format: ProgramFormat) -> None: + """Keep specialized QDMI formats out of the generic program API.""" + with pytest.raises(ValueError, match="do not use a generic program payload"): + ddsim_device.submit_job(b"", program_format, num_shots=1) + + +def test_device_executes_qir_program(ddsim_device: Device) -> None: + """Compile and execute a QIR program with the DDSIM device.""" + # Keep this lazy to cover loading MLIR after the QIR-enabled device. + mlir = pytest.importorskip("mqt.core.mlir") + + qasm3_program = """ +OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +bit[2] c; +h q[0]; +cx q[0], q[1]; +c = measure q; +""" + program = mlir.compile_program(qasm3_program, output=mlir.OutputFormat.QIR_BASE) + assert ProgramFormat.QIR_BASE_STRING in ddsim_device.supported_program_formats() + + job = ddsim_device.submit_job(program.llvm_ir, ProgramFormat.QIR_BASE_STRING, num_shots=10) + job.wait() + + assert job.check() == Job.Status.DONE + assert sum(job.get_counts().values()) == 10 + + +def test_device_executes_binary_qir_program(ddsim_device: Device) -> None: + """Submit and retrieve an exact QIR module byte payload.""" + mlir = pytest.importorskip("mqt.core.mlir") + + qasm3_program = """ +OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +bit[2] c; +h q[0]; +cx q[0], q[1]; +c = measure q; +""" + program = mlir.compile_program(qasm3_program, output=mlir.OutputFormat.QIR_BASE) + program_bytes = program.to_bitcode() + assert ProgramFormat.QIR_BASE_MODULE in ddsim_device.supported_program_formats() + + job = ddsim_device.submit_job(program_bytes, ProgramFormat.QIR_BASE_MODULE, num_shots=10) + assert job.program_bytes == program_bytes + with pytest.raises(ValueError, match="binary program"): + _ = job.program + job.wait() + + assert job.check() == Job.Status.DONE + assert sum(job.get_counts().values()) == 10 + + def test_device_submit_job_handles_custom_parameters(ddsim_device: Device) -> None: """Test that submit_job forwards custom job parameters to DDSIM.""" with pytest.raises(RuntimeError, match=r"Setting custom parameter: Not supported\."): @@ -675,11 +762,11 @@ def test_session_construction_with_token() -> None: assert session is not None # Non-empty token should be accepted - session = Session(token="test_token_123") # noqa: S106 + session = Session(token="test_token_123") # ruff:ignore[hardcoded-password-func-arg] assert session is not None # Token with special characters should be accepted - session = Session(token="very_long_token_with_special_characters_!@#$%^&*()") # noqa: S106 + session = Session(token="very_long_token_with_special_characters_!@#$%^&*()") # ruff:ignore[hardcoded-password-func-arg] assert session is not None @@ -743,9 +830,11 @@ def test_session_construction_with_auth_file() -> None: tmp_path = tmp_file.name try: - # Existing file should be accepted (validation passes, parameter may be skipped) - session = Session(auth_file=tmp_path) - assert session is not None + # Both string and pathlib paths should be accepted. + string_session = Session(auth_file=tmp_path) + path_session = Session(auth_file=Path(tmp_path)) + assert string_session is not None + assert path_session is not None finally: # Clean up Path(tmp_path).unlink(missing_ok=True) @@ -761,11 +850,11 @@ def test_session_construction_with_username_password() -> None: assert session is not None # Password only - session = Session(password="secure_password") # noqa: S106 + session = Session(password="secure_password") # ruff:ignore[hardcoded-password-func-arg] assert session is not None # Both username and password - session = Session(username="user123", password="secure_password") # noqa: S106 + session = Session(username="user123", password="secure_password") # ruff:ignore[hardcoded-password-func-arg] assert session is not None @@ -784,9 +873,9 @@ def test_session_construction_with_multiple_parameters() -> None: Unsupported parameters are skipped, so construction should succeed. """ session = Session( - token="test_token", # noqa: S106 + token="test_token", # ruff:ignore[hardcoded-password-func-arg] username="test_user", - password="test_pass", # noqa: S106 + password="test_pass", # ruff:ignore[hardcoded-password-func-arg] project_id="test_project", ) assert session is not None @@ -829,7 +918,7 @@ def test_session_construction_with_custom_parameters() -> None: # Test mixing custom parameters with standard authentication try: session = Session( - token="test_token", # noqa: S106 + token="test_token", # ruff:ignore[hardcoded-password-func-arg] custom1="custom_value", project_id="project_id", ) @@ -869,7 +958,47 @@ def test_session_multiple_instances() -> None: assert len(devices1) == len(devices2) -def test_add_dynamic_device_library_nonexistent_library() -> None: - """Test that loading a non-existent library raises an error.""" +def test_register_device_does_not_load_nonexistent_library() -> None: + """Registration stores metadata and opening performs native loading.""" + library_path = Path("/nonexistent/lib.so") + definition = DeviceDefinition("python.missing", library_path, "PREFIX") + assert definition.device_id == "python.missing" + assert definition.library_path == library_path + assert definition.prefix == "PREFIX" + register_device(definition) with pytest.raises(RuntimeError): - add_dynamic_device_library("/nonexistent/lib.so", "PREFIX") + open_device("python.missing") + + +def test_register_device_if_absent_only_ignores_existing_id() -> None: + """Idempotent registration still validates duplicate definitions.""" + definition = DeviceDefinition("python.if-absent", "/nonexistent/device.so", "PREFIX") + assert register_device_if_absent(definition) + assert not register_device_if_absent(definition) + with pytest.raises(ValueError, match="library must not be empty"): + register_device_if_absent(DeviceDefinition("python.if-absent", "", "PREFIX")) + + +def test_open_device_rejects_unknown_id() -> None: + """Opening requires a stable registered ID.""" + with pytest.raises(IndexError, match="Unknown QDMI device ID"): + open_device("python.unknown") + + +def test_open_device_creates_a_fresh_session() -> None: + """Stable-ID opens should return separately owned sessions.""" + first = open_device("mqt.na.default") + second = open_device("mqt.na.default") + assert first != second + + +def test_site_keeps_fresh_session_alive() -> None: + """A site should remain usable after its device wrapper is destroyed.""" + site = open_device("mqt.na.default").sites()[0] + assert site.index() == 0 + + +def test_operation_keeps_fresh_session_alive() -> None: + """An operation should remain usable after its device wrapper is destroyed.""" + operation = open_device("mqt.na.default").operations()[0] + assert operation.name() diff --git a/test/python/plugins/qiskit/test_backend.py b/test/python/plugins/qiskit/test_backend.py index 4db8440785..6f0a5143b6 100644 --- a/test/python/plugins/qiskit/test_backend.py +++ b/test/python/plugins/qiskit/test_backend.py @@ -558,6 +558,21 @@ def test_job_submit_raises_error(ddsim_backend: QDMIBackend) -> None: job.submit() +def test_backend_supports_rccx_gate(ddsim_backend: QDMIBackend) -> None: + """DDSIM backend exposes and executes native RCCX gate circuits.""" + assert "rccx" in ddsim_backend.target.operation_names + + qc = QuantumCircuit(3) + qc.x(0) + qc.x(1) + qc.rccx(0, 1, 2) + qc.measure_all() + + job = ddsim_backend.run(qc, shots=100) + counts = job.result().get_counts() + assert counts == {"111": 100} + + def test_backend_supports_cz_gate(ddsim_backend: QDMIBackend) -> None: """Backend executes CZ gate circuits and returns counts.""" qc = QuantumCircuit(2) diff --git a/test/python/plugins/qiskit/test_mock_backend.py b/test/python/plugins/qiskit/test_mock_backend.py index b5b60af6c0..52922f21ef 100644 --- a/test/python/plugins/qiskit/test_mock_backend.py +++ b/test/python/plugins/qiskit/test_mock_backend.py @@ -266,7 +266,7 @@ def supported_program_formats() -> list[fomac.ProgramFormat]: """Return list of supported program formats.""" return [fomac.ProgramFormat.QASM2, fomac.ProgramFormat.QASM3] - def submit_job(self, program: str, program_format: fomac.ProgramFormat, num_shots: int) -> MockJob: # noqa: ARG002 + def submit_job(self, program: str, program_format: fomac.ProgramFormat, num_shots: int) -> MockJob: # ruff:ignore[unused-method-argument] """Submit a mock job to the device. Args: @@ -433,7 +433,7 @@ def test_backend_qasm_conversion_no_supported_formats(mock_qdmi_device_factory: backend = QDMIBackend(device) # ty: ignore[invalid-argument-type] with pytest.raises(UnsupportedFormatError, match="No supported program formats found"): - backend._convert_circuit(qc, []) # noqa: SLF001 + backend._convert_circuit(qc, []) # ruff:ignore[private-member-access] def test_backend_qasm3_conversion_success(mock_qdmi_device_factory: type[MockQDMIDevice]) -> None: @@ -446,7 +446,7 @@ def test_backend_qasm3_conversion_success(mock_qdmi_device_factory: type[MockQDM device = mock_qdmi_device_factory(num_qubits=2, operations=["h", "cx", "measure"]) backend = QDMIBackend(device) # ty: ignore[invalid-argument-type] - program, fmt = backend._convert_circuit(qc, [fomac.ProgramFormat.QASM3]) # noqa: SLF001 + program, fmt = backend._convert_circuit(qc, [fomac.ProgramFormat.QASM3]) # ruff:ignore[private-member-access] assert fmt == fomac.ProgramFormat.QASM3 assert "OPENQASM 3" in program @@ -464,7 +464,7 @@ def test_backend_qasm2_conversion_success(mock_qdmi_device_factory: type[MockQDM device = mock_qdmi_device_factory(num_qubits=2, operations=["h", "cx", "measure"]) backend = QDMIBackend(device) # ty: ignore[invalid-argument-type] - program, fmt = backend._convert_circuit(qc, [fomac.ProgramFormat.QASM2]) # noqa: SLF001 + program, fmt = backend._convert_circuit(qc, [fomac.ProgramFormat.QASM2]) # ruff:ignore[private-member-access] assert fmt == fomac.ProgramFormat.QASM2 assert "OPENQASM 2.0" in program @@ -482,7 +482,7 @@ def test_backend_qasm3_preferred_over_qasm2(mock_qdmi_device_factory: type[MockQ backend = QDMIBackend(device) # ty: ignore[invalid-argument-type] # When both formats are available, QASM3 should be chosen - program, fmt = backend._convert_circuit(qc, [fomac.ProgramFormat.QASM2, fomac.ProgramFormat.QASM3]) # noqa: SLF001 + program, fmt = backend._convert_circuit(qc, [fomac.ProgramFormat.QASM2, fomac.ProgramFormat.QASM3]) # ruff:ignore[private-member-access] assert fmt == fomac.ProgramFormat.QASM3 assert "OPENQASM 3" in program @@ -497,7 +497,7 @@ def test_backend_uses_iqm_json_when_supported(mock_qdmi_device_factory: type[Moc def mock_supported_formats() -> list[fomac.ProgramFormat]: return [fomac.ProgramFormat.IQM_JSON, fomac.ProgramFormat.QASM3] - def mock_submit_job(program: str, program_format: fomac.ProgramFormat, num_shots: int) -> MockQDMIDevice.MockJob: # noqa: ARG001 + def mock_submit_job(program: str, program_format: fomac.ProgramFormat, num_shots: int) -> MockQDMIDevice.MockJob: # ruff:ignore[unused-function-argument] nonlocal submitted_format submitted_format = program_format return device.MockJob(num_clbits=2, shots=num_shots) @@ -525,7 +525,7 @@ def test_backend_iqm_json_preferred_over_qasm(mock_qdmi_device_factory: type[Moc def mock_supported_formats() -> list[fomac.ProgramFormat]: return [fomac.ProgramFormat.QASM2, fomac.ProgramFormat.QASM3, fomac.ProgramFormat.IQM_JSON] - def mock_submit_job(program: str, program_format: fomac.ProgramFormat, num_shots: int) -> MockQDMIDevice.MockJob: # noqa: ARG001 + def mock_submit_job(program: str, program_format: fomac.ProgramFormat, num_shots: int) -> MockQDMIDevice.MockJob: # ruff:ignore[unused-function-argument] nonlocal submitted_format submitted_format = program_format return device.MockJob(num_clbits=2, shots=num_shots) @@ -561,7 +561,7 @@ def test_backend_qasm_conversion_failure( qasm_module = qasm3 if qasm_module_name == "qasm3" else qasm2 # Monkeypatch qasm dumps to raise an exception - def failing_dumps(circuit: object) -> NoReturn: # noqa: ARG001 + def failing_dumps(circuit: object) -> NoReturn: # ruff:ignore[unused-function-argument] msg = f"Simulated {qasm_module_name.upper()} conversion failure" raise ValueError(msg) @@ -575,7 +575,7 @@ def failing_dumps(circuit: object) -> NoReturn: # noqa: ARG001 backend = QDMIBackend(device) # ty: ignore[invalid-argument-type] with pytest.raises(TranslationError, match=f"Failed to convert circuit to {qasm_module_name.upper()}"): - backend._convert_circuit(qc, [program_format]) # noqa: SLF001 + backend._convert_circuit(qc, [program_format]) # ruff:ignore[private-member-access] def test_backend_unsupported_format_error(mock_qdmi_device_factory: type[MockQDMIDevice]) -> None: @@ -591,19 +591,19 @@ def test_backend_unsupported_format_error(mock_qdmi_device_factory: type[MockQDM with pytest.raises( UnsupportedFormatError, match="No conversion from Qiskit to any of the supported program formats" ): - backend._convert_circuit(qc, [fomac.ProgramFormat.QPY]) # noqa: SLF001 + backend._convert_circuit(qc, [fomac.ProgramFormat.QPY]) # ruff:ignore[private-member-access] def test_map_operation_returns_none_for_unknown() -> None: """Unknown FoMaC operations cannot be mapped to Qiskit gates.""" - assert QDMIBackend._map_operation_to_gate("unknown_gate") is None # noqa: SLF001 - assert QDMIBackend._map_operation_to_gate("custom_op") is None # noqa: SLF001 - assert QDMIBackend._map_operation_to_gate("") is None # noqa: SLF001 + assert QDMIBackend._map_operation_to_gate("unknown_gate") is None # ruff:ignore[private-member-access] + assert QDMIBackend._map_operation_to_gate("custom_op") is None # ruff:ignore[private-member-access] + assert QDMIBackend._map_operation_to_gate("") is None # ruff:ignore[private-member-access] def test_map_operation_to_move_gate() -> None: """MOVE operations map to an opaque 2-qubit gate.""" - gate = QDMIBackend._map_operation_to_gate("move") # noqa: SLF001 + gate = QDMIBackend._map_operation_to_gate("move") # ruff:ignore[private-member-access] assert gate is not None assert gate.name == "move" assert gate.num_qubits == 2 @@ -612,39 +612,39 @@ def test_map_operation_to_move_gate() -> None: def test_map_qiskit_gate_to_operation_names() -> None: """Test the inverse gate name mapping function comprehensively.""" # Basic gates map to themselves - assert QDMIBackend._map_qiskit_gate_to_operation_names("x") == {"x"} # noqa: SLF001 - assert QDMIBackend._map_qiskit_gate_to_operation_names("h") == {"h"} # noqa: SLF001 - assert QDMIBackend._map_qiskit_gate_to_operation_names("cz") == {"cz"} # noqa: SLF001 + assert QDMIBackend._map_qiskit_gate_to_operation_names("x") == {"x"} # ruff:ignore[private-member-access] + assert QDMIBackend._map_qiskit_gate_to_operation_names("h") == {"h"} # ruff:ignore[private-member-access] + assert QDMIBackend._map_qiskit_gate_to_operation_names("cz") == {"cz"} # ruff:ignore[private-member-access] # Aliases: gates with multiple naming conventions return all possible aliases - id_names = QDMIBackend._map_qiskit_gate_to_operation_names("id") # noqa: SLF001 + id_names = QDMIBackend._map_qiskit_gate_to_operation_names("id") # ruff:ignore[private-member-access] assert id_names == {"id", "i"} - assert QDMIBackend._map_qiskit_gate_to_operation_names("i") == id_names # noqa: SLF001 + assert QDMIBackend._map_qiskit_gate_to_operation_names("i") == id_names # ruff:ignore[private-member-access] - cx_names = QDMIBackend._map_qiskit_gate_to_operation_names("cx") # noqa: SLF001 + cx_names = QDMIBackend._map_qiskit_gate_to_operation_names("cx") # ruff:ignore[private-member-access] assert cx_names == {"cx", "cnot"} - assert QDMIBackend._map_qiskit_gate_to_operation_names("cnot") == cx_names # noqa: SLF001 + assert QDMIBackend._map_qiskit_gate_to_operation_names("cnot") == cx_names # ruff:ignore[private-member-access] # Device-specific aliases: bidirectional consistency for R/PRX (IQM naming) - r_names = QDMIBackend._map_qiskit_gate_to_operation_names("r") # noqa: SLF001 + r_names = QDMIBackend._map_qiskit_gate_to_operation_names("r") # ruff:ignore[private-member-access] assert r_names == {"r", "prx"} - assert QDMIBackend._map_qiskit_gate_to_operation_names("prx") == r_names # noqa: SLF001 + assert QDMIBackend._map_qiskit_gate_to_operation_names("prx") == r_names # ruff:ignore[private-member-access] - p_names = QDMIBackend._map_qiskit_gate_to_operation_names("p") # noqa: SLF001 + p_names = QDMIBackend._map_qiskit_gate_to_operation_names("p") # ruff:ignore[private-member-access] assert p_names == {"p", "phase"} - assert QDMIBackend._map_qiskit_gate_to_operation_names("phase") == p_names # noqa: SLF001 + assert QDMIBackend._map_qiskit_gate_to_operation_names("phase") == p_names # ruff:ignore[private-member-access] # Case-insensitive matching - assert QDMIBackend._map_qiskit_gate_to_operation_names("X") == {"x"} # noqa: SLF001 - assert QDMIBackend._map_qiskit_gate_to_operation_names("CX") == {"cx", "cnot"} # noqa: SLF001 + assert QDMIBackend._map_qiskit_gate_to_operation_names("X") == {"x"} # ruff:ignore[private-member-access] + assert QDMIBackend._map_qiskit_gate_to_operation_names("CX") == {"cx", "cnot"} # ruff:ignore[private-member-access] # MOVE operation is represented as a real gate for IQM devices - assert QDMIBackend._map_qiskit_gate_to_operation_names("move") == {"move"} # noqa: SLF001 - assert QDMIBackend._map_qiskit_gate_to_operation_names("MOVE") == {"move"} # noqa: SLF001 + assert QDMIBackend._map_qiskit_gate_to_operation_names("move") == {"move"} # ruff:ignore[private-member-access] + assert QDMIBackend._map_qiskit_gate_to_operation_names("MOVE") == {"move"} # ruff:ignore[private-member-access] # Fallback for unknown gates (returns lowercase name) - assert QDMIBackend._map_qiskit_gate_to_operation_names("unknown") == {"unknown"} # noqa: SLF001 - assert QDMIBackend._map_qiskit_gate_to_operation_names("CUSTOM") == {"custom"} # noqa: SLF001 + assert QDMIBackend._map_qiskit_gate_to_operation_names("unknown") == {"unknown"} # ruff:ignore[private-member-access] + assert QDMIBackend._map_qiskit_gate_to_operation_names("CUSTOM") == {"custom"} # ruff:ignore[private-member-access] def test_backend_validation_uses_inverse_mapping( diff --git a/test/python/plugins/qiskit/test_provider.py b/test/python/plugins/qiskit/test_provider.py index 8bc1136269..f6e6480b6f 100644 --- a/test/python/plugins/qiskit/test_provider.py +++ b/test/python/plugins/qiskit/test_provider.py @@ -111,7 +111,7 @@ def test_provider_with_token_parameter() -> None: # Should not raise an error when creating provider with token # Note: The currently available QDMI devices don't support authentication. try: - provider = QDMIProvider(token="test_token") # noqa: S106 + provider = QDMIProvider(token="test_token") # ruff:ignore[hardcoded-password-func-arg] # If device supports token, verify provider was created assert provider is not None except RuntimeError: @@ -157,7 +157,7 @@ def test_provider_with_username_password_parameters() -> None: # Should not raise an error when creating provider with username and password # Note: The currently available QDMI devices don't support authentication. try: - provider = QDMIProvider(username="test_user", password="test_pass") # noqa: S106 + provider = QDMIProvider(username="test_user", password="test_pass") # ruff:ignore[hardcoded-password-func-arg] assert provider is not None except RuntimeError: # If not supported, that's okay for now @@ -182,9 +182,9 @@ def test_provider_with_multiple_auth_parameters() -> None: # Note: The currently available QDMI devices don't support authentication. try: provider = QDMIProvider( - token="test_token", # noqa: S106 + token="test_token", # ruff:ignore[hardcoded-password-func-arg] username="test_user", - password="test_pass", # noqa: S106 + password="test_pass", # ruff:ignore[hardcoded-password-func-arg] project_id="test_project", ) assert provider is not None @@ -237,7 +237,7 @@ def test_provider_with_custom_parameters() -> None: # Test mixing custom with standard authentication try: provider = QDMIProvider( - token="test_token", # noqa: S106 + token="test_token", # ruff:ignore[hardcoded-password-func-arg] custom1="custom_value", project_id="project_id", ) diff --git a/test/python/plugins/test_qiskit.py b/test/python/plugins/test_qiskit.py index a7b4f1fbc7..db31b179ba 100644 --- a/test/python/plugins/test_qiskit.py +++ b/test/python/plugins/test_qiskit.py @@ -25,9 +25,10 @@ QuantumRegister, ) from qiskit.circuit.classical import expr -from qiskit.circuit.library import U2Gate, XXMinusYYGate, XXPlusYYGate +from qiskit.circuit.library import RCCXGate, U2Gate, XXMinusYYGate, XXPlusYYGate from qiskit.providers.fake_provider import GenericBackendV2 from qiskit.qasm3 import dumps +from qiskit.quantum_info import Operator from mqt.core.ir.operations import ( ComparisonKind, @@ -401,6 +402,7 @@ def test_operations() -> None: qc.append(U2Gate(0.5, 0.5), [0]) qc.append(XXMinusYYGate(0.5, 0.5), [0, 1]) qc.append(XXPlusYYGate(0.5, 0.5), [0, 1]) + qc.rccx(0, 1, 2) print(qc) mqt_qc = qiskit_to_mqt(qc) @@ -417,6 +419,20 @@ def test_operations() -> None: assert len(qiskit_qc) == len(qc) +@pytest.mark.parametrize("ctrl_state", ["0", "1"]) +def test_controlled_rccx(ctrl_state: str) -> None: + """Controlled RCCX imports via gate definition and round-trips.""" + qc = QuantumCircuit(4) + qc.append(RCCXGate().control(1, ctrl_state=ctrl_state), [0, 1, 2, 3]) + + mqt_qc = qiskit_to_mqt(qc) + assert len(mqt_qc) == 1 + assert isinstance(mqt_qc[0], CompoundOperation) + + qiskit_qc = mqt_to_qiskit(mqt_qc) + assert Operator(qc).equiv(Operator(qiskit_qc)) + + def test_symbolic() -> None: """Test import of symbolic parameters.""" qc = QuantumCircuit(1) diff --git a/test/python/test_cli.py b/test/python/test_cli.py index 2b4980c385..75f2b53984 100644 --- a/test/python/test_cli.py +++ b/test/python/test_cli.py @@ -109,5 +109,5 @@ def test_cli_cmake_dir_not_found(script_runner: ScriptRunner) -> None: @pytest.mark.skipif(sys.platform.startswith("win"), reason="The subprocess calls do not work properly on Windows.") def test_cli_execute_module() -> None: """Test running the CLI by executing the mqt-core module.""" - output = check_output(["python", "-m", "mqt.core", "--version"]) # noqa: S607 + output = check_output(["python", "-m", "mqt.core", "--version"]) # ruff:ignore[start-process-with-partial-path] assert mqt_core_version in output.decode() diff --git a/test/python/test_optional_dependency.py b/test/python/test_optional_dependency.py index 306923ca32..f3e1726538 100644 --- a/test/python/test_optional_dependency.py +++ b/test/python/test_optional_dependency.py @@ -15,7 +15,7 @@ import pytest -from mqt.core._compat.optional import OptionalDependencyTester # noqa: PLC2701 +from mqt.core._compat.optional import OptionalDependencyTester # ruff:ignore[import-private-name] def test_available_module() -> None: @@ -40,7 +40,7 @@ def test_caching() -> None: assert tester # Cache should be set now - assert tester._bool # noqa: SLF001 + assert tester._bool # ruff:ignore[private-member-access] # Second check should use cache assert tester @@ -107,13 +107,13 @@ def test_disable_locally_preserves_state() -> None: """Test disable_locally preserves original state including None.""" tester = OptionalDependencyTester("sys") # Don't check yet, so _bool is None - assert tester._bool is None # noqa: SLF001 + assert tester._bool is None # ruff:ignore[private-member-access] with tester.disable_locally(): assert not tester # Should be None again (not True) - assert tester._bool is None # noqa: SLF001 + assert tester._bool is None # ruff:ignore[private-member-access] def test_disable_locally_with_unavailable() -> None: diff --git a/test/qdmi/CMakeLists.txt b/test/qdmi/CMakeLists.txt index 595fc88d15..e7408fe449 100644 --- a/test/qdmi/CMakeLists.txt +++ b/test/qdmi/CMakeLists.txt @@ -7,4 +7,9 @@ # Licensed under the MIT License add_subdirectory(devices) -add_subdirectory(driver) +if(BUILD_MQT_CORE_QDMI_DDSIM_DEVICE + AND BUILD_MQT_CORE_QDMI_NA_DEVICE + AND BUILD_MQT_CORE_QDMI_SC_DEVICE) + add_subdirectory(driver) +endif() +add_subdirectory(registry) diff --git a/test/qdmi/devices/dd/CMakeLists.txt b/test/qdmi/devices/dd/CMakeLists.txt index a2e19861d1..321b105718 100644 --- a/test/qdmi/devices/dd/CMakeLists.txt +++ b/test/qdmi/devices/dd/CMakeLists.txt @@ -33,6 +33,15 @@ if(TARGET MQT::CoreQDMI_DDSIM_Device) target_compile_definitions(${TARGET_NAME} PRIVATE MQT_CORE_VERSION="${MQT_CORE_VERSION}") target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + if(BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR) + llvm_map_components_to_libnames(llvm_native_libs asmparser bitwriter core support) + target_link_libraries(${TARGET_NAME} PRIVATE MQT::CoreQIRRuntime mqt-core-qir-test-utils + ${llvm_native_libs}) + target_compile_definitions( + ${TARGET_NAME} PRIVATE BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR + QIR_FILES_DIR="${PROJECT_SOURCE_DIR}/test/circuits") + endif() + # On Windows, we need to copy the DLL to the test executable directory if(WIN32) add_custom_command( diff --git a/test/qdmi/devices/dd/error_handling_test.cpp b/test/qdmi/devices/dd/error_handling_test.cpp index 428f7d49e2..c67f1ec19b 100644 --- a/test/qdmi/devices/dd/error_handling_test.cpp +++ b/test/qdmi/devices/dd/error_handling_test.cpp @@ -294,7 +294,7 @@ TEST_F(ErrorHandling, MalformedProgramFailsForBothModes) { { const qdmi_test::JobGuard j{s.session}; ASSERT_EQ(qdmi_test::setProgram(j.job, QDMI_PROGRAM_FORMAT_QASM3, - qdmi_test::MALFORMED_PROGRAM), + qdmi_test::QASM3_MALFORMED), QDMI_SUCCESS); ASSERT_EQ(qdmi_test::setShots(j.job, 128), QDMI_SUCCESS); ASSERT_EQ(qdmi_test::submitAndWait(j.job, 0), QDMI_SUCCESS); @@ -306,7 +306,7 @@ TEST_F(ErrorHandling, MalformedProgramFailsForBothModes) { { const qdmi_test::JobGuard j{s.session}; ASSERT_EQ(qdmi_test::setProgram(j.job, QDMI_PROGRAM_FORMAT_QASM3, - qdmi_test::MALFORMED_PROGRAM), + qdmi_test::QASM3_MALFORMED), QDMI_SUCCESS); ASSERT_EQ(qdmi_test::setShots(j.job, 0), QDMI_SUCCESS); ASSERT_EQ(qdmi_test::submitAndWait(j.job, 0), QDMI_SUCCESS); diff --git a/test/qdmi/devices/dd/helpers/circuits.hpp b/test/qdmi/devices/dd/helpers/circuits.hpp index f530eabe92..b6f7564ff1 100644 --- a/test/qdmi/devices/dd/helpers/circuits.hpp +++ b/test/qdmi/devices/dd/helpers/circuits.hpp @@ -30,7 +30,7 @@ h q[0]; cx q[0], q[1]; )"; -inline constexpr const char* MALFORMED_PROGRAM = "Definitely not OpenQASM"; +inline constexpr const char* QASM3_MALFORMED = "Definitely not OpenQASM"; // A slightly heavier dynamic sampling circuit to prolong runtime slightly while // remaining fast diff --git a/test/qdmi/devices/dd/helpers/test_utils.cpp b/test/qdmi/devices/dd/helpers/test_utils.cpp index f17e6b63df..9290821378 100644 --- a/test/qdmi/devices/dd/helpers/test_utils.cpp +++ b/test/qdmi/devices/dd/helpers/test_utils.cpp @@ -97,9 +97,19 @@ int setProgram(MQT_DDSIM_QDMI_Device_Job job, const QDMI_Program_Format fmt, if (rc != QDMI_SUCCESS && rc != QDMI_ERROR_NOTSUPPORTED) { return rc; } + // Text payloads include the trailing '\0' per the QDMI wire convention. + // Binary payloads ship the exact byte count. + // The `+1` is safe here because every existing call to `setProgram` with a + // text format passes a `program` with a string literal or `std::string`, both + // of which guarantee `'\0'` at `data()[size()]`. + const bool isTextProgramFormat = fmt == QDMI_PROGRAM_FORMAT_QASM2 || + fmt == QDMI_PROGRAM_FORMAT_QASM3 || + fmt == QDMI_PROGRAM_FORMAT_QIRBASESTRING || + fmt == QDMI_PROGRAM_FORMAT_QIRADAPTIVESTRING; + const auto bytesToSend = + isTextProgramFormat ? program.size() + 1 : program.size(); rc = MQT_DDSIM_QDMI_device_job_set_parameter( - job, QDMI_DEVICE_JOB_PARAMETER_PROGRAM, program.size() + 1, - program.data()); + job, QDMI_DEVICE_JOB_PARAMETER_PROGRAM, bytesToSend, program.data()); return rc; } diff --git a/test/qdmi/devices/dd/job_parameters_test.cpp b/test/qdmi/devices/dd/job_parameters_test.cpp index 6cfe4b1214..da57756f37 100644 --- a/test/qdmi/devices/dd/job_parameters_test.cpp +++ b/test/qdmi/devices/dd/job_parameters_test.cpp @@ -18,6 +18,7 @@ #include +#include #include #include #include @@ -85,13 +86,55 @@ TEST(JobParameters, SetAndQueryBasics) { EXPECT_EQ(program, qdmi_test::QASM3_BELL_SAMPLING); } +TEST(JobParameters, RejectsUnterminatedTextProgram) { + const qdmi_test::SessionGuard s{}; + const qdmi_test::JobGuard j{s.session}; + + constexpr QDMI_Program_Format fmt = QDMI_PROGRAM_FORMAT_QASM3; + ASSERT_EQ(MQT_DDSIM_QDMI_device_job_set_parameter( + j.job, QDMI_DEVICE_JOB_PARAMETER_PROGRAMFORMAT, + sizeof(QDMI_Program_Format), &fmt), + QDMI_SUCCESS); + + EXPECT_EQ(MQT_DDSIM_QDMI_device_job_set_parameter( + j.job, QDMI_DEVICE_JOB_PARAMETER_PROGRAM, + strlen(qdmi_test::QASM3_BELL_SAMPLING), + qdmi_test::QASM3_BELL_SAMPLING), + QDMI_ERROR_INVALIDARGUMENT); +} + +TEST(JobParameters, RejectsInteriorNullInTextProgram) { + const qdmi_test::SessionGuard s{}; + const qdmi_test::JobGuard j{s.session}; + + constexpr QDMI_Program_Format fmt = QDMI_PROGRAM_FORMAT_QASM3; + ASSERT_EQ(MQT_DDSIM_QDMI_device_job_set_parameter( + j.job, QDMI_DEVICE_JOB_PARAMETER_PROGRAMFORMAT, + sizeof(QDMI_Program_Format), &fmt), + QDMI_SUCCESS); + + constexpr auto program = std::to_array("OPENQASM 3.0;\0garbage"); + EXPECT_EQ(MQT_DDSIM_QDMI_device_job_set_parameter( + j.job, QDMI_DEVICE_JOB_PARAMETER_PROGRAM, program.size(), + program.data()), + QDMI_ERROR_INVALIDARGUMENT); +} + TEST(JobParameters, ProgramFormatSupport) { const qdmi_test::SessionGuard s{}; const qdmi_test::JobGuard j{s.session}; // Supported - for (QDMI_Program_Format fmt : - {QDMI_PROGRAM_FORMAT_QASM2, QDMI_PROGRAM_FORMAT_QASM3}) { + for (QDMI_Program_Format fmt : { + QDMI_PROGRAM_FORMAT_QASM2, + QDMI_PROGRAM_FORMAT_QASM3, +#ifdef BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR + QDMI_PROGRAM_FORMAT_QIRBASESTRING, + QDMI_PROGRAM_FORMAT_QIRBASEMODULE, + QDMI_PROGRAM_FORMAT_QIRADAPTIVESTRING, + QDMI_PROGRAM_FORMAT_QIRADAPTIVEMODULE, +#endif + }) { EXPECT_EQ(MQT_DDSIM_QDMI_device_job_set_parameter( j.job, QDMI_DEVICE_JOB_PARAMETER_PROGRAMFORMAT, sizeof(QDMI_Program_Format), &fmt), @@ -100,10 +143,12 @@ TEST(JobParameters, ProgramFormatSupport) { // Unsupported → NOTSUPPORTED for (QDMI_Program_Format fmt : { +#ifndef BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR QDMI_PROGRAM_FORMAT_QIRBASESTRING, QDMI_PROGRAM_FORMAT_QIRBASEMODULE, QDMI_PROGRAM_FORMAT_QIRADAPTIVESTRING, QDMI_PROGRAM_FORMAT_QIRADAPTIVEMODULE, +#endif QDMI_PROGRAM_FORMAT_CALIBRATION, QDMI_PROGRAM_FORMAT_QPY, QDMI_PROGRAM_FORMAT_IQMJSON, diff --git a/test/qdmi/devices/dd/results_sampling_test.cpp b/test/qdmi/devices/dd/results_sampling_test.cpp index 8d329f6c16..150d295d4d 100644 --- a/test/qdmi/devices/dd/results_sampling_test.cpp +++ b/test/qdmi/devices/dd/results_sampling_test.cpp @@ -18,27 +18,163 @@ #include +#include #include +#include +#include +#include +#include +#include #include -TEST(ResultsSampling, HistogramKeysAndValuesSumToShots) { - const qdmi_test::SessionGuard s{}; - const qdmi_test::JobGuard j{s.session}; - ASSERT_EQ(qdmi_test::setProgram(j.job, QDMI_PROGRAM_FORMAT_QASM3, - qdmi_test::QASM3_BELL_SAMPLING), - QDMI_SUCCESS); - constexpr size_t shots = 1024; - ASSERT_EQ(qdmi_test::setShots(j.job, shots), QDMI_SUCCESS); - ASSERT_EQ(qdmi_test::submitAndWait(j.job, 0), QDMI_SUCCESS); +#ifdef BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR +#include "qir/helpers/test_utils.hpp" +#include "qir/runtime/Runtime.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#endif + +namespace { - auto [keys, vals] = qdmi_test::getHistogram(j.job); - ASSERT_EQ(keys.size(), vals.size()); - size_t sum = 0U; - for (const auto& v : vals) { - sum += v; +class HistogramTest : public ::testing::Test { +protected: +#ifdef BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR + std::ostringstream sink; + void SetUp() override { qir::Runtime::getInstance().setOstream(sink); } + void TearDown() override { qir::Runtime::getInstance().resetOstream(); } +#endif + + using Histogram = std::pair, std::vector>; + static constexpr size_t NUM_SHOTS = 1024; + static constexpr size_t NUM_QUBITS = 3; + + static Histogram runProgram(const QDMI_Program_Format format, + const std::string_view program) { + const qdmi_test::SessionGuard s{}; + const qdmi_test::JobGuard j{s.session}; + EXPECT_EQ(qdmi_test::setProgram(j.job, format, program), QDMI_SUCCESS); + EXPECT_EQ(qdmi_test::setShots(j.job, NUM_SHOTS), QDMI_SUCCESS); + EXPECT_EQ(qdmi_test::submitAndWait(j.job, 0), QDMI_SUCCESS); + return qdmi_test::getHistogram(j.job); } - EXPECT_EQ(sum, shots); + + static void checkHistogram(const Histogram& hist) { + const auto& [keys, vals] = hist; + // Keys and values come from two independent device queries. + // Check both vectors have the same size. + ASSERT_EQ(keys.size(), vals.size()); + // Values should sum up to NUM_SHOTS. + const auto sum = std::accumulate(vals.cbegin(), vals.cend(), size_t{0}); + EXPECT_EQ(sum, NUM_SHOTS); + // Both keys '00' and '11' should be expected. + ASSERT_EQ(keys.size(), 2U); + // And no other keys should be expected. + EXPECT_TRUE(std::ranges::all_of( + keys, [](const auto& k) { return k == "00" || k == "11"; })); + } + + /// Smoke check used for circuits whose distribution we do not know precisely. + /// For example, multi-output adaptive programs. + static void checkSmokeHistogram(const Histogram& hist) { + const auto& [keys, vals] = hist; + // Both vectors have the same size. + ASSERT_EQ(keys.size(), vals.size()); + // Values sum up to NUM_SHOTS. + const auto sum = std::accumulate(vals.cbegin(), vals.cend(), size_t{0}); + EXPECT_EQ(sum, NUM_SHOTS); + // Every key is a NUM_QUBITS long bit string. + EXPECT_TRUE(std::ranges::all_of(keys, [](const auto& k) { + return k.size() == NUM_QUBITS && std::ranges::all_of(k, [](char c) { + return c == '0' || c == '1'; + }); + })); + } +}; + +#ifdef BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR +class QIRHistogramTestModule : public HistogramTest { +protected: + static std::string getProgram(const std::string_view file) { + const std::string text = qir_test::getProgram(file); + llvm::LLVMContext context; + llvm::SMDiagnostic err; + auto llvmModule = llvm::parseAssemblyString(text, err, context); + EXPECT_NE(llvmModule, nullptr) + << "parseAssemblyString failed: " << err.getMessage().str(); + if (llvmModule == nullptr) { + return {}; + } + std::string bitcodeBuffer; + llvm::raw_string_ostream os(bitcodeBuffer); + llvm::WriteBitcodeToFile(*llvmModule, os); + os.flush(); + return bitcodeBuffer; + } +}; + +class QIRHistogramTestString : public HistogramTest {}; +#endif + +} // namespace + +TEST_F(HistogramTest, QASM3Program) { + constexpr QDMI_Program_Format format = QDMI_PROGRAM_FORMAT_QASM3; + constexpr std::string_view program = qdmi_test::QASM3_BELL_SAMPLING; + checkHistogram(runProgram(format, program)); +} + +#ifdef BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR +TEST_F(QIRHistogramTestModule, BaseStatic) { + constexpr auto format = QDMI_PROGRAM_FORMAT_QIRBASEMODULE; + checkHistogram(runProgram(format, getProgram("BellPairStatic.ll"))); +} + +TEST_F(QIRHistogramTestString, BaseStatic) { + constexpr auto format = QDMI_PROGRAM_FORMAT_QIRBASESTRING; + checkHistogram(runProgram(format, qir_test::getProgram("BellPairStatic.ll"))); +} + +TEST_F(QIRHistogramTestModule, BaseDynamic) { + constexpr auto format = QDMI_PROGRAM_FORMAT_QIRBASEMODULE; + checkHistogram(runProgram(format, getProgram("BellPairDynamic.ll"))); +} + +TEST_F(QIRHistogramTestString, BaseDynamic) { + constexpr auto format = QDMI_PROGRAM_FORMAT_QIRBASESTRING; + checkHistogram( + runProgram(format, qir_test::getProgram("BellPairDynamic.ll"))); +} + +TEST_F(QIRHistogramTestModule, Adaptive) { + constexpr auto format = QDMI_PROGRAM_FORMAT_QIRADAPTIVEMODULE; + checkHistogram(runProgram(format, getProgram("BellPairAdaptive.ll"))); +} + +TEST_F(QIRHistogramTestString, Adaptive) { + constexpr auto format = QDMI_PROGRAM_FORMAT_QIRADAPTIVESTRING; + checkHistogram( + runProgram(format, qir_test::getProgram("BellPairAdaptive.ll"))); +} + +TEST_F(QIRHistogramTestModule, AdaptiveRecordOutputs) { + constexpr auto format = QDMI_PROGRAM_FORMAT_QIRADAPTIVEMODULE; + checkSmokeHistogram( + runProgram(format, getProgram("AdaptiveRecordOutputs.ll"))); +} + +TEST_F(QIRHistogramTestString, AdaptiveRecordOutputs) { + constexpr auto format = QDMI_PROGRAM_FORMAT_QIRADAPTIVESTRING; + checkSmokeHistogram( + runProgram(format, qir_test::getProgram("AdaptiveRecordOutputs.ll"))); } +#endif TEST(ResultsSampling, BufferTooSmallErrors) { const qdmi_test::SessionGuard s{}; diff --git a/test/qdmi/devices/dd/results_statevector_test.cpp b/test/qdmi/devices/dd/results_statevector_test.cpp index 5f9b78a033..eb05ee429a 100644 --- a/test/qdmi/devices/dd/results_statevector_test.cpp +++ b/test/qdmi/devices/dd/results_statevector_test.cpp @@ -22,6 +22,15 @@ #include #include +#ifdef BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR +#include "qir/helpers/test_utils.hpp" +#include "qir/runtime/Runtime.hpp" + +#include +#include +#include +#endif + TEST(ResultsStatevector, DenseNormalizedAndBufferTooSmall) { const qdmi_test::SessionGuard s{}; const qdmi_test::JobGuard j{s.session}; @@ -103,3 +112,37 @@ TEST(ResultsStatevector, HistogramRequestsInvalidWithShotsZero) { j.job, QDMI_JOB_RESULT_HIST_VALUES, 0, nullptr, nullptr), QDMI_ERROR_INVALIDARGUMENT); } + +#ifdef BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR +namespace { + +class QIRStateExtractionTest : public testing::Test { +protected: + std::ostringstream sink; + void SetUp() override { qir::Runtime::getInstance().setOstream(sink); } + void TearDown() override { qir::Runtime::getInstance().resetOstream(); } +}; + +TEST_F(QIRStateExtractionTest, BellPairStaticBaseStringYieldsBellState) { + const qdmi_test::SessionGuard s{}; + const qdmi_test::JobGuard j{s.session}; + const auto program = qir_test::getProgram("BellPairStatic.ll"); + ASSERT_EQ( + qdmi_test::setProgram(j.job, QDMI_PROGRAM_FORMAT_QIRBASESTRING, program), + QDMI_SUCCESS); + ASSERT_EQ(qdmi_test::setShots(j.job, 0), QDMI_SUCCESS); + ASSERT_EQ(qdmi_test::submitAndWait(j.job, 0), QDMI_SUCCESS); + + const auto vec = qdmi_test::getDenseState(j.job); + ASSERT_EQ(vec.size(), 4U); + + // Bell pair: amplitudes at |00> and |11> are 1/sqrt(2), |01> and |10> are 0. + constexpr double invSqrt2 = 1.0 / std::numbers::sqrt2; + EXPECT_NEAR(std::abs(vec[0]), invSqrt2, 1e-6); + EXPECT_NEAR(std::abs(vec[1]), 0.0, 1e-6); + EXPECT_NEAR(std::abs(vec[2]), 0.0, 1e-6); + EXPECT_NEAR(std::abs(vec[3]), invSqrt2, 1e-6); +} + +} // namespace +#endif diff --git a/test/qdmi/driver/CMakeLists.txt b/test/qdmi/driver/CMakeLists.txt index 6fc3089a23..3e373d44ee 100644 --- a/test/qdmi/driver/CMakeLists.txt +++ b/test/qdmi/driver/CMakeLists.txt @@ -9,30 +9,71 @@ set(TARGET_NAME mqt-core-qdmi-driver-test) if(TARGET MQT::CoreQDMIDriver) + add_library(mqt-core-qdmi-metadata-device SHARED metadata_device.cpp) + set_target_properties( + mqt-core-qdmi-metadata-device + PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/device" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/device" + QDMI_DEVICE_ID "test.metadata-only" + QDMI_DEVICE_PREFIX "TEST_METADATA") + set_property( + TARGET mqt-core-qdmi-metadata-device + APPEND + PROPERTY EXPORT_PROPERTIES QDMI_DEVICE_ID QDMI_DEVICE_PREFIX) + set(metadata_device_export + "${CMAKE_CURRENT_BINARY_DIR}/mqt-core-qdmi-metadata-device-targets.cmake") + export(TARGETS mqt-core-qdmi-metadata-device FILE "${metadata_device_export}") + + add_library(mqt-core-qdmi-session-device SHARED session_device.cpp) + target_link_libraries(mqt-core-qdmi-session-device PRIVATE qdmi::qdmi) + set_target_properties( + mqt-core-qdmi-session-device + PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/device" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/device") + package_add_test(${TARGET_NAME} MQT::CoreQDMIDriver test_driver.cpp) target_link_libraries(${TARGET_NAME} PRIVATE MQT::CoreFoMaC) + set(config_file "${CMAKE_CURRENT_BINARY_DIR}/$/configured-devices.json") + file( + GENERATE + OUTPUT "${config_file}" + CONTENT + "{\n \"schema-version\": 1,\n \"qdmi\": {\n \"devices\": [\n {\"id\": \"mqt.na.default\", \"library\": \"$\", \"prefix\": \"MQT_NA\"},\n {\"id\": \"mqt.sc.default\", \"library\": \"$\", \"prefix\": \"MQT_SC\"},\n {\"id\": \"mqt.ddsim.default\", \"library\": \"$\", \"prefix\": \"MQT_DDSIM\"},\n {\"id\": \"test.disabled\", \"enabled\": false},\n {\"id\": \"broken.example\", \"library\": \"missing-device-library\", \"prefix\": \"BROKEN\"}\n ]\n }\n}\n" + ) + string(MAKE_C_IDENTIFIER "${TARGET_NAME}-mqt-core-qdmi-metadata-device" metadata_manifest_stem) + set(metadata_manifest_file + "$/${metadata_manifest_stem}.qdmi.json") target_compile_definitions( ${TARGET_NAME} PRIVATE - "DYN_DEV_LIBS=std::array{ std::pair{\"$\", \"MQT_NA\"}, std::pair{\"$\", \"MQT_SC\"}, std::pair{\"$\", \"MQT_DDSIM\"} }" + "MQT_CORE_QDMI_TEST_CONFIG_FILE=\"${config_file}\"" + "MQT_CORE_QDMI_METADATA_MANIFEST=\"${metadata_manifest_file}\"" + "MQT_CORE_QDMI_SESSION_DEVICE=\"$\"" + "TEST_DEVICE_LIBRARIES=std::array{ std::pair{\"$\", \"MQT_NA\"}, std::pair{\"$\", \"MQT_SC\"}, std::pair{\"$\", \"MQT_DDSIM\"} }" ) - if(WIN32) - # On Windows, we need to copy the device DLLs to the test directory - add_custom_command( - TARGET ${TARGET_NAME} - PRE_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ - $) - add_custom_command( - TARGET ${TARGET_NAME} - PRE_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ - $) - add_custom_command( - TARGET ${TARGET_NAME} - PRE_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ - $) + mqt_copy_qdmi_runtime(${TARGET_NAME}) + mqt_copy_qdmi_runtime(${TARGET_NAME} mqt-core-qdmi-metadata-device) + add_dependencies(${TARGET_NAME} mqt-core-qdmi-session-device) + + set(imported_device_build_dir "${CMAKE_CURRENT_BINARY_DIR}/imported-device-consumer") + set(imported_device_configure_command + ${CMAKE_COMMAND} -S "${CMAKE_CURRENT_SOURCE_DIR}/imported_device" -B + "${imported_device_build_dir}" -G "${CMAKE_GENERATOR}" + "-DMQT_CORE_QDMI_DEVICE_TARGETS=${metadata_device_export}" + "-DMQT_CORE_QDMI_HELPER=${PROJECT_SOURCE_DIR}/cmake/AddMQTQDMIDevice.cmake") + if(CMAKE_GENERATOR_PLATFORM) + list(APPEND imported_device_configure_command -A "${CMAKE_GENERATOR_PLATFORM}") + endif() + if(CMAKE_GENERATOR_TOOLSET) + list(APPEND imported_device_configure_command -T "${CMAKE_GENERATOR_TOOLSET}") endif() + add_test(NAME mqt-core-qdmi-imported-device-configure + COMMAND ${imported_device_configure_command}) + add_test(NAME mqt-core-qdmi-imported-device-build + COMMAND ${CMAKE_COMMAND} --build "${imported_device_build_dir}" --config $) + set_tests_properties(mqt-core-qdmi-imported-device-configure + PROPERTIES FIXTURES_SETUP mqt-core-qdmi-imported-device) + set_tests_properties(mqt-core-qdmi-imported-device-build PROPERTIES FIXTURES_REQUIRED + mqt-core-qdmi-imported-device) endif() diff --git a/test/qdmi/driver/imported_device/CMakeLists.txt b/test/qdmi/driver/imported_device/CMakeLists.txt new file mode 100644 index 0000000000..bfb2493c1a --- /dev/null +++ b/test/qdmi/driver/imported_device/CMakeLists.txt @@ -0,0 +1,28 @@ +# 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 + +cmake_minimum_required(VERSION 3.24) +project(mqt-core-qdmi-imported-device-test LANGUAGES CXX) + +if(NOT MQT_CORE_QDMI_DEVICE_TARGETS OR NOT MQT_CORE_QDMI_HELPER) + message(FATAL_ERROR "Device targets and the QDMI helper are required") +endif() + +include("${MQT_CORE_QDMI_DEVICE_TARGETS}") +include("${MQT_CORE_QDMI_HELPER}") + +set(device mqt-core-qdmi-metadata-device) +get_target_property(device_id ${device} QDMI_DEVICE_ID) +get_target_property(device_prefix ${device} QDMI_DEVICE_PREFIX) +if(NOT device_id STREQUAL "test.metadata-only" OR NOT device_prefix STREQUAL "TEST_METADATA") + message(FATAL_ERROR "Exported QDMI device metadata was not preserved") +endif() + +file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/main.cpp" "int main() { return 0; }\n") +add_executable(imported-device-consumer "${CMAKE_CURRENT_BINARY_DIR}/main.cpp") +mqt_copy_qdmi_runtime(imported-device-consumer ${device}) diff --git a/test/qdmi/driver/metadata_device.cpp b/test/qdmi/driver/metadata_device.cpp new file mode 100644 index 0000000000..6bc4f9e826 --- /dev/null +++ b/test/qdmi/driver/metadata_device.cpp @@ -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 + */ + +extern "C" void mqtCoreQdmiMetadataDeviceMarker() {} diff --git a/test/qdmi/driver/session_device.cpp b/test/qdmi/driver/session_device.cpp new file mode 100644 index 0000000000..5f91e81268 --- /dev/null +++ b/test/qdmi/driver/session_device.cpp @@ -0,0 +1,261 @@ +/* + * 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 + */ + +#include + +#include +#include +#include +#include +#include +#include + +struct QDMI_Child_Device_impl_d {}; + +struct QDMI_Device_Session_impl_d { + std::unordered_map parameters; + QDMI_Child_Device child = nullptr; + bool initialized = false; +}; + +struct QDMI_Device_Job_impl_d { + QDMI_Device_Session session = nullptr; +}; + +namespace { +[[nodiscard]] auto activeSessions() -> std::atomic_size_t& { + static std::atomic_size_t sessions = 0; + return sessions; +} + +[[nodiscard]] auto parameter(const QDMI_Device_Session_impl_d* const session, + const QDMI_Device_Session_Parameter key) + -> std::string { + if (const auto entry = session->parameters.find(key); + entry != session->parameters.end()) { + return entry->second; + } + return ""; +} + +[[nodiscard]] auto childDeviceHandle() -> QDMI_Child_Device { + static QDMI_Child_Device_impl_d child; + return &child; +} + +auto queryString(const std::string& result, const size_t size, void* value, + size_t* sizeRet) -> int { + const auto required = result.size() + 1; + if (sizeRet != nullptr) { + *sizeRet = required; + } + if (value == nullptr) { + return QDMI_SUCCESS; + } + if (size < required) { + return QDMI_ERROR_INVALIDARGUMENT; + } + std::memcpy(value, result.c_str(), required); + return QDMI_SUCCESS; +} +} // namespace + +// QDMI requires these exported C symbols to use the configured device prefix. +// NOLINTBEGIN(readability-identifier-naming) +extern "C" int TEST_SESSION_QDMI_device_initialize() { return QDMI_SUCCESS; } + +extern "C" int TEST_SESSION_QDMI_device_finalize() { return QDMI_SUCCESS; } + +extern "C" int +TEST_SESSION_QDMI_device_session_alloc(QDMI_Device_Session* session) { + if (session == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + // The QDMI C API transfers this allocation through an opaque raw handle. + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + *session = new (std::nothrow) QDMI_Device_Session_impl_d; + if (*session == nullptr) { + return QDMI_ERROR_OUTOFMEM; + } + ++activeSessions(); + return QDMI_SUCCESS; +} + +extern "C" int TEST_SESSION_QDMI_device_session_set_parameter( + QDMI_Device_Session session, const QDMI_Device_Session_Parameter param, + const size_t size, const void* value) { + if (session == nullptr || (value != nullptr && size == 0)) { + return QDMI_ERROR_INVALIDARGUMENT; + } + if (session->initialized) { + return QDMI_ERROR_BADSTATE; + } + if (param == QDMI_DEVICE_SESSION_PARAMETER_CHILDDEVICE) { + if (value == nullptr || size != sizeof(QDMI_Child_Device)) { + return QDMI_ERROR_INVALIDARGUMENT; + } + QDMI_Child_Device child = nullptr; + std::memcpy(static_cast(&child), value, sizeof(QDMI_Child_Device)); + if (child != childDeviceHandle()) { + return QDMI_ERROR_INVALIDARGUMENT; + } + session->child = child; + return QDMI_SUCCESS; + } + if (value != nullptr) { + session->parameters[param] = static_cast(value); + } + return QDMI_SUCCESS; +} + +extern "C" int +TEST_SESSION_QDMI_device_session_init(QDMI_Device_Session session) { + if (session == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + if (session->initialized) { + return QDMI_ERROR_BADSTATE; + } + session->initialized = true; + return QDMI_SUCCESS; +} + +extern "C" void +TEST_SESSION_QDMI_device_session_free(QDMI_Device_Session session) { + if (session == nullptr) { + return; + } + --activeSessions(); + // This releases the opaque handle allocated by device_session_alloc. + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + delete session; +} + +extern "C" int TEST_SESSION_QDMI_device_session_query_device_property( + QDMI_Device_Session session, const QDMI_Device_Property prop, + const size_t size, void* value, size_t* sizeRet) { + if (session == nullptr || !session->initialized) { + return QDMI_ERROR_BADSTATE; + } + if (prop == QDMI_DEVICE_PROPERTY_CHILDDEVICES) { + if (session->child != nullptr || + parameter(session, QDMI_DEVICE_SESSION_PARAMETER_CUSTOM5) != + "with-child") { + return QDMI_ERROR_NOTSUPPORTED; + } + constexpr auto required = sizeof(QDMI_Child_Device); + if (sizeRet != nullptr) { + *sizeRet = required; + } + if (value == nullptr) { + return QDMI_SUCCESS; + } + if (size < required) { + return QDMI_ERROR_INVALIDARGUMENT; + } + auto* const child = childDeviceHandle(); + std::memcpy(value, static_cast(&child), + sizeof(QDMI_Child_Device)); + return QDMI_SUCCESS; + } + if (prop != QDMI_DEVICE_PROPERTY_NAME) { + return QDMI_ERROR_NOTSUPPORTED; + } + if (session->child != nullptr) { + return queryString("child;active=" + + std::to_string(activeSessions().load()), + size, value, sizeRet); + } + const auto name = + "base=" + parameter(session, QDMI_DEVICE_SESSION_PARAMETER_BASEURL) + + ";token=" + parameter(session, QDMI_DEVICE_SESSION_PARAMETER_TOKEN) + + ";custom1=" + parameter(session, QDMI_DEVICE_SESSION_PARAMETER_CUSTOM1) + + ";custom2=" + parameter(session, QDMI_DEVICE_SESSION_PARAMETER_CUSTOM2) + + ";active=" + std::to_string(activeSessions().load()); + return queryString(name, size, value, sizeRet); +} + +extern "C" int TEST_SESSION_QDMI_device_session_query_site_property( + QDMI_Device_Session /*session*/, QDMI_Site /*site*/, + QDMI_Site_Property /*property*/, size_t /*size*/, void* /*value*/, + size_t* /*sizeRet*/) { + return QDMI_ERROR_NOTSUPPORTED; +} + +extern "C" int TEST_SESSION_QDMI_device_session_query_operation_property( + QDMI_Device_Session /*session*/, QDMI_Operation /*operation*/, + size_t /*numSites*/, const QDMI_Site* /*sites*/, size_t /*numParams*/, + const double* /*params*/, QDMI_Operation_Property /*property*/, + size_t /*size*/, void* /*value*/, size_t* /*sizeRet*/) { + return QDMI_ERROR_NOTSUPPORTED; +} + +extern "C" int +TEST_SESSION_QDMI_device_session_create_device_job(QDMI_Device_Session session, + QDMI_Device_Job* job) { + if (session == nullptr || !session->initialized || job == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + // The QDMI C API transfers this allocation through an opaque raw handle. + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + *job = new (std::nothrow) QDMI_Device_Job_impl_d{session}; + return *job == nullptr ? QDMI_ERROR_OUTOFMEM : QDMI_SUCCESS; +} + +extern "C" int TEST_SESSION_QDMI_device_job_set_parameter( + QDMI_Device_Job job, QDMI_Device_Job_Parameter /*parameter*/, + size_t /*size*/, const void* /*value*/) { + return job == nullptr ? QDMI_ERROR_INVALIDARGUMENT : QDMI_SUCCESS; +} + +extern "C" int TEST_SESSION_QDMI_device_job_query_property( + QDMI_Device_Job job, const QDMI_Device_Job_Property prop, const size_t size, + void* value, size_t* sizeRet) { + if (job == nullptr || job->session == nullptr || + prop != QDMI_DEVICE_JOB_PROPERTY_ID) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return queryString("session-job", size, value, sizeRet); +} + +extern "C" int TEST_SESSION_QDMI_device_job_submit(QDMI_Device_Job job) { + if (job == nullptr || job->session == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return QDMI_SUCCESS; +} + +extern "C" int TEST_SESSION_QDMI_device_job_cancel(QDMI_Device_Job /*job*/) { + return QDMI_ERROR_NOTSUPPORTED; +} + +extern "C" int TEST_SESSION_QDMI_device_job_check(QDMI_Device_Job /*job*/, + QDMI_Job_Status* /*status*/) { + return QDMI_ERROR_NOTSUPPORTED; +} + +extern "C" int TEST_SESSION_QDMI_device_job_wait(QDMI_Device_Job /*job*/, + size_t /*timeout*/) { + return QDMI_ERROR_NOTSUPPORTED; +} + +extern "C" int TEST_SESSION_QDMI_device_job_get_results( + QDMI_Device_Job /*job*/, QDMI_Job_Result /*result*/, size_t /*size*/, + void* /*value*/, size_t* /*sizeRet*/) { + return QDMI_ERROR_NOTSUPPORTED; +} + +extern "C" void TEST_SESSION_QDMI_device_job_free(QDMI_Device_Job job) { + // This releases the opaque handle allocated by + // device_session_create_device_job. + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + delete job; +} +// NOLINTEND(readability-identifier-naming) diff --git a/test/qdmi/driver/test_driver.cpp b/test/qdmi/driver/test_driver.cpp index 9c3737fdf6..06aeb5c579 100644 --- a/test/qdmi/driver/test_driver.cpp +++ b/test/qdmi/driver/test_driver.cpp @@ -20,8 +20,13 @@ #include #include #include +#include #include +#include +#include +#include #include +#include #include #include #include @@ -54,6 +59,25 @@ namespace qc { namespace { +struct ConfiguredDriverEnvironment { + ConfiguredDriverEnvironment() noexcept { +#ifdef _WIN32 + if (_putenv_s("MQT_CORE_QDMI_CONFIG_FILE", + MQT_CORE_QDMI_TEST_CONFIG_FILE) != 0) { +#else + // POSIX exposes setenv through , but include-cleaner does not + // associate the global declaration with that C++ header. + // NOLINTNEXTLINE(misc-include-cleaner) + if (setenv("MQT_CORE_QDMI_CONFIG_FILE", MQT_CORE_QDMI_TEST_CONFIG_FILE, + 1) != 0) { +#endif + std::abort(); + } + } +}; + +const ConfiguredDriverEnvironment CONFIGURED_DRIVER_ENVIRONMENT; + class ChildDeviceLibrary final : public qdmi::DeviceLibrary { struct Child { size_t id; @@ -229,6 +253,18 @@ class ChildDeviceLibrary final : public qdmi::DeviceLibrary { return name; } +[[nodiscard]] auto openTestDevice(const std::string& library, + const std::string& prefix, + const qdmi::DeviceSessionConfig& session = {}) + -> QDMI_Device { + static size_t nextId = 0; + auto& driver = qdmi::Driver::get(); + const auto id = "test.runtime." + std::to_string(nextId++); + driver.registerDevice( + {.id = id, .library = library, .prefix = prefix, .session = session}); + return driver.open(id); +} + class DriverTest : public testing::TestWithParam { protected: QDMI_Session session = nullptr; @@ -786,6 +822,21 @@ TEST_P(DriverTest, QueryNeedsCalibration) { constexpr std::array DEVICES{"MQT NA Default QDMI Device", "MQT Core DDSIM QDMI Device", "MQT SC Default QDMI Device"}; + +namespace { +void registerSessionTestDevice() { + qdmi::DeviceSessionConfig sessionConfig; + sessionConfig.baseUrl = "registered-base"; + sessionConfig.token = "registered-token"; + sessionConfig.custom1 = "registered-custom"; + static_cast(qdmi::Driver::get().registerDeviceIfAbsent( + {.id = "test.session-overrides", + .library = MQT_CORE_QDMI_SESSION_DEVICE, + .prefix = "TEST_SESSION", + .session = std::move(sessionConfig)})); +} +} // namespace + // Instantiate the test suite with different parameters INSTANTIATE_TEST_SUITE_P( // Custom instantiation name @@ -804,26 +855,256 @@ INSTANTIATE_TEST_SUITE_P( return name; }); -TEST(DeviceSessionConfigTest, AddDynamicDeviceLibraryWithBaseUrl) { +TEST(ConfiguredDriverTest, ConstructionRegistersWithoutOpeningDevices) { + const auto [library, prefix] = TEST_DEVICE_LIBRARIES.front(); + EXPECT_NO_THROW(qdmi::Driver::get().registerDevice({.id = "mqt.na.default", + .library = library, + .prefix = prefix, + .session = {}}, + true)); +} + +TEST(ConfiguredDriverTest, ExposesWorkingDefinitionsAndIsolatesFailures) { + QDMI_Session session = nullptr; + ASSERT_EQ(QDMI_session_alloc(&session), QDMI_SUCCESS); + ASSERT_EQ(QDMI_session_init(session), QDMI_SUCCESS); + + size_t size = 0; + ASSERT_EQ(QDMI_session_query_session_property( + session, QDMI_SESSION_PROPERTY_DEVICES, 0, nullptr, &size), + QDMI_SUCCESS); + ASSERT_EQ(size, 3 * sizeof(QDMI_Device)); + std::array devices{}; + ASSERT_EQ(QDMI_session_query_session_property( + session, QDMI_SESSION_PROPERTY_DEVICES, size, + static_cast(devices.data()), nullptr), + QDMI_SUCCESS); + + std::vector names; + std::ranges::transform(devices, std::back_inserter(names), queryName); + EXPECT_THAT(names, + testing::UnorderedElementsAre("MQT NA Default QDMI Device", + "MQT Core DDSIM QDMI Device", + "MQT SC Default QDMI Device")); + QDMI_session_free(session); +} + +TEST(DeviceRegistrationTest, ValidatesDuplicatesAndReplacement) { + auto& driver = qdmi::Driver::get(); + EXPECT_THROW(driver.registerDevice({}), std::invalid_argument); + EXPECT_THROW(driver.open("test.unknown"), std::out_of_range); + + const auto [library, prefix] = TEST_DEVICE_LIBRARIES.front(); + const qdmi::DeviceDefinition original{.id = "test.replaceable", + .library = library, + .prefix = prefix, + .session = {}}; + driver.registerDevice(original); + EXPECT_THROW(driver.registerDevice(original), std::invalid_argument); + + auto replacement = original; + replacement.session.custom1 = "replacement"; + EXPECT_NO_THROW(driver.registerDevice(replacement, true)); + auto* const opened = driver.open(original.id); + ASSERT_NE(opened, nullptr); + EXPECT_EQ(driver.open(original.id), opened); + EXPECT_THROW(driver.registerDevice(original, true), std::runtime_error); + EXPECT_NO_THROW(driver.registerDevice({.id = "test.upserted", + .library = library, + .prefix = prefix, + .session = {}}, + true)); + EXPECT_NE(driver.open("test.upserted"), nullptr); +} + +TEST(DeviceRegistrationTest, RegistersOnlyWhenIdIsAbsent) { + auto& driver = qdmi::Driver::get(); + const auto [library, prefix] = TEST_DEVICE_LIBRARIES.front(); + const qdmi::DeviceDefinition definition{.id = "test.insert-if-absent", + .library = library, + .prefix = prefix, + .session = {}}; + EXPECT_TRUE(driver.registerDeviceIfAbsent(definition)); + EXPECT_FALSE(driver.registerDeviceIfAbsent(definition)); + + auto invalidDuplicate = definition; + invalidDuplicate.library.clear(); + EXPECT_THROW(static_cast( + driver.registerDeviceIfAbsent(std::move(invalidDuplicate))), + std::invalid_argument); + + const qdmi::DeviceDefinition disabled{.id = "test.disabled", + .library = library, + .prefix = prefix, + .session = {}}; + EXPECT_FALSE(driver.registerDeviceIfAbsent(disabled)); + EXPECT_THROW(static_cast(driver.open(disabled.id)), std::runtime_error); + EXPECT_THROW(driver.registerDevice(disabled), std::invalid_argument); +} + +TEST(DeviceRegistrationTest, RegistrationDoesNotLoadLibraries) { + auto& driver = qdmi::Driver::get(); + driver.registerDevice({.id = "test.missing-library", + .library = "/nonexistent/device-library", + .prefix = "MISSING", + .session = {}}); + EXPECT_THROW(static_cast(driver.open("test.missing-library")), + std::runtime_error); +} + +TEST(DeviceRegistrationTest, SynthesizesManifestForMetadataOnlyTarget) { + std::ifstream manifest(MQT_CORE_QDMI_METADATA_MANIFEST); + ASSERT_TRUE(manifest); + const std::string contents{std::istreambuf_iterator(manifest), + std::istreambuf_iterator()}; + EXPECT_THAT(contents, testing::HasSubstr("\"id\": \"test.metadata-only\"")); + EXPECT_THAT(contents, testing::HasSubstr("\"prefix\": \"TEST_METADATA\"")); + EXPECT_THAT(contents, testing::HasSubstr("mqt-core-qdmi-metadata-device")); +} + +TEST(DeviceRegistrationTest, + FreshOverridesMergeValuesOwnTheirSessionAndStayOutOfCatalog) { + registerSessionTestDevice(); + + const auto clientCatalogSize = [] { + QDMI_Session session = nullptr; + if (QDMI_session_alloc(&session) != QDMI_SUCCESS || + QDMI_session_init(session) != QDMI_SUCCESS) { + throw std::runtime_error("Failed to create QDMI test session"); + } + size_t size = 0; + const auto status = QDMI_session_query_session_property( + session, QDMI_SESSION_PROPERTY_DEVICES, 0, nullptr, &size); + QDMI_session_free(session); + if (status != QDMI_SUCCESS) { + throw std::runtime_error("Failed to query QDMI device catalog"); + } + return size; + }; + + const auto catalogSizeBefore = clientCatalogSize(); + { + qdmi::DeviceSessionConfig overrides; + overrides.token = "override-token"; + overrides.custom2 = "override-custom"; + auto device = + fomac::Session::openDevice("test.session-overrides", overrides); + EXPECT_EQ(device.getName(), + "base=registered-base;token=override-token;custom1=" + "registered-custom;custom2=override-custom;active=1"); + EXPECT_EQ(clientCatalogSize(), catalogSizeBefore); + } + + qdmi::DeviceSessionConfig probeOverrides; + probeOverrides.token = "probe-token"; + const auto probe = + fomac::Session::openDevice("test.session-overrides", probeOverrides); + EXPECT_THAT(queryName(probe), testing::HasSubstr("active=1")); + EXPECT_EQ(clientCatalogSize(), catalogSizeBefore); +} + +TEST(DeviceRegistrationTest, FreshOpenCreatesDistinctSessions) { + registerSessionTestDevice(); + const auto first = fomac::Session::openDevice("test.session-overrides"); + const auto second = fomac::Session::openDevice("test.session-overrides"); + EXPECT_NE(first, second); +} + +TEST(DeviceRegistrationTest, FreshJobRetainsItsDeviceSession) { + registerSessionTestDevice(); + std::optional job; + { + auto device = fomac::Session::openDevice("test.session-overrides"); + job.emplace( + device.submitJob("OPENQASM 2.0;", QDMI_PROGRAM_FORMAT_QASM2, 1)); + } + + ASSERT_TRUE(job.has_value()); + EXPECT_EQ(job->getId(), "session-job"); + job.reset(); + + const auto probe = fomac::Session::openDevice("test.session-overrides"); + EXPECT_THAT(queryName(probe), testing::HasSubstr("active=1")); +} + +TEST(DeviceRegistrationTest, FreshChildDeviceRetainsItsRootSession) { + registerSessionTestDevice(); + std::optional child; + { + qdmi::DeviceSessionConfig overrides; + overrides.custom5 = "with-child"; + auto root = fomac::Session::openDevice("test.session-overrides", overrides); + auto children = root.getChildDevices(); + ASSERT_EQ(children.size(), 1); + child.emplace(std::move(children.front())); + } + + ASSERT_TRUE(child.has_value()); + EXPECT_EQ(child->getName(), "child;active=2"); + child.reset(); + + const auto probe = fomac::Session::openDevice("test.session-overrides"); + EXPECT_THAT(queryName(probe), testing::HasSubstr("active=1")); +} + +TEST(DeviceRegistrationTest, RuntimeRegistrationsStayOutOfClientCatalog) { + auto& driver = qdmi::Driver::get(); + QDMI_Session existingSession = nullptr; + ASSERT_EQ(QDMI_session_alloc(&existingSession), QDMI_SUCCESS); + ASSERT_EQ(QDMI_session_init(existingSession), QDMI_SUCCESS); + size_t originalSize = 0; + ASSERT_EQ(QDMI_session_query_session_property(existingSession, + QDMI_SESSION_PROPERTY_DEVICES, + 0, nullptr, &originalSize), + QDMI_SUCCESS); + + const auto [library, prefix] = TEST_DEVICE_LIBRARIES.front(); + driver.registerDevice({.id = "test.snapshot", + .library = library, + .prefix = prefix, + .session = {}}); + ASSERT_NE(driver.open("test.snapshot"), nullptr); + + size_t existingSize = 0; + EXPECT_EQ(QDMI_session_query_session_property(existingSession, + QDMI_SESSION_PROPERTY_DEVICES, + 0, nullptr, &existingSize), + QDMI_SUCCESS); + EXPECT_EQ(existingSize, originalSize); + + QDMI_Session newSession = nullptr; + ASSERT_EQ(QDMI_session_alloc(&newSession), QDMI_SUCCESS); + ASSERT_EQ(QDMI_session_init(newSession), QDMI_SUCCESS); + size_t newSize = 0; + EXPECT_EQ(QDMI_session_query_session_property(newSession, + QDMI_SESSION_PROPERTY_DEVICES, + 0, nullptr, &newSize), + QDMI_SUCCESS); + EXPECT_EQ(newSize, originalSize); + QDMI_session_free(newSession); + QDMI_session_free(existingSession); +} + +TEST(DeviceSessionConfigTest, OpenWithBaseUrl) { qdmi::DeviceSessionConfig config; config.baseUrl = "http://localhost:8080"; - for (const auto& [lib, prefix] : DYN_DEV_LIBS) { + for (const auto& [lib, prefix] : TEST_DEVICE_LIBRARIES) { EXPECT_NO_THROW( - { qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); }); + { static_cast(openTestDevice(lib, prefix, config)); }); } } -TEST(DeviceSessionConfigTest, AddDynamicDeviceLibraryWithCustomParameters) { +TEST(DeviceSessionConfigTest, OpenWithCustomParameters) { qdmi::DeviceSessionConfig config; config.custom1 = "RESONANCE_COCOS_V1"; config.custom2 = "test_value"; config.baseUrl = "http://localhost:9090"; - for (const auto& [lib, prefix] : DYN_DEV_LIBS) { + for (const auto& [lib, prefix] : TEST_DEVICE_LIBRARIES) { // Custom parameters may fail with validation errors or succeed/return false try { - qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); + static_cast(openTestDevice(lib, prefix, config)); SUCCEED() << "Library loaded or already loaded"; } catch (const std::runtime_error& e) { // Custom parameters may be rejected with INVALIDARGUMENT @@ -838,42 +1119,42 @@ TEST(DeviceSessionConfigTest, AddDynamicDeviceLibraryWithCustomParameters) { } } -TEST(DeviceSessionConfigTest, AddDynamicDeviceLibraryWithAuthToken) { +TEST(DeviceSessionConfigTest, OpenWithAuthToken) { qdmi::DeviceSessionConfig config; config.token = "test_token_123"; config.baseUrl = "https://api.example.com"; - for (const auto& [lib, prefix] : DYN_DEV_LIBS) { + for (const auto& [lib, prefix] : TEST_DEVICE_LIBRARIES) { EXPECT_NO_THROW( - { qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); }); + { static_cast(openTestDevice(lib, prefix, config)); }); } } -TEST(DeviceSessionConfigTest, AddDynamicDeviceLibraryWithAuthFile) { +TEST(DeviceSessionConfigTest, OpenWithAuthFile) { qdmi::DeviceSessionConfig config; config.authFile = "/nonexistent/auth.json"; - for (const auto& [lib, prefix] : DYN_DEV_LIBS) { + for (const auto& [lib, prefix] : TEST_DEVICE_LIBRARIES) { // This should not throw even with non-existent file because // if the auth file parameter is not supported, it's skipped EXPECT_NO_THROW( - { qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); }); + { static_cast(openTestDevice(lib, prefix, config)); }); } } -TEST(DeviceSessionConfigTest, AddDynamicDeviceLibraryWithUsernamePassword) { +TEST(DeviceSessionConfigTest, OpenWithUsernamePassword) { qdmi::DeviceSessionConfig config; config.authUrl = "https://auth.example.com"; config.username = "quantum_user"; config.password = "secret_password"; - for (const auto& [lib, prefix] : DYN_DEV_LIBS) { + for (const auto& [lib, prefix] : TEST_DEVICE_LIBRARIES) { EXPECT_NO_THROW( - { qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); }); + { static_cast(openTestDevice(lib, prefix, config)); }); } } -TEST(DeviceSessionConfigTest, AddDynamicDeviceLibraryWithAllParameters) { +TEST(DeviceSessionConfigTest, OpenWithAllParameters) { qdmi::DeviceSessionConfig config; config.baseUrl = "http://localhost:8080"; config.token = "test_token"; @@ -886,9 +1167,9 @@ TEST(DeviceSessionConfigTest, AddDynamicDeviceLibraryWithAllParameters) { config.custom4 = "value4"; config.custom5 = "value5"; - for (const auto& [lib, prefix] : DYN_DEV_LIBS) { + for (const auto& [lib, prefix] : TEST_DEVICE_LIBRARIES) { try { - qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); + static_cast(openTestDevice(lib, prefix, config)); SUCCEED() << "Library loaded or already loaded"; } catch (const std::runtime_error& e) { // Custom parameters may be rejected with INVALIDARGUMENT @@ -906,16 +1187,15 @@ TEST(DeviceSessionConfigTest, AddDynamicDeviceLibraryWithAllParameters) { TEST(DeviceSessionConfigTest, IdempotentLoadingWithDifferentConfigs) { // This test is explicitly not part of the fixture because this would // automatically load the default config and the respective libraries. - if constexpr (DYN_DEV_LIBS.empty()) { + if constexpr (TEST_DEVICE_LIBRARIES.empty()) { GTEST_SKIP() << "No dynamic device libraries to test"; } - auto& driver = qdmi::Driver::get(); - for (const auto& [lib, prefix] : DYN_DEV_LIBS) { + for (const auto& [lib, prefix] : TEST_DEVICE_LIBRARIES) { // Config 1: baseUrl { qdmi::DeviceSessionConfig config; config.baseUrl = "http://localhost:8080"; - EXPECT_NO_THROW(driver.addDynamicDeviceLibrary(lib, prefix, config);); + EXPECT_NO_THROW(static_cast(openTestDevice(lib, prefix, config));); } // Config 2: different baseUrl and custom parameters @@ -923,7 +1203,7 @@ TEST(DeviceSessionConfigTest, IdempotentLoadingWithDifferentConfigs) { qdmi::DeviceSessionConfig config; config.baseUrl = "http://localhost:9090"; config.custom1 = "API_V2"; - EXPECT_NO_THROW(driver.addDynamicDeviceLibrary(lib, prefix, config);); + EXPECT_NO_THROW(static_cast(openTestDevice(lib, prefix, config));); } // Config 3: authentication parameters @@ -931,24 +1211,38 @@ TEST(DeviceSessionConfigTest, IdempotentLoadingWithDifferentConfigs) { qdmi::DeviceSessionConfig config; config.token = "new_token"; config.authUrl = "https://auth.example.com"; - EXPECT_NO_THROW(driver.addDynamicDeviceLibrary(lib, prefix, config);); + EXPECT_NO_THROW(static_cast(openTestDevice(lib, prefix, config));); } } } -TEST(DynamicDeviceLibraryTest, addDynamicDeviceLibraryReturnsDevice) { - // Test that addDynamicDeviceLibrary returns a valid device pointer - if constexpr (DYN_DEV_LIBS.empty()) { +TEST(DynamicDeviceLibraryTest, ReusesLibraryWithFreshDeviceSessions) { + const auto [library, prefix] = TEST_DEVICE_LIBRARIES.front(); + qdmi::DeviceSessionConfig firstSessionConfig; + firstSessionConfig.custom1 = "first-session"; + auto* const first = openTestDevice(library, prefix, firstSessionConfig); + const auto equivalentLibrary = std::filesystem::path(library).parent_path() / + "." / + std::filesystem::path(library).filename(); + qdmi::DeviceSessionConfig secondSessionConfig; + secondSessionConfig.custom1 = "second-session"; + auto* const second = + openTestDevice(equivalentLibrary.string(), prefix, secondSessionConfig); + + ASSERT_NE(first, second); + EXPECT_EQ(&first->getLibrary(), &second->getLibrary()); +} + +TEST(DynamicDeviceLibraryTest, OpenReturnsDevice) { + if constexpr (TEST_DEVICE_LIBRARIES.empty()) { GTEST_SKIP() << "No dynamic device libraries configured for testing."; } - auto& driver = qdmi::Driver::get(); - for (const auto& [lib, prefix] : DYN_DEV_LIBS) { + for (const auto& [lib, prefix] : TEST_DEVICE_LIBRARIES) { const qdmi::DeviceSessionConfig config; QDMI_Device device = nullptr; - ASSERT_NO_THROW( - { device = driver.addDynamicDeviceLibrary(lib, prefix, config); }); + ASSERT_NO_THROW({ device = openTestDevice(lib, prefix, config); }); ASSERT_NE(device, nullptr) - << "addDynamicDeviceLibrary should return a non-null device pointer"; + << "open should return a non-null device pointer"; // Verify the device is valid by querying its name size_t size = 0; diff --git a/test/qdmi/registry/CMakeLists.txt b/test/qdmi/registry/CMakeLists.txt new file mode 100644 index 0000000000..918784ccd0 --- /dev/null +++ b/test/qdmi/registry/CMakeLists.txt @@ -0,0 +1,24 @@ +# 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 + +set(TARGET_NAME mqt-core-qdmi-registry-test) + +if(TARGET MQT::CoreQDMIDriver) + package_add_test(${TARGET_NAME} MQT::CoreQDMIDriver test_device_registry.cpp) + target_include_directories(${TARGET_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/src/qdmi/driver) + target_compile_definitions( + ${TARGET_NAME} + PRIVATE + MQT_CORE_QDMI_HAS_ALL_BUILTIN_DEVICES=$,$,$> + ) + if(BUILD_MQT_CORE_QDMI_DDSIM_DEVICE + OR BUILD_MQT_CORE_QDMI_NA_DEVICE + OR BUILD_MQT_CORE_QDMI_SC_DEVICE) + mqt_copy_qdmi_runtime(${TARGET_NAME}) + endif() +endif() diff --git a/test/qdmi/registry/test_device_registry.cpp b/test/qdmi/registry/test_device_registry.cpp new file mode 100644 index 0000000000..222b7085e3 --- /dev/null +++ b/test/qdmi/registry/test_device_registry.cpp @@ -0,0 +1,456 @@ +/* + * 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 + */ + +#include "DeviceRegistry.hpp" +#include "qdmi/driver/Driver.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +class TemporaryDirectory { +public: + TemporaryDirectory() { + path_ = std::filesystem::temp_directory_path() / + ("mqt-core-qdmi-registry-test-" + + std::to_string(std::random_device{}())); + std::filesystem::remove_all(path_); + std::filesystem::create_directories(path_); + } + + ~TemporaryDirectory() { std::filesystem::remove_all(path_); } + + [[nodiscard]] const std::filesystem::path& path() const { return path_; } + + [[nodiscard]] std::filesystem::path + write(const std::filesystem::path& relative, + const std::string& contents) const { + const auto path = path_ / relative; + std::filesystem::create_directories(path.parent_path()); + std::ofstream output(path); + output << contents; + return path; + } + +private: + std::filesystem::path path_; +}; + +class ScopedEnvironmentVariable { +public: + ScopedEnvironmentVariable(std::string name, const std::string& value) + : name_(std::move(name)) { + if (const auto* previous = std::getenv(name_.c_str()); + previous != nullptr) { + previous_ = previous; + } + set(value); + } + + ~ScopedEnvironmentVariable() { + if (previous_) { + static_cast(setWithoutChecking(*previous_)); + } else { +#ifdef _WIN32 + static_cast(_putenv_s(name_.c_str(), "")); +#else + // NOLINTNEXTLINE(misc-include-cleaner) + static_cast(unsetenv(name_.c_str())); +#endif + } + } + + ScopedEnvironmentVariable(const ScopedEnvironmentVariable&) = delete; + ScopedEnvironmentVariable& + operator=(const ScopedEnvironmentVariable&) = delete; + ScopedEnvironmentVariable(ScopedEnvironmentVariable&&) = delete; + ScopedEnvironmentVariable& operator=(ScopedEnvironmentVariable&&) = delete; + +private: + void set(const std::string& value) const { + if (!setWithoutChecking(value)) { + throw std::runtime_error("Failed to set environment variable " + name_); + } + } + + [[nodiscard]] bool setWithoutChecking(const std::string& value) const { +#ifdef _WIN32 + return _putenv_s(name_.c_str(), value.c_str()) == 0; +#else + // NOLINTNEXTLINE(misc-include-cleaner) + return setenv(name_.c_str(), value.c_str(), 1) == 0; +#endif + } + + std::string name_; + std::optional previous_; +}; + +class ScopedCurrentPath { +public: + explicit ScopedCurrentPath(const std::filesystem::path& path) + : previous_(std::filesystem::current_path()) { + std::filesystem::current_path(path); + } + ~ScopedCurrentPath() { std::filesystem::current_path(previous_); } + + ScopedCurrentPath(const ScopedCurrentPath&) = delete; + ScopedCurrentPath& operator=(const ScopedCurrentPath&) = delete; + ScopedCurrentPath(ScopedCurrentPath&&) = delete; + ScopedCurrentPath& operator=(ScopedCurrentPath&&) = delete; + +private: + std::filesystem::path previous_; +}; + +[[nodiscard]] auto findDefinition(const qdmi::detail::DeviceRegistry& registry, + const std::string_view id) + -> const qdmi::DeviceDefinition* { + const auto& definitions = registry.definitions(); + const auto found = + std::ranges::find(definitions, id, &qdmi::DeviceDefinition::id); + return found == definitions.end() ? nullptr : &*found; +} + +[[nodiscard]] auto emptyConfig(const TemporaryDirectory& directory) + -> ScopedEnvironmentVariable { + const auto path = + directory.write("empty.json", R"({"schema-version": 1, "qdmi": {}})"); + return {"MQT_CORE_QDMI_CONFIG_FILE", path.string()}; +} + +TEST(DeviceRegistry, ParsesEnvironmentConfigurationWithoutLoadingLibraries) { + const TemporaryDirectory directory; + const ScopedCurrentPath currentPath(directory.path()); + const auto configFile = emptyConfig(directory); + const ScopedEnvironmentVariable configJson("MQT_CORE_QDMI_CONFIG_JSON", R"({ + "schema-version": 1, + "qdmi": {"devices": [{ + "id": "example.device", "library": "libexample.so", "prefix": "EXAMPLE", + "session": {"auth-file": "secret.json", "custom1": "value"} + }]} + })"); + + const qdmi::detail::DeviceRegistry registry; + const auto* definition = findDefinition(registry, "example.device"); + ASSERT_NE(definition, nullptr); + EXPECT_EQ(std::filesystem::weakly_canonical(definition->library), + std::filesystem::weakly_canonical(directory.path()) / + "libexample.so"); + ASSERT_TRUE(definition->session.authFile.has_value()); + EXPECT_EQ(std::filesystem::weakly_canonical(*definition->session.authFile), + std::filesystem::weakly_canonical(directory.path()) / + "secret.json"); + EXPECT_EQ(definition->session.custom1, "value"); +} + +TEST(DeviceRegistry, RejectsDuplicateIdsAndUnsupportedKeys) { + const TemporaryDirectory directory; + const auto configFile = emptyConfig(directory); + { + const ScopedEnvironmentVariable configJson("MQT_CORE_QDMI_CONFIG_JSON", R"({ + "schema-version": 1, + "qdmi": {"devices": [ + {"id": "duplicate", "library": "one", "prefix": "ONE"}, + {"id": "duplicate", "library": "two", "prefix": "TWO"} + ]} + })"); + EXPECT_THROW(static_cast(qdmi::detail::DeviceRegistry()), + std::invalid_argument); + } + { + const ScopedEnvironmentVariable configJson("MQT_CORE_QDMI_CONFIG_JSON", R"({ + "schema-version": 1, + "qdmi": {"device-config": {"model": "unused"}} + })"); + EXPECT_THROW(static_cast(qdmi::detail::DeviceRegistry()), + std::invalid_argument); + } +} + +TEST(DeviceRegistry, MergesEnvironmentJsonOverExplicitFile) { + const TemporaryDirectory directory; + const auto path = directory.write("environment.json", R"({ + "schema-version": 1, + "qdmi": {"devices": [{ + "id": "environment", "library": "file.so", "prefix": "FILE", + "session": {"custom1": "from-file", "custom2": "preserved"} + }]} + })"); + const ScopedEnvironmentVariable configFile("MQT_CORE_QDMI_CONFIG_FILE", + path.string()); + const ScopedEnvironmentVariable configJson("MQT_CORE_QDMI_CONFIG_JSON", R"({ + "schema-version": 1, + "qdmi": {"devices": [{ + "id": "environment", "session": {"custom1": "from-json"} + }]} + })"); + + const qdmi::detail::DeviceRegistry registry; + const auto* definition = findDefinition(registry, "environment"); + ASSERT_NE(definition, nullptr); + EXPECT_EQ(definition->library, directory.path() / "file.so"); + EXPECT_EQ(definition->prefix, "FILE"); + EXPECT_EQ(definition->session.custom1, "from-json"); + EXPECT_EQ(definition->session.custom2, "preserved"); +} + +TEST(DeviceRegistry, DisabledEnvironmentEntryMasksExplicitDefinition) { + const TemporaryDirectory directory; + const auto path = directory.write("complete.json", R"({ + "schema-version": 1, + "qdmi": {"devices": [ + {"id": "masked", "library": "device.so", "prefix": "DEVICE"} + ]} + })"); + const ScopedEnvironmentVariable configFile("MQT_CORE_QDMI_CONFIG_FILE", + path.string()); + const ScopedEnvironmentVariable configJson("MQT_CORE_QDMI_CONFIG_JSON", R"({ + "schema-version": 1, + "qdmi": {"devices": [{"id": "masked", "enabled": false}]} + })"); + + const qdmi::detail::DeviceRegistry registry; + EXPECT_EQ(findDefinition(registry, "masked"), nullptr); + ASSERT_EQ(registry.disabledIds().size(), 1); + EXPECT_EQ(registry.disabledIds().front(), "masked"); +} + +TEST(DeviceRegistry, HigherPrecedenceDefinitionMustExplicitlyReenableDevice) { + const TemporaryDirectory directory; + const auto path = directory.write("disabled.json", R"({ + "schema-version": 1, + "qdmi": {"devices": [{"id": "masked", "enabled": false}]} + })"); + const ScopedEnvironmentVariable configFile("MQT_CORE_QDMI_CONFIG_FILE", + path.string()); + + { + const ScopedEnvironmentVariable configJson("MQT_CORE_QDMI_CONFIG_JSON", R"({ + "schema-version": 1, + "qdmi": {"devices": [{ + "id": "masked", "library": "device.so", "prefix": "DEVICE" + }]} + })"); + const qdmi::detail::DeviceRegistry registry; + EXPECT_EQ(findDefinition(registry, "masked"), nullptr); + ASSERT_EQ(registry.disabledIds().size(), 1); + EXPECT_EQ(registry.disabledIds().front(), "masked"); + } + + { + const ScopedEnvironmentVariable configJson("MQT_CORE_QDMI_CONFIG_JSON", R"({ + "schema-version": 1, + "qdmi": {"devices": [{ + "id": "masked", "library": "device.so", "prefix": "DEVICE", + "enabled": true + }]} + })"); + const qdmi::detail::DeviceRegistry registry; + const auto* definition = findDefinition(registry, "masked"); + ASSERT_NE(definition, nullptr); + EXPECT_EQ(definition->library, + std::filesystem::current_path() / "device.so"); + EXPECT_EQ(definition->prefix, "DEVICE"); + EXPECT_TRUE(registry.disabledIds().empty()); + } +} + +TEST(DeviceRegistry, ResolvesRelativeConfigurationPathsBeforeCwdChanges) { + const TemporaryDirectory directory; + static_cast(directory.write("config/device.json", R"({ + "schema-version": 1, + "qdmi": {"devices": [{ + "id": "relative", "library": "libdevice.so", "prefix": "RELATIVE", + "session": {"auth-file": "auth.json"} + }]} + })")); + + std::filesystem::path library; + std::filesystem::path authFile; + { + const ScopedCurrentPath currentPath(directory.path()); + const ScopedEnvironmentVariable configFile("MQT_CORE_QDMI_CONFIG_FILE", + "config/device.json"); + const ScopedEnvironmentVariable configJson("MQT_CORE_QDMI_CONFIG_JSON", ""); + const qdmi::detail::DeviceRegistry registry; + const auto* definition = findDefinition(registry, "relative"); + ASSERT_NE(definition, nullptr); + library = definition->library; + ASSERT_TRUE(definition->session.authFile.has_value()); + authFile = *definition->session.authFile; + } + + EXPECT_TRUE(library.is_absolute()); + EXPECT_TRUE(authFile.is_absolute()); + EXPECT_EQ(std::filesystem::weakly_canonical(library), + std::filesystem::weakly_canonical(directory.path()) / "config" / + "libdevice.so"); + EXPECT_EQ(std::filesystem::weakly_canonical(authFile), + std::filesystem::weakly_canonical(directory.path()) / "config" / + "auth.json"); +} + +#if MQT_CORE_QDMI_HAS_ALL_BUILTIN_DEVICES +TEST(DeviceRegistry, DiscoversGeneratedBuildTreeManifests) { + const TemporaryDirectory directory; + const auto configFile = emptyConfig(directory); + const ScopedEnvironmentVariable configJson("MQT_CORE_QDMI_CONFIG_JSON", ""); + + const qdmi::detail::DeviceRegistry registry; + ASSERT_EQ(registry.definitions().size(), 3); + EXPECT_EQ(registry.definitions().at(0).id, "mqt.ddsim.default"); + EXPECT_EQ(registry.definitions().at(1).id, "mqt.na.default"); + EXPECT_EQ(registry.definitions().at(2).id, "mqt.sc.default"); + for (const auto& definition : registry.definitions()) { + EXPECT_TRUE(std::filesystem::is_regular_file(definition.library)); + } +} +#endif + +TEST(DeviceRegistry, ReadsProjectConfigurationFromPyprojectToml) { + const TemporaryDirectory directory; + static_cast(directory.write("pyproject.toml", R"( + [tool.qdmi] + devices = [{ id = "toml", library = "device.so", prefix = "TOML" }] + )")); + const ScopedCurrentPath currentPath(directory.path()); + const ScopedEnvironmentVariable configFile("MQT_CORE_QDMI_CONFIG_FILE", ""); + const ScopedEnvironmentVariable configJson("MQT_CORE_QDMI_CONFIG_JSON", ""); +#ifdef _WIN32 + const ScopedEnvironmentVariable userConfig("APPDATA", + directory.path().string()); +#else + const ScopedEnvironmentVariable userConfig("XDG_CONFIG_HOME", + directory.path().string()); +#endif + + const qdmi::detail::DeviceRegistry registry; + const auto* definition = findDefinition(registry, "toml"); + ASSERT_NE(definition, nullptr); + EXPECT_EQ(std::filesystem::weakly_canonical(definition->library), + std::filesystem::weakly_canonical(directory.path()) / "device.so"); +} + +TEST(DeviceRegistry, DedicatedProjectFileWinsOverPyproject) { + const TemporaryDirectory directory; + static_cast(directory.write("pyproject.toml", R"( + [tool.qdmi] + devices = [{ id = "toml", library = "toml.so", prefix = "TOML" }] + )")); + static_cast(directory.write("qdmi.json", R"({ + "schema-version": 1, + "qdmi": {"devices": [ + {"id": "json", "library": "json.so", "prefix": "JSON"} + ]} + })")); + const ScopedCurrentPath currentPath(directory.path()); + const ScopedEnvironmentVariable configFile("MQT_CORE_QDMI_CONFIG_FILE", ""); + const ScopedEnvironmentVariable configJson("MQT_CORE_QDMI_CONFIG_JSON", ""); + + const qdmi::detail::DeviceRegistry registry; + EXPECT_NE(findDefinition(registry, "json"), nullptr); + EXPECT_EQ(findDefinition(registry, "toml"), nullptr); +} + +TEST(DeviceRegistry, MergesProjectConfigurationOverUserConfiguration) { + const TemporaryDirectory directory; + static_cast(directory.write("user/mqt-core/qdmi.json", R"({ + "schema-version": 1, + "qdmi": {"devices": [{ + "id": "layered", "library": "user.so", "prefix": "USER", + "session": {"custom1": "user-default"} + }]} + })")); + static_cast(directory.write("project/qdmi.json", R"({ + "schema-version": 1, + "qdmi": {"devices": [{"id": "layered", "prefix": "PROJECT"}]} + })")); + const ScopedCurrentPath currentPath(directory.path() / "project"); + const ScopedEnvironmentVariable configFile("MQT_CORE_QDMI_CONFIG_FILE", ""); + const ScopedEnvironmentVariable configJson("MQT_CORE_QDMI_CONFIG_JSON", ""); +#ifdef _WIN32 + const ScopedEnvironmentVariable programData("PROGRAMDATA", + directory.path().string()); + const ScopedEnvironmentVariable userConfig( + "APPDATA", (directory.path() / "user").string()); +#else + const ScopedEnvironmentVariable userConfig( + "XDG_CONFIG_HOME", (directory.path() / "user").string()); +#endif + + const qdmi::detail::DeviceRegistry registry; + const auto* definition = findDefinition(registry, "layered"); + ASSERT_NE(definition, nullptr); + EXPECT_EQ(definition->library, + directory.path() / "user" / "mqt-core" / "user.so"); + EXPECT_EQ(definition->prefix, "PROJECT"); + EXPECT_EQ(definition->session.custom1, "user-default"); +} + +TEST(DeviceRegistry, ReportsInvalidDocumentsAndDefinitionTypes) { + const TemporaryDirectory directory; + const auto configFile = emptyConfig(directory); + for ( + const auto* document : { + R"({})", + R"({"schema-version": 2, "qdmi": {}})", + R"({"schema-version": 1, "qdmi": {"devices": {}}})", + R"({"schema-version": 1, "qdmi": {"devices": [{"id": 4}]}})", + R"({"schema-version": 1, "qdmi": {"devices": [{"id": "invalid", "library": "device", "prefix": "P", "enabled": "yes"}]}})", + R"({"schema-version": 1, "qdmi": {"devices": [{"id": "invalid", "library": "device", "prefix": "P", "session": {"token": 42}}]}})", + R"({"schema-version": 1, "qdmi": {"devices": [{"id": "missing", "prefix": "P"}]}})", + R"({"schema-version": 1, "qdmi": {"devices": [{"id": "unknown", "library": "device", "prefix": "P", "unexpected": true}]}})", + }) { + const ScopedEnvironmentVariable configJson("MQT_CORE_QDMI_CONFIG_JSON", + document); + EXPECT_THROW(static_cast(qdmi::detail::DeviceRegistry()), + std::invalid_argument); + } +} + +TEST(DeviceRegistry, ReportsInvalidExplicitJsonAndToml) { + const TemporaryDirectory directory; + { + const ScopedEnvironmentVariable configFile( + "MQT_CORE_QDMI_CONFIG_FILE", + (directory.path() / "missing.json").string()); + EXPECT_THROW(static_cast(qdmi::detail::DeviceRegistry()), + std::runtime_error); + } + { + const auto invalid = directory.write("invalid.json", "{"); + const ScopedEnvironmentVariable configFile("MQT_CORE_QDMI_CONFIG_FILE", + invalid.string()); + EXPECT_THROW(static_cast(qdmi::detail::DeviceRegistry()), + std::invalid_argument); + } + { + static_cast(directory.write("pyproject.toml", "[tool.qdmi\n")); + const ScopedCurrentPath currentPath(directory.path()); + const ScopedEnvironmentVariable configFile("MQT_CORE_QDMI_CONFIG_FILE", ""); + EXPECT_THROW(static_cast(qdmi::detail::DeviceRegistry()), + std::invalid_argument); + } +} + +} // namespace diff --git a/test/qir/CMakeLists.txt b/test/qir/CMakeLists.txt index d5fce427ab..434588601e 100644 --- a/test/qir/CMakeLists.txt +++ b/test/qir/CMakeLists.txt @@ -6,5 +6,7 @@ # # Licensed under the MIT License +add_subdirectory(helpers) +add_subdirectory(jit) add_subdirectory(runtime) add_subdirectory(runner) diff --git a/test/qir/helpers/CMakeLists.txt b/test/qir/helpers/CMakeLists.txt new file mode 100644 index 0000000000..bd57bd8850 --- /dev/null +++ b/test/qir/helpers/CMakeLists.txt @@ -0,0 +1,13 @@ +# 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 + +add_library(mqt-core-qir-test-utils STATIC test_utils.cpp) +target_link_libraries(mqt-core-qir-test-utils PUBLIC gtest) +target_include_directories(mqt-core-qir-test-utils PUBLIC ${PROJECT_SOURCE_DIR}/test) +target_compile_definitions(mqt-core-qir-test-utils + PRIVATE QIR_FILES_DIR="${PROJECT_SOURCE_DIR}/test/circuits") diff --git a/test/qir/helpers/test_utils.cpp b/test/qir/helpers/test_utils.cpp new file mode 100644 index 0000000000..f6c6fa7da8 --- /dev/null +++ b/test/qir/helpers/test_utils.cpp @@ -0,0 +1,31 @@ +/* + * 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 + */ + +#include "qir/helpers/test_utils.hpp" + +#include + +#include +#include +#include +#include +#include + +namespace qir_test { + +std::string getProgram(const std::string_view file) { + const std::filesystem::path path = + std::filesystem::path(QIR_FILES_DIR) / file; + std::ifstream ifs(path); + EXPECT_TRUE(ifs.is_open()) << "Failed to open " << path.string(); + return {std::istreambuf_iterator{ifs}, {}}; +} + +} // namespace qir_test diff --git a/test/qir/helpers/test_utils.hpp b/test/qir/helpers/test_utils.hpp new file mode 100644 index 0000000000..e6d5c0777a --- /dev/null +++ b/test/qir/helpers/test_utils.hpp @@ -0,0 +1,25 @@ +/* + * 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 + */ + +/* + * Test utilities for QIR-based tests. + */ +#pragma once + +#include +#include + +namespace qir_test { + +/// Read a QIR source file from the test circuits directory and +/// return its contents as a string. +std::string getProgram(std::string_view file); + +} // namespace qir_test diff --git a/test/qir/jit/CMakeLists.txt b/test/qir/jit/CMakeLists.txt new file mode 100644 index 0000000000..8552a167d0 --- /dev/null +++ b/test/qir/jit/CMakeLists.txt @@ -0,0 +1,21 @@ +# 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 + +if(TARGET MQT::CoreQIRJIT) + set(TARGET_NAME ${MQT_CORE_TARGET_NAME}-qir-jit-test) + + # Get the LLVM native target libraries + llvm_map_components_to_libnames(llvm_native_libs_for_test asmparser core irreader support) + + package_add_test(${TARGET_NAME} MQT::CoreQIRJIT test_ir_rewriter.cpp test_jit_session.cpp) + target_link_libraries(${TARGET_NAME} PRIVATE MQT::CoreQIRRuntime mqt-core-qir-test-utils + ${llvm_native_libs_for_test}) + + target_compile_definitions(${TARGET_NAME} + PRIVATE QIR_FILES_DIR="${PROJECT_SOURCE_DIR}/test/circuits") +endif() diff --git a/test/qir/jit/test_ir_rewriter.cpp b/test/qir/jit/test_ir_rewriter.cpp new file mode 100644 index 0000000000..7c1c7ba698 --- /dev/null +++ b/test/qir/jit/test_ir_rewriter.cpp @@ -0,0 +1,98 @@ +/* + * 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 + */ + +#include "qir/jit/IRRewriter.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +std::size_t countCallsTo(const llvm::Module& m, llvm::StringRef name) { + std::size_t count = 0; + for (const auto& fn : m) { + for (const auto& bb : fn) { + for (const auto& inst : bb) { + const auto* call = llvm::dyn_cast(&inst); + if (call == nullptr) { + continue; + } + const auto* callee = call->getCalledFunction(); + if (callee != nullptr && callee->getName() == name) { + ++count; + } + } + } + } + return count; +} + +std::size_t countCallsToStripTarget(const llvm::Module& m) { + return std::accumulate(qir::STRIP_TARGETS.begin(), qir::STRIP_TARGETS.end(), + std::size_t{}, + [&m](std::size_t total, const auto& target) { + return total + countCallsTo(m, target); + }); +} + +std::unique_ptr loadIRFile(const std::filesystem::path& path, + llvm::LLVMContext& ctx) { + llvm::SMDiagnostic err; + auto llvmModule = llvm::parseIRFile(path.string(), err, ctx); + if (!llvmModule) { + std::string errStr; + llvm::raw_string_ostream s(errStr); + err.print("test_ir_rewriter", s); + throw std::runtime_error("Failed to parse IR file " + path.string() + ": " + + errStr); + } + return llvmModule; +} + +class IRRewriterTest : public testing::TestWithParam { +protected: + llvm::LLVMContext ctx_; +}; + +TEST_P(IRRewriterTest, StripMeasurementRelatedCalls) { + const std::filesystem::path path = + std::filesystem::path(QIR_FILES_DIR) / GetParam(); + auto llvmModule = loadIRFile(path, ctx_); + + const auto numStripCalls = countCallsToStripTarget(*llvmModule); + ASSERT_GT(numStripCalls, 0U) << "Module has no calls to strip targets"; + + const auto numErased = qir::stripMeasurementRelatedCalls(*llvmModule); + + EXPECT_EQ(numErased, numStripCalls); + EXPECT_EQ(countCallsToStripTarget(*llvmModule), 0U); +} + +INSTANTIATE_TEST_SUITE_P(BellPair, IRRewriterTest, + testing::Values("BellPairStatic.ll", + "BellPairDynamic.ll")); + +} // namespace diff --git a/test/qir/jit/test_jit_session.cpp b/test/qir/jit/test_jit_session.cpp new file mode 100644 index 0000000000..8a7a362dda --- /dev/null +++ b/test/qir/jit/test_jit_session.cpp @@ -0,0 +1,100 @@ +/* + * 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 + */ + +#include "qir/helpers/test_utils.hpp" +#include "qir/jit/Session.hpp" +#include "qir/runtime/Runtime.hpp" + +#include + +#include +#include +#include +#include + +namespace { + +class JitSessionTest : public testing::Test { +protected: + std::ostringstream sink; + + void SetUp() override { + auto& runtime = qir::Runtime::getInstance(); + runtime.reset(); + runtime.setOstream(sink); + } + void TearDown() override { + auto& runtime = qir::Runtime::getInstance(); + runtime.resetOstream(); + runtime.setOutputSchema(qir::Runtime::OutputSchema::Labeled); + } +}; + +TEST_F(JitSessionTest, LoadModuleFromMemory) { + const auto program = qir_test::getProgram("BellPairStatic.ll"); + const qir::JitSession session(program, "BellPairStatic.ll"); + ASSERT_EQ(session.run(), 0); + EXPECT_FALSE(qir::Runtime::getInstance().getMeasurements().empty()); +} + +TEST_F(JitSessionTest, SamplingRecordsOutputs) { + const auto path = std::filesystem::path(QIR_FILES_DIR) / "BellPairStatic.ll"; + // qir::Execution::Sampling is the default Execution mode + const qir::JitSession session(path.string()); + ASSERT_EQ(session.run(), 0); + EXPECT_FALSE(qir::Runtime::getInstance().getMeasurements().empty()); +} + +TEST_F(JitSessionTest, StateExtractionLeavesNoRecordedOutputs) { + const auto path = std::filesystem::path(QIR_FILES_DIR) / "BellPairStatic.ll"; + const qir::JitSession session(path.string(), qir::Execution::StateExtraction); + ASSERT_EQ(session.run(), 0); + EXPECT_TRUE(qir::Runtime::getInstance().getMeasurements().empty()); +} + +TEST_F(JitSessionTest, OutputSchemaDefaultsToLabeledWhenAttributeAbsent) { + constexpr std::string_view ir = R"( +define i32 @main() #0 { ret i32 0 } +attributes #0 = { "entry_point" } +)"; + // Preset @c Ordered so we can tell the default kicked in. + qir::Runtime::getInstance().setOutputSchema( + qir::Runtime::OutputSchema::Ordered); + const qir::JitSession session(ir, "NoOutputSchema.ll"); + EXPECT_EQ(qir::Runtime::getInstance().getOutputSchema(), + qir::Runtime::OutputSchema::Labeled); +} + +TEST_F(JitSessionTest, OutputSchemaFromLabeledAttribute) { + constexpr std::string_view ir = R"( +define i32 @main() #0 { ret i32 0 } +attributes #0 = { "entry_point" "output_labeling_schema"="labeled" } +)"; + const qir::JitSession session(ir, "LabeledOutputSchema.ll"); + EXPECT_EQ(qir::Runtime::getInstance().getOutputSchema(), + qir::Runtime::OutputSchema::Labeled); +} + +TEST_F(JitSessionTest, OutputSchemaFromOrderedAttribute) { + constexpr std::string_view ir = R"( +define i32 @main() #0 { ret i32 0 } +attributes #0 = { "entry_point" "output_labeling_schema"="ordered" } +)"; + const qir::JitSession session(ir, "OrderedOutputSchema.ll"); + EXPECT_EQ(qir::Runtime::getInstance().getOutputSchema(), + qir::Runtime::OutputSchema::Ordered); +} + +TEST(JitSessionErrors, MalformedIRThrows) { + constexpr std::string_view ir = R"(define i32 @main() {})"; + EXPECT_THROW(qir::JitSession(ir, "MalformedIR.ll"), std::runtime_error); +} + +} // namespace diff --git a/test/qir/runner/CMakeLists.txt b/test/qir/runner/CMakeLists.txt index f79e3cf62d..6fd179f5be 100644 --- a/test/qir/runner/CMakeLists.txt +++ b/test/qir/runner/CMakeLists.txt @@ -13,7 +13,7 @@ if(TARGET MQT::CoreQIRRunner) package_add_test(${TARGET_NAME} "" test_qir_runner.cpp) # Collect QIR files set(QIR_FILES "") - set(QIR_FILES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/..) + set(QIR_FILES_DIR ${PROJECT_SOURCE_DIR}/test/circuits) file(REAL_PATH "${QIR_FILES_DIR}" QIR_FILES_DIR) file(GLOB QIR_FILES "${QIR_FILES_DIR}/*.ll") # transform QIR_FILES to comma separated list of string ("...") diff --git a/test/qir/runtime/CMakeLists.txt b/test/qir/runtime/CMakeLists.txt index 782b801182..ba21107f7d 100644 --- a/test/qir/runtime/CMakeLists.txt +++ b/test/qir/runtime/CMakeLists.txt @@ -53,7 +53,7 @@ if(TARGET MQT::CoreQIRRuntime) # add tests for QIR files set(QIR_EXECUTABLES "") - set(QIR_FILES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/..) + set(QIR_FILES_DIR ${PROJECT_SOURCE_DIR}/test/circuits) file(GLOB QIR_FILES "${QIR_FILES_DIR}/*.ll") foreach(QIR_EXAMPLE ${QIR_FILES}) diff --git a/test/qir/runtime/test_qir_runtime.cpp b/test/qir/runtime/test_qir_runtime.cpp index cc4e733f7f..2987e0870e 100644 --- a/test/qir/runtime/test_qir_runtime.cpp +++ b/test/qir/runtime/test_qir_runtime.cpp @@ -10,17 +10,18 @@ #include "ir/Definitions.hpp" #include "qir/runtime/QIR.h" +#include "qir/runtime/Runtime.hpp" #include #include #include #include +#include #include #include -#include +#include #include -#include #ifdef _WIN32 #define SYSTEM _wsystem @@ -34,14 +35,91 @@ namespace { class QIRRuntimeTest : public testing::Test { protected: - std::stringstream buffer; - std::streambuf* old = nullptr; - void SetUp() override { old = std::cout.rdbuf(buffer.rdbuf()); } - void TearDown() override { std::cout.rdbuf(old); } + std::ostringstream sink; + void SetUp() override { Runtime::getInstance().setOstream(sink); } + void TearDown() override { + Runtime::getInstance().resetOstream(); + Runtime::getInstance().setOutputSchema(Runtime::OutputSchema::Labeled); + } }; } // namespace +// Any test that emits output relies on the runtime producing the spec-mandated +// HEADER/START/METADATA/END records around the per-shot OUTPUT block. +// The runtime picks @c Labeled as default output schema, which is why the +// the framing emits `labeled` in both HEADER and METADATA here. +TEST_F(QIRRuntimeTest, OutputFraming) { + auto& runtime = Runtime::getInstance(); + runtime.outputProgramHeader(); + runtime.outputShotStart(); + runtime.outputShotEnd(); + std::ostringstream expected; + expected << "HEADER\tschema_id\tlabeled\n" + << "HEADER\tschema_version\t2.1\n" + << "START\n" + << "METADATA\toutput_labeling_schema\tlabeled\n" + << "END\t0\n"; + EXPECT_EQ(sink.str(), expected.str()); +} + +// In Labeled mode: +// - the HEADER announces `labeled`, +// - the per-shot METADATA line matches the output schema, and +// - OUTPUT records carry the label column. +TEST_F(QIRRuntimeTest, OutputFramingLabeled) { + auto& runtime = Runtime::getInstance(); + runtime.outputProgramHeader(); + runtime.outputShotStart(); + runtime.outputBool(true, "bool_label"); + runtime.outputInt(42, "int_label"); + runtime.outputFloat(3.14, "double_label"); + runtime.outputTuple(2, "tuple_label"); + runtime.outputArray(3, "array_label"); + runtime.outputShotEnd(); + std::ostringstream expected; + expected << "HEADER\tschema_id\tlabeled\n" + << "HEADER\tschema_version\t2.1\n" + << "START\n" + << "METADATA\toutput_labeling_schema\tlabeled\n" + << "OUTPUT\tBOOL\ttrue\tbool_label\n" + << "OUTPUT\tINT\t42\tint_label\n" + << "OUTPUT\tDOUBLE\t3.14\tdouble_label\n" + << "OUTPUT\tTUPLE\t2\ttuple_label\n" + << "OUTPUT\tARRAY\t3\tarray_label\n" + << "END\t0\n"; + EXPECT_EQ(sink.str(), expected.str()); +} + +// In Ordered mode: +// - the HEADER announces `ordered`, +// - the per-shot METADATA line matches the output schema, and +// - OUTPUT records drop the label column. +TEST_F(QIRRuntimeTest, OutputFramingOrdered) { + auto& runtime = Runtime::getInstance(); + runtime.setOutputSchema(Runtime::OutputSchema::Ordered); + runtime.outputProgramHeader(); + runtime.outputShotStart(); + runtime.outputBool(true, "bool_label"); + runtime.outputInt(42, "int_label"); + runtime.outputFloat(3.14, "double_label"); + runtime.outputTuple(2, "tuple_label"); + runtime.outputArray(3, "array_label"); + runtime.outputShotEnd(); + std::ostringstream expected; + expected << "HEADER\tschema_id\tordered\n" + << "HEADER\tschema_version\t2.1\n" + << "START\n" + << "METADATA\toutput_labeling_schema\tordered\n" + << "OUTPUT\tBOOL\ttrue\n" + << "OUTPUT\tINT\t42\n" + << "OUTPUT\tDOUBLE\t3.14\n" + << "OUTPUT\tTUPLE\t2\n" + << "OUTPUT\tARRAY\t3\n" + << "END\t0\n"; + EXPECT_EQ(sink.str(), expected.str()); +} + TEST_F(QIRRuntimeTest, XGate) { auto* q0 = reinterpret_cast(0UL); __quantum__rt__initialize(nullptr); @@ -75,7 +153,7 @@ TEST_F(QIRRuntimeTest, SGate) { TEST_F(QIRRuntimeTest, SdgGate) { auto* q0 = reinterpret_cast(0UL); __quantum__rt__initialize(nullptr); - __quantum__qis__sdg__body(q0); + __quantum__qis__s__adj(q0); } TEST_F(QIRRuntimeTest, SXGate) { @@ -87,7 +165,7 @@ TEST_F(QIRRuntimeTest, SXGate) { TEST_F(QIRRuntimeTest, SXdgGate) { auto* q0 = reinterpret_cast(0UL); __quantum__rt__initialize(nullptr); - __quantum__qis__sxdg__body(q0); + __quantum__qis__sx__adj(q0); } TEST_F(QIRRuntimeTest, SqrtXGate) { @@ -99,7 +177,7 @@ TEST_F(QIRRuntimeTest, SqrtXGate) { TEST_F(QIRRuntimeTest, SqrtXdgGate) { auto* q0 = reinterpret_cast(0UL); __quantum__rt__initialize(nullptr); - __quantum__qis__sqrtxdg__body(q0); + __quantum__qis__sqrtx__adj(q0); } TEST_F(QIRRuntimeTest, TGate) { @@ -111,7 +189,7 @@ TEST_F(QIRRuntimeTest, TGate) { TEST_F(QIRRuntimeTest, TdgGate) { auto* q0 = reinterpret_cast(0UL); __quantum__rt__initialize(nullptr); - __quantum__qis__tdg__body(q0); + __quantum__qis__t__adj(q0); } TEST_F(QIRRuntimeTest, RGate) { @@ -257,13 +335,19 @@ TEST_F(QIRRuntimeTest, SwapGate) { auto* r0 = reinterpret_cast(0UL); auto* r1 = reinterpret_cast(1UL); __quantum__rt__initialize(nullptr); + Runtime::getInstance().outputProgramHeader(); + Runtime::getInstance().outputShotStart(); __quantum__qis__x__body(q0); __quantum__qis__swap__body(q0, q1); __quantum__qis__mz__body(q0, r0); __quantum__qis__mz__body(q1, r1); __quantum__rt__result_record_output(r0, "r0"); __quantum__rt__result_record_output(r1, "r1"); - EXPECT_EQ(buffer.str(), "r0: 0\nr1: 1\n"); + Runtime::getInstance().outputShotEnd(); + std::ostringstream expected; + expected << "OUTPUT\tRESULT\t0\tr0\n" + << "OUTPUT\tRESULT\t1\tr1\n"; + EXPECT_THAT(sink.str(), ::testing::HasSubstr(expected.str())); } TEST_F(QIRRuntimeTest, CSwapGate) { @@ -357,6 +441,8 @@ TEST_F(QIRRuntimeTest, BellPairStatic) { auto* r0 = reinterpret_cast(0UL); auto* r1 = reinterpret_cast(1UL); __quantum__rt__initialize(nullptr); + Runtime::getInstance().outputProgramHeader(); + Runtime::getInstance().outputShotStart(); __quantum__qis__h__body(q0); __quantum__qis__cx__body(q0, q1); __quantum__qis__mz__body(q0, r0); @@ -366,11 +452,17 @@ TEST_F(QIRRuntimeTest, BellPairStatic) { EXPECT_EQ(m1, m2); __quantum__rt__result_record_output(r0, "r0"); __quantum__rt__result_record_output(r1, "r1"); - EXPECT_THAT(buffer.str(), testing::AnyOf("r0: 0\nr1: 0\n", "r0: 1\nr1: 1\n")); + Runtime::getInstance().outputShotEnd(); + std::ostringstream expected; + expected << "OUTPUT\tRESULT\t" << m1 << "\tr0\n" + << "OUTPUT\tRESULT\t" << m2 << "\tr1\n"; + EXPECT_THAT(sink.str(), ::testing::HasSubstr(expected.str())); } TEST_F(QIRRuntimeTest, BellPairDynamic) { __quantum__rt__initialize(nullptr); + Runtime::getInstance().outputProgramHeader(); + Runtime::getInstance().outputShotStart(); auto* q0 = __quantum__rt__qubit_allocate(); auto* q1 = __quantum__rt__qubit_allocate(); __quantum__qis__h__body(q0); @@ -384,7 +476,11 @@ TEST_F(QIRRuntimeTest, BellPairDynamic) { EXPECT_EQ(m1, m2); __quantum__rt__result_record_output(r0, "r0"); __quantum__rt__result_record_output(r1, "r1"); - EXPECT_THAT(buffer.str(), testing::AnyOf("r0: 0\nr1: 0\n", "r0: 1\nr1: 1\n")); + Runtime::getInstance().outputShotEnd(); + std::ostringstream expected; + expected << "OUTPUT\tRESULT\t" << m1 << "\tr0\n" + << "OUTPUT\tRESULT\t" << m2 << "\tr1\n"; + EXPECT_THAT(sink.str(), ::testing::HasSubstr(expected.str())); __quantum__rt__result_update_reference_count(r0, -1); __quantum__rt__result_update_reference_count(r1, -1); } @@ -395,6 +491,8 @@ TEST_F(QIRRuntimeTest, BellPairStaticReverse) { auto* r0 = reinterpret_cast(0UL); auto* r1 = reinterpret_cast(1UL); __quantum__rt__initialize(nullptr); + Runtime::getInstance().outputProgramHeader(); + Runtime::getInstance().outputShotStart(); __quantum__qis__h__body(q1); __quantum__qis__cx__body(q1, q0); __quantum__qis__mz__body(q0, r0); @@ -404,11 +502,17 @@ TEST_F(QIRRuntimeTest, BellPairStaticReverse) { EXPECT_EQ(m1, m2); __quantum__rt__result_record_output(r0, "r0"); __quantum__rt__result_record_output(r1, "r1"); - EXPECT_THAT(buffer.str(), testing::AnyOf("r0: 0\nr1: 0\n", "r0: 1\nr1: 1\n")); + Runtime::getInstance().outputShotEnd(); + std::ostringstream expected; + expected << "OUTPUT\tRESULT\t" << m1 << "\tr0\n" + << "OUTPUT\tRESULT\t" << m2 << "\tr1\n"; + EXPECT_THAT(sink.str(), ::testing::HasSubstr(expected.str())); } TEST_F(QIRRuntimeTest, BellPairDynamicReverse) { __quantum__rt__initialize(nullptr); + Runtime::getInstance().outputProgramHeader(); + Runtime::getInstance().outputShotStart(); auto* q0 = __quantum__rt__qubit_allocate(); auto* q1 = __quantum__rt__qubit_allocate(); __quantum__qis__h__body(q1); @@ -422,7 +526,11 @@ TEST_F(QIRRuntimeTest, BellPairDynamicReverse) { EXPECT_EQ(m1, m2); __quantum__rt__result_record_output(r0, "r0"); __quantum__rt__result_record_output(r1, "r1"); - EXPECT_THAT(buffer.str(), testing::AnyOf("r0: 0\nr1: 0\n", "r0: 1\nr1: 1\n")); + Runtime::getInstance().outputShotEnd(); + std::ostringstream expected; + expected << "OUTPUT\tRESULT\t" << m1 << "\tr0\n" + << "OUTPUT\tRESULT\t" << m2 << "\tr1\n"; + EXPECT_THAT(sink.str(), ::testing::HasSubstr(expected.str())); __quantum__rt__result_update_reference_count(r0, -1); __quantum__rt__result_update_reference_count(r1, -1); } @@ -505,6 +613,81 @@ TEST_F(QIRRuntimeTest, GHZ4Dynamic) { __quantum__rt__array_update_reference_count(rArr, -1); } +TEST_F(QIRRuntimeTest, PackageResizeWhenEnlargingState) { + // dd::Package starts at 32 qubits. + // Acting on qubit 32 forces qState.dd->resize. + auto* q32 = reinterpret_cast(32UL); + __quantum__rt__initialize(nullptr); + __quantum__qis__h__body(q32); +} + +TEST_F(QIRRuntimeTest, TakeStateReturnsStateAndResetsRuntime) { + // Drive a small program through the runtime: H on q0. + auto* q0 = reinterpret_cast(0UL); + __quantum__rt__initialize(nullptr); + __quantum__qis__h__body(q0); + + auto state = Runtime::getInstance().takeState(); + EXPECT_NE(state.dd, nullptr); + EXPECT_FALSE(state.edge.isTerminal()); + EXPECT_EQ(state.numQubits, 1); + + // After takeState the runtime is reset and usable again. + EXPECT_NO_THROW(__quantum__rt__initialize(nullptr)); + EXPECT_NO_THROW(__quantum__qis__h__body(q0)); +} + +TEST_F(QIRRuntimeTest, AdaptiveRecordOutputs) { + __quantum__rt__initialize(nullptr); + Runtime::getInstance().outputProgramHeader(); + Runtime::getInstance().outputShotStart(); + auto* q0 = __quantum__rt__qubit_allocate(); + auto* q1 = __quantum__rt__qubit_allocate(); + auto* q2 = __quantum__rt__qubit_allocate(); + __quantum__qis__h__body(q0); + __quantum__qis__h__body(q1); + __quantum__qis__h__body(q2); + auto* r0 = __quantum__qis__m__body(q0); + auto* r1 = __quantum__qis__m__body(q1); + auto* r2 = __quantum__qis__m__body(q2); + const auto b0 = __quantum__rt__read_result(r0); + const auto b1 = __quantum__rt__read_result(r1); + const auto b2 = __quantum__rt__read_result(r2); + __quantum__rt__qubit_release(q0); + __quantum__rt__qubit_release(q1); + __quantum__rt__qubit_release(q2); + + // Classical compute: Hamming weight and its mean. + const int64_t weight = + static_cast(b0) + static_cast(b1) + static_cast(b2); + const double mean = static_cast(weight) / 3.0; + + // Output: tuple of 3 elements (array of 3 bools, int weight, float mean). + __quantum__rt__tuple_record_output(3, "outputs"); + __quantum__rt__array_record_output(3, "measurements"); + __quantum__rt__bool_record_output(b0, "m0"); + __quantum__rt__bool_record_output(b1, "m1"); + __quantum__rt__bool_record_output(b2, "m2"); + __quantum__rt__int_record_output(weight, "hamming_weight"); + __quantum__rt__float_record_output(mean, "mean"); + Runtime::getInstance().outputShotEnd(); + + std::ostringstream expected; + expected.setf(std::ios::boolalpha); + expected << "OUTPUT\tTUPLE\t3\toutputs\n" + << "OUTPUT\tARRAY\t3\tmeasurements\n" + << "OUTPUT\tBOOL\t" << b0 << "\tm0\n" + << "OUTPUT\tBOOL\t" << b1 << "\tm1\n" + << "OUTPUT\tBOOL\t" << b2 << "\tm2\n" + << "OUTPUT\tINT\t" << weight << "\thamming_weight\n" + << "OUTPUT\tDOUBLE\t" << mean << "\tmean\n"; + EXPECT_THAT(sink.str(), ::testing::HasSubstr(expected.str())); + + __quantum__rt__result_update_reference_count(r0, -1); + __quantum__rt__result_update_reference_count(r1, -1); + __quantum__rt__result_update_reference_count(r2, -1); +} + namespace { class QIRFilesTest : public ::testing::TestWithParam {}; diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index 3667a47e1b..30a3e76b05 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -676,4 +676,83 @@ TEST_F(ZXFunctionalityTest, MCT2) { checkEquivalence(qc, qcPrime, {0, 1, 2}); } + +TEST_F(ZXFunctionalityTest, RCCX) { + qc = qc::QuantumComputation(3); + qc.rccx(0, 1, 2); + + auto qcPrime = qc::QuantumComputation(3); + qcPrime.h(2); + qcPrime.t(2); + qcPrime.cx(1, 2); + qcPrime.tdg(2); + qcPrime.cx(0, 2); + qcPrime.t(2); + qcPrime.cx(1, 2); + qcPrime.tdg(2); + qcPrime.h(2); + + checkEquivalence(qc, qcPrime, {0, 1, 2}); +} + +TEST_F(ZXFunctionalityTest, RCCXSelfInverse) { + qc = qc::QuantumComputation(3); + qc.rccx(0, 1, 2); + qc.rccx(0, 1, 2); + + auto qcPrime = qc::QuantumComputation(3); + checkEquivalence(qc, qcPrime, {0, 1, 2}); +} + +TEST_F(ZXFunctionalityTest, CRCCX) { + qc = qc::QuantumComputation(4); + qc.crccx(0, 1, 2, 3); + + auto qcPrime = qc::QuantumComputation(4); + const auto controlledH = [](qc::QuantumComputation& comp, + const qc::Qubit ctrl, const qc::Qubit target) { + comp.rz(qc::PI_2, target); + comp.rx(qc::PI_2, target); + comp.cp(qc::PI, ctrl, target); + comp.rx(-qc::PI_2, target); + comp.rz(-qc::PI_2, target); + }; + controlledH(qcPrime, 0, 3); + qcPrime.mct({0}, 3); + qcPrime.mcx({0, 2}, 3); + qcPrime.mctdg({0}, 3); + qcPrime.mcx({0, 1}, 3); + qcPrime.mct({0}, 3); + qcPrime.mcx({0, 2}, 3); + qcPrime.mctdg({0}, 3); + controlledH(qcPrime, 0, 3); + + checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); +} + +TEST_F(ZXFunctionalityTest, MCRCCX) { + qc = qc::QuantumComputation(5); + qc.mcrccx({0, 1}, 2, 3, 4); + + auto qcPrime = qc::QuantumComputation(5); + qcPrime.mcrz(qc::PI_2, {0, 1}, 4); + qcPrime.mcrx(qc::PI_2, {0, 1}, 4); + qcPrime.mcz({0, 1}, 4); + qcPrime.mcrx(-qc::PI_2, {0, 1}, 4); + qcPrime.mcrz(-qc::PI_2, {0, 1}, 4); + qcPrime.mct({0, 1}, 4); + qcPrime.mcx({0, 1, 3}, 4); + qcPrime.mctdg({0, 1}, 4); + qcPrime.mcx({0, 1, 2}, 4); + qcPrime.mct({0, 1}, 4); + qcPrime.mcx({0, 1, 3}, 4); + qcPrime.mctdg({0, 1}, 4); + qcPrime.mcrz(qc::PI_2, {0, 1}, 4); + qcPrime.mcrx(qc::PI_2, {0, 1}, 4); + qcPrime.mcz({0, 1}, 4); + qcPrime.mcrx(-qc::PI_2, {0, 1}, 4); + qcPrime.mcrz(-qc::PI_2, {0, 1}, 4); + + checkEquivalence(qc, qcPrime, {0, 1, 2, 3, 4}); +} } // namespace zx diff --git a/uv.lock b/uv.lock index ac56ece132..587ede7726 100644 --- a/uv.lock +++ b/uv.lock @@ -79,11 +79,11 @@ wheels = [ [[package]] name = "asttokens" -version = "3.0.1" +version = "3.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } +sdist = { url = "https://files.pythonhosted.org/packages/25/1e/faf0f247f6f881b98fc4d6d07e14085cb89d13665084e6d6ac1dc2c03d0b/asttokens-3.0.2.tar.gz", hash = "sha256:3ecdbd8f2cc195f53ccada3a613538bb5f9ef6f6869129f13e03c30a677b8fe2", size = 63136, upload-time = "2026-07-12T03:31:49.084Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, + { url = "https://files.pythonhosted.org/packages/d4/2b/04b8a15f3a1c77bc79ddf5c73875327f34b4fa75982df2b76e45e402d364/asttokens-3.0.2-py3-none-any.whl", hash = "sha256:9da13157f5b28becde0bd374fc677dcd3c290614264eff096f167c469cd9f933", size = 28702, upload-time = "2026-07-12T03:31:47.542Z" }, ] [[package]] @@ -133,198 +133,208 @@ wheels = [ [[package]] name = "certifi" -version = "2026.6.17" +version = "2026.7.22" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" }, + { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, ] [[package]] name = "cffi" -version = "2.0.0" +version = "2.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pycparser", marker = "implementation_name != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, - { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, - { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, - { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, - { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, - { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, - { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, - { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, - { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, - { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, - { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, - { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, - { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, - { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, - { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, - { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, - { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, - { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, - { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, - { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, - { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, - { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, - { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, - { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, - { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, - { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, - { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, - { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, - { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, - { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, - { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, - { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, - { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, - { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, - { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, - { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, - { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, - { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, - { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, - { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, - { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, - { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, - { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, - { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, - { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, - { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, - { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, - { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, - { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, - { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, - { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, - { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, - { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, - { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, - { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, - { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, - { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/5f/ff100cae70ebe9d8df1c01a00e510e45d9adb5c1fdda84791b199141de97/cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9", size = 531036, upload-time = "2026-07-06T21:34:30.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/e9/6d7724983b3d5a0908dbf74f64038ade77c18646ff6636ec7894fd392ce1/cffi-2.1.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:b65f590ef2a44640f9a05dbb548a429b4ade77913ce683ac8b1480777658a6c0", size = 183837, upload-time = "2026-07-06T21:32:09.655Z" }, + { url = "https://files.pythonhosted.org/packages/69/aa/24580a278de21fd7322635556334d9b535f1cbc00b0a3919447cdf464c65/cffi-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164bff1657b2a74f0b6d54e11c9b375bc97b931f2ca9c43fcf875838da1570dd", size = 184226, upload-time = "2026-07-06T21:32:11.196Z" }, + { url = "https://files.pythonhosted.org/packages/88/a9/02cae418ec4beb282ace11958d9d4737793439d561fadc7e6d56f2e2b354/cffi-2.1.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:c941bb58d5a6e1c3892d86e42927ed6c180302f07e6d395d08c416e594b98b46", size = 211107, upload-time = "2026-07-06T21:32:12.328Z" }, + { url = "https://files.pythonhosted.org/packages/3b/30/c806937ed5e4c2c7ac30d9d6b76b5dc57ff8b75d83800d9bb11a8253cf2a/cffi-2.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a016194dbe13d14ee9556e734b772d8d67b947092b268d757fd4290e3ba2dfc2", size = 218733, upload-time = "2026-07-06T21:32:13.67Z" }, + { url = "https://files.pythonhosted.org/packages/f9/cf/398272b8bbfd58aa314fda5a7f1cdbb26d1d78ae324a11211521315dd1f0/cffi-2.1.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:03e9810d18c646077e501f661b682fbf5dee4676048527ca3cffe66faa9960dd", size = 205543, upload-time = "2026-07-06T21:32:15.148Z" }, + { url = "https://files.pythonhosted.org/packages/45/ca/f91641185cdd90c36d317a9dc7f85e88ef8682d8b300977baff5e23c35d8/cffi-2.1.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:19c54ac121cad98450b4896fa9a43ee0180d57bc4bc911a33db6cab1efab6cd3", size = 205460, upload-time = "2026-07-06T21:32:16.479Z" }, + { url = "https://files.pythonhosted.org/packages/38/66/04781a77b411f0bb5b234d62c1814754ab75ebe455ccff1b08e8d7aae98f/cffi-2.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d433a51f1870e43a13b6732f92aaf540ff77c2015097c78556f75a2d6c030e0", size = 218760, upload-time = "2026-07-06T21:32:17.98Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9a/bb1d5ed9c3fcae158e9f6391bf309c95d98c2ac37ed56573228471d0af5e/cffi-2.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3d7f118b5adbfdfead90c25822690b02bc8074fba949bb7858bec4ebd55adb43", size = 221230, upload-time = "2026-07-06T21:32:19.407Z" }, + { url = "https://files.pythonhosted.org/packages/41/aa/3c1409cdd26094efacd1c36c66e0a6eb9d4296e4fd4f9901b8b2042f4323/cffi-2.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c5f5df567f6eb216de69be06ce55c8b714090fae02b18a3b40da8163b8c5fa9c", size = 213524, upload-time = "2026-07-06T21:32:20.828Z" }, + { url = "https://files.pythonhosted.org/packages/fa/75/74dfb7c3fc6ebbd408038476bd4c1d7e925c62614e7b9c534ecc34218288/cffi-2.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:11b3fb55f4f8ad92274ed26705f65d8f91457de71f5380061eb6d125a768fecd", size = 220341, upload-time = "2026-07-06T21:32:21.9Z" }, + { url = "https://files.pythonhosted.org/packages/70/b6/9003c33a3e7d2c1306f5962e646457dcfe5a8cd8fce6bbe02d7af25db783/cffi-2.1.0-cp310-cp310-win32.whl", hash = "sha256:9d72af0cf10a76a600a9690078fe31c63b9588c8e86bf9fd353f713c84b5db0f", size = 174578, upload-time = "2026-07-06T21:32:23.073Z" }, + { url = "https://files.pythonhosted.org/packages/8a/26/710688310447531c7a22f857c7f79d9855ec18b03e04494ced723fb37e2f/cffi-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb62edb5bb52cca65fab91a63afa7561607120d26090a7e8fda6fb9f064726da", size = 185071, upload-time = "2026-07-06T21:32:24.671Z" }, + { url = "https://files.pythonhosted.org/packages/d3/67/85c89a59ba36a671e79638f44d466749f08179266a57e4f2ffdf92174072/cffi-2.1.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:02cb7ff33ded4f1532476731f89ede53e2e488a8e6205515a82144246ffa7dcc", size = 183845, upload-time = "2026-07-06T21:32:26.32Z" }, + { url = "https://files.pythonhosted.org/packages/ea/dd/e3b0baa2d3d6a857ac72b7efbf18e32e487c9cdafcc13049ad765495b15e/cffi-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f5bce581e6b8c235e566a14768a943b172ada3ed73537bb0c0be1edee312d4e7", size = 184186, upload-time = "2026-07-06T21:32:28.025Z" }, + { url = "https://files.pythonhosted.org/packages/65/68/9f3ef890cf3c6ab97bd531c5677f67613d302165d16f8142b2811782a614/cffi-2.1.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:30b65779d598c370374fefabf138d456fd6f3216bfa7bedfab1ba82025b0cd93", size = 211892, upload-time = "2026-07-06T21:32:29.565Z" }, + { url = "https://files.pythonhosted.org/packages/22/d7/1a74539db16d8bfd839ff1515948948efbb162e574650fd3d846896eea95/cffi-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88023dfe18799507b73f1dbb0d14326a17465de1bc9c9c7655c22845e9ddc3a2", size = 218793, upload-time = "2026-07-06T21:32:30.951Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d1/9a5b7169499e8e8d8e636de70b97ac7c9447104d2ff1a2cd94790cea5162/cffi-2.1.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:0a96b74cda968eebbad56d973efe5098974f0a9fb323865bf99ea1fd24e3e64c", size = 205737, upload-time = "2026-07-06T21:32:32.216Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b0/e131a9c41f10607926278453d9596163594fe1c4ebc46efe3b5e5b34eb84/cffi-2.1.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a5781494d4d400a3f47f8f1da94b324f6e6b440a53387774002890a2a2f4b50f", size = 204909, upload-time = "2026-07-06T21:32:33.655Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d2/4398416cd699b35167947c6e22aca52c47e69ad5695073c9f1f2c52e04aa/cffi-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aa7a1b53a2a4452ada2d1b5dade9960b2522f1e61293a811a077439e39029565", size = 217883, upload-time = "2026-07-06T21:32:35.173Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a5/d4fe77b589e5e82d43ebc809bf2e6474afe8e48e32ea050b9357645b6471/cffi-2.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d8272c0e483b024e1b9ad029821470ed8ec65631dbd90217469da0e7cd89f1c", size = 221251, upload-time = "2026-07-06T21:32:36.527Z" }, + { url = "https://files.pythonhosted.org/packages/22/f0/a2fc43084c0433caf7f461bccc013e28f848d04ee1c5ed7fce71423cf4d9/cffi-2.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7762faa47e8ff7eb80bd261d9a7d8eea2d8baa69de5e95b70c1f338bbe712f02", size = 214250, upload-time = "2026-07-06T21:32:37.852Z" }, + { url = "https://files.pythonhosted.org/packages/04/8c/b925975448cf20634a9fbd5efceb807219db452653648d2897c0989cab2d/cffi-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:89095c1968b4ba8285840e131bf2891b09ae137fe2146905acae0354fbce1b5e", size = 219441, upload-time = "2026-07-06T21:32:39.146Z" }, + { url = "https://files.pythonhosted.org/packages/eb/da/5c4918a2d61d86fa927d716cb3d8e4626ef8dc8f605a599d32f33897f59a/cffi-2.1.0-cp311-cp311-win32.whl", hash = "sha256:64c753a0f87a256020004f37a1c8c02c480e725f910f0b2a0f3f07debd1b2479", size = 174496, upload-time = "2026-07-06T21:32:40.467Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c8/6c2de1d55cf35ef8b92885d5ef280790f0fb9634d87ea1cc315176aecd61/cffi-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:4f26194e3d95e06501b942642855aed4f953d55e95d7d01b7c4483db3ecff458", size = 185113, upload-time = "2026-07-06T21:32:41.761Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4e/e8d7cb5783f1841a3c8fb3a7735838d7484d08ec08c9f984b14cac1ac0e9/cffi-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:35aaea0c7ee0e58a5cd8c2fd1a48fdf7ece0d2699b7ecdda08194e9ce5dd9b3d", size = 179927, upload-time = "2026-07-06T21:32:42.961Z" }, + { url = "https://files.pythonhosted.org/packages/1e/85/990925db5df586ec90beb97529c853497e7f85ba0234830447faf41c3057/cffi-2.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:df2b82571a1b30f58a87bf4e5a9e78d2b1eff6c6ce8fd3aa3757221f93f0863f", size = 184829, upload-time = "2026-07-06T21:32:44.324Z" }, + { url = "https://files.pythonhosted.org/packages/4b/92/e7bb136ad6b5352603732cf907ef862ca103f20f2031c1735a46300c20c9/cffi-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78474632761faa0fb96f30b1c928c84ebcf68713cbb80d15bab09dfe61640fde", size = 184728, upload-time = "2026-07-06T21:32:45.683Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c0/d1ec30ffb370f748f2fb54425972bfef9871e0132e82fb589c46b6676049/cffi-2.1.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5972433ad71a9e46516584ef60a0fda12d9dc459938d1539c3ddecf9bdc1368d", size = 214815, upload-time = "2026-07-06T21:32:48.557Z" }, + { url = "https://files.pythonhosted.org/packages/1b/dc/5620cf930688be01f2d673804291de757a934c90b946dbdc3d84130c2ea4/cffi-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6422532152adf4e59b110cb2808cee7a033800952f5c036b4af047ee43199e7", size = 222429, upload-time = "2026-07-06T21:32:49.848Z" }, + { url = "https://files.pythonhosted.org/packages/4b/a4/77b53abbf7a1e0beb9637edbef2a94d15f9c822f591e85d439ffd91519a6/cffi-2.1.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:46b1c8db8f6122420f32d02fffb924c2fe9bc772d228c7c711748fff56aabb2b", size = 210315, upload-time = "2026-07-06T21:32:51.221Z" }, + { url = "https://files.pythonhosted.org/packages/58/0c/f528df19cc94b675087324d4760d9e6d5bfae97d6217aa4fac43de4f5fcc/cffi-2.1.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9fafc5aa2e2a39aaf7f8cc0c1f044a9b07fca12e558dca53a3cc5c654ad67a7", size = 208859, upload-time = "2026-07-06T21:32:52.512Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/c9522a81c32132799a1972c39f5c5f8b4c8b9f00488a23feaa6c06f07741/cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66", size = 221844, upload-time = "2026-07-06T21:32:53.704Z" }, + { url = "https://files.pythonhosted.org/packages/6e/28/bd53988b9833e8f8ad539d26f4c07a6b3f6bcb1e9e02e7ca038250b3428d/cffi-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98fff996e983a36d3aa2eca83af40c5821202e7e6f32d13ae94e3d2286f10cfe", size = 225287, upload-time = "2026-07-06T21:32:54.907Z" }, + { url = "https://files.pythonhosted.org/packages/79/99/0d0fd37f055224085f42bbb2c022d002e17dde4a97972822327b07d84101/cffi-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:379de10ce1ba048b1448599d1b37b24caee16309d1ac98d3982fc997f768700b", size = 223681, upload-time = "2026-07-06T21:32:56.329Z" }, + { url = "https://files.pythonhosted.org/packages/b0/80/c138990aa2a70b1a269f6e06348729836d733d6f970867943f61d367f8cc/cffi-2.1.0-cp312-cp312-win32.whl", hash = "sha256:9b8f0f26ca4e7513c534d351eca551947d053fac438f2a04ac96d882909b0d3a", size = 175269, upload-time = "2026-07-06T21:32:57.777Z" }, + { url = "https://files.pythonhosted.org/packages/a8/eb/f636456ff21a83fc13c032b58cc5dde061691546ac79efa284b2989b7982/cffi-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384", size = 185881, upload-time = "2026-07-06T21:32:59.253Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/400ea43e721727dca8a65c4521390e9196757caba4a45643acb2b63271b8/cffi-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:6d194185eabd279f1c05ebe3504265ddfc5ad2b58d0714f7db9f01da592e9eb6", size = 180088, upload-time = "2026-07-06T21:33:02.278Z" }, + { url = "https://files.pythonhosted.org/packages/96/88/a996879e2eeccb815f6e3a5967b12a308257412acec882039d386bd2aa7b/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:10537b1df4967ca26d21e5072d7d54188354483b91dc75058968d3f0cf13fbda", size = 194331, upload-time = "2026-07-06T21:33:03.697Z" }, + { url = "https://files.pythonhosted.org/packages/58/85/7ae00d5c8dd6266f4e944c3db630f3c5c9a98b61d469c714d848b1d8138a/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a95b05f9baf29b91171b3a8bd2020b028835243e7b0ff6bb23e2a3c228518b1b", size = 196966, upload-time = "2026-07-06T21:33:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e9/45c3a76ad8d43ad9261f4c95436da61128d3ca545d72b9612c0ab5be0b1c/cffi-2.1.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:15faec4adfff450819f3aee0e2e02c812de6edb88203aa58807955db2003472a", size = 184795, upload-time = "2026-07-06T21:33:06.699Z" }, + { url = "https://files.pythonhosted.org/packages/84/4c/82f132cb4418ee6d953d982b19191e87e2a6372c8a4ce36e50b69d6ade4a/cffi-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:716ff8ec22f20b4d988b12884086bcef0fc99737043e503f7a3935a6be99b1ea", size = 184746, upload-time = "2026-07-06T21:33:08.071Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1c/4ed5a0e5bdca6cbc275556de3328dd1b76fd0c11cc13c88fe66d1d8715f2/cffi-2.1.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:63960549e4f8dc41e31accb97b975abaecfc44c03e396c093a6436763c2ea7db", size = 214747, upload-time = "2026-07-06T21:33:09.671Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a6/e879bb68cc23a2bc9ba8f4b7d8019f0c2694bad2ab6c4a3701d429439f58/cffi-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff067a8d8d880e7809e4ac88eb009bb848870115317b306666502ccad30b147f", size = 222392, upload-time = "2026-07-06T21:33:10.896Z" }, + { url = "https://files.pythonhosted.org/packages/88/f6/01890cfd63c08f8eb96a8319b0443690197d240a8bd6346048cf7bde9190/cffi-2.1.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3b926723c13eba9f81d2ef3820d63aeceec3b2d4639906047bf675cb8a7a500d", size = 210285, upload-time = "2026-07-06T21:33:12.251Z" }, + { url = "https://files.pythonhosted.org/packages/a6/cf/2b684132056f438567b61e19d690dd31cd0921ace051e0a458be6074369e/cffi-2.1.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:47ff3a8bfd8cb9da1af7524b965127095055654c177fcfc7578debcb015eecd0", size = 208801, upload-time = "2026-07-06T21:33:13.617Z" }, + { url = "https://files.pythonhosted.org/packages/6f/08/f2e7d62c460faae0926f2d6e423694aa409ced3bc1fe2927a0a6e5f05416/cffi-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:799416bae98336e400981ff6e532d67d5c709cfb30afb79865a1315f94b0e224", size = 221808, upload-time = "2026-07-06T21:33:15.466Z" }, + { url = "https://files.pythonhosted.org/packages/38/37/04f54b8e63a02f3d908332c9effbf8c366167c6f733ed8a3d4f79b7e2a1e/cffi-2.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:961be50688f7fba2fa65f63712d3b9b341a22311f5253460ce933f52f0de1c8c", size = 225241, upload-time = "2026-07-06T21:33:16.869Z" }, + { url = "https://files.pythonhosted.org/packages/a9/d6/c72eecca433cd3e681c65ed313ab4835d9d4a379704d0f628a6a05f51c2e/cffi-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf5c6cf48238b0eb4c086978c492ad1cbc22373fc5b2d7353b3a598ce6db887a", size = 223588, upload-time = "2026-07-06T21:33:18.239Z" }, + { url = "https://files.pythonhosted.org/packages/c6/4b/e706f67279140f92939da3475ad610df18bfd52d50f14953a8e5fede71d5/cffi-2.1.0-cp313-cp313-win32.whl", hash = "sha256:db3eb7d46527159a878ec3460e9d40615bc25ba337d477db681aea6e4f05c5d2", size = 175248, upload-time = "2026-07-06T21:33:19.799Z" }, + { url = "https://files.pythonhosted.org/packages/5a/47/59eb7975cb0e4ef0afa764ea945b29a5bb4537a9f771cb7d6c8a5dd74c95/cffi-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:8e74a6135550c4748af665b1b1118b6aab33b1fc6a16f9aff630af107c3b4512", size = 185717, upload-time = "2026-07-06T21:33:21.47Z" }, + { url = "https://files.pythonhosted.org/packages/5a/af/34fee85c48f8d94efc8597bc09470c9dd274c145f1c12e0fbc6ab6d38d74/cffi-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:2282cd5e38aa8accd03e99d1256af8411c84cdbee6a89d841b563fdbd1f3e50f", size = 180114, upload-time = "2026-07-06T21:33:22.515Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f0/81478e482afa03f6d18dc8f2afb5edc45b3080853b634b5ed91961be0998/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d2117334c3af3bdcb9a88522b844a2bdb5efdc4f71c6c822df55486ae1c3347a", size = 194142, upload-time = "2026-07-06T21:33:23.657Z" }, + { url = "https://files.pythonhosted.org/packages/7d/95/8de304305cd9204974b0ca051b86d307cafca13aa575a0ef1b44d92c0d8c/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:702c436735fbe99d59ada02a1f65cfc0d31c0ee8b7290912f8fbc5cd1e4b16c3", size = 196819, upload-time = "2026-07-06T21:33:25.007Z" }, + { url = "https://files.pythonhosted.org/packages/20/71/7c8372d30e42415602ed9f268f7cfd66f1b855fed881ecd168bcb45dbc0b/cffi-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1ff3456eab0d889592d1936d6125bbfbc7ae4d3354a700f8bd80450a66445d4d", size = 184965, upload-time = "2026-07-06T21:33:26.605Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5c/584e626835f0375c928176c04137c96927165cb8733cdb3150ec04e5ee5e/cffi-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c4165821e131d6d4ca444347c2b694e2311bcfa3fe5a861cc72968f28867beac", size = 184952, upload-time = "2026-07-06T21:33:27.823Z" }, + { url = "https://files.pythonhosted.org/packages/2e/d2/065fcae1c73979fac8e054462478d0ff8a29c40cdc2ed7ea5676a061df53/cffi-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:276f20fffd7b396e12516ba8edf9509210ac248cbbc5acbc39cd512f9f59ebe6", size = 222353, upload-time = "2026-07-06T21:33:29.178Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a5/e8bbb1ce5b3ac2f53ad6a10bde44318a5a8d99d4f4a000d44a6e39aeb3e4/cffi-2.1.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7d5980a3433d4b71a5e120f9dd551403d7824e31e2e67124fe2769c404c06913", size = 210051, upload-time = "2026-07-06T21:33:30.534Z" }, + { url = "https://files.pythonhosted.org/packages/28/ed/c127d3ac36e899c965e3361357c3befacd6578c03f40125183e41c3b219e/cffi-2.1.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6ca4919c6e4f89aa99c42510b42cf54596892c00b3f9077f6bdd1505e24b9c8d", size = 208630, upload-time = "2026-07-06T21:33:31.753Z" }, + { url = "https://files.pythonhosted.org/packages/cc/d7/97d3136f81db489ec8d1d67748c110d6c994268fd7528014aa9f2b085e4e/cffi-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d53d10f7da99ae46f7373b9150393e9c5eab9b224909982b43832668de4779f5", size = 221593, upload-time = "2026-07-06T21:33:33.044Z" }, + { url = "https://files.pythonhosted.org/packages/d3/27/93195977168ee63aed233a1a0993a2178798654d1f4bddcdd321d6fd3b21/cffi-2.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c351efb95e832a853a29361675f33a7ce53de1a109cd73fd47af0712213aa4ce", size = 225146, upload-time = "2026-07-06T21:33:34.224Z" }, + { url = "https://files.pythonhosted.org/packages/b3/c1/6dbd291ee2ae5a50a034aa057207081f545923bbf15dad4511e985aafff5/cffi-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbf7c7a88e2bac086f06d14577332760bdeecc42bdec8ac4077f6260557d9326", size = 223240, upload-time = "2026-07-06T21:33:35.57Z" }, + { url = "https://files.pythonhosted.org/packages/0f/6f/ade5ce9863a57992a6ea3d0d10d7e29b8749fc127204b3d493d667b2815f/cffi-2.1.0-cp314-cp314-win32.whl", hash = "sha256:1854b724d00f6654c742097d5387569021be12d3a0f770eae1df8f8acfcc6acd", size = 177723, upload-time = "2026-07-06T21:33:51.626Z" }, + { url = "https://files.pythonhosted.org/packages/41/de/92b9eeed4ae4a21d6fd9b2a2c8505cbed573299902ea73981cc13f7ff62c/cffi-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:1b96bfe2c4bd825681b7d311ad6d9b7280a091f43e8f63da5729638083cd3bfb", size = 187937, upload-time = "2026-07-06T21:33:53.403Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1a/cc6ae6c2913a03aab8898eee57963cf1035b8df5872ed8b9115fcc7e2be8/cffi-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:7d28dff1db6764108bc30788d85d61c876beff416d9a49cb9dd7c5a9f34f5804", size = 183001, upload-time = "2026-07-06T21:33:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/14/f0/134c00ce0779ec86dea2aa1aac69339c2741a8045072676763512363a2ea/cffi-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7ea6b3e2c4250ff1de21c630fe72d0f63eb95c2c32ffbf64a358cf4a8836d714", size = 188538, upload-time = "2026-07-06T21:33:36.792Z" }, + { url = "https://files.pythonhosted.org/packages/50/d8/3b86aba791cb610d24e8a3e1b2cd529e71fa15096b04e4d4e360049d4a4c/cffi-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6af371f3767faeffc6ac1ef57cdfd25844403e9d3f476c5537caee499de96376", size = 188230, upload-time = "2026-07-06T21:33:38.011Z" }, + { url = "https://files.pythonhosted.org/packages/14/d0/117dcd9209255ad8571fbc8c92ef32593a1d294dcec91ddc4e4db50606f2/cffi-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb4e8997a49aa2c08a3e43c9045d224448b8941d88e7ac163c7d383e560cbf98", size = 223899, upload-time = "2026-07-06T21:33:39.514Z" }, + { url = "https://files.pythonhosted.org/packages/b6/3d/f20f8b886b254e3ad10e15cd4186d3aed49f3e6a35ab37aab9f8f25f7c03/cffi-2.1.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf01d8c84cbea96b944c73b22182e6c7c432b3475632b8111dbfdc95ddad6e13", size = 211652, upload-time = "2026-07-06T21:33:40.851Z" }, + { url = "https://files.pythonhosted.org/packages/28/3b/fad54de07260b93ddeef4b96d0131d57ea900675df1d410ae1deee52d7a6/cffi-2.1.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:33eb1ad83ebe8f313e0df035c406227d55a79456704a863fad9842136af5ad7d", size = 210755, upload-time = "2026-07-06T21:33:42.183Z" }, + { url = "https://files.pythonhosted.org/packages/cc/82/3d5c705acb7abbba9bbd7d79b8e62e0f25b6120eb7ae6ac49f1b721722fe/cffi-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ac0f1a2d0cfa7eea3f2aaf006ab6e70e8feeb16b75d65b7e5939982ca2f11056", size = 223933, upload-time = "2026-07-06T21:33:43.603Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d0/47e338384ab6b1004241002fa616301020cea4fc95f283506565d252f276/cffi-2.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c16914df9fb7f500e440e6875fa23ff5e0b31db01fa9c06af98d59a91f0dc2e4", size = 226749, upload-time = "2026-07-06T21:33:45.046Z" }, + { url = "https://files.pythonhosted.org/packages/70/25/65bd5b58ea4bfdfc15cde02cb5365f89ef8ab8b2adfb8fe5c4bd4233382f/cffi-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5ecbd0499275d57506d397eebe1981cee87b47fcd9ef5c22cab7ed7644a39a94", size = 225703, upload-time = "2026-07-06T21:33:46.374Z" }, + { url = "https://files.pythonhosted.org/packages/dc/78/aa01ac599a8a4322533d45a1f9bc93b338276d2d59dabbe7c6d92a775c81/cffi-2.1.0-cp314-cp314t-win32.whl", hash = "sha256:7d034dcffa09e9a46c93fa3a3be402096cb5354ac6e41ab8e5cc9cd8b642ad76", size = 182857, upload-time = "2026-07-06T21:33:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/b9/26/d00496b22de4d4228f32dde94ad996f350c8aad676d63bcca0743c8dea4d/cffi-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0582a58f3051372229ca8e7f5f589f9e5632678208d8636fea3676711fdf7fe5", size = 194065, upload-time = "2026-07-06T21:33:48.953Z" }, + { url = "https://files.pythonhosted.org/packages/d5/dd/0c7dbf815a579ff005008a2d815a55d6bb047c349eef536d9dc53d3f0a8d/cffi-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:510aeeeac94811b138077451da1fb18b308a5feab47dd2b603af55804155e1c8", size = 186404, upload-time = "2026-07-06T21:33:50.309Z" }, + { url = "https://files.pythonhosted.org/packages/55/c7/8c8c50cb11c6750051daf12164098a9a6f027ac4356967fd4d800a07f242/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:2e9dabb9abcb7ad15938c7196ad5c1718a4e6d33cc79b4c0209bdb64c4a54a5c", size = 194121, upload-time = "2026-07-06T21:33:56.109Z" }, + { url = "https://files.pythonhosted.org/packages/99/e2/67680bf19a6b60d2bb7ff83baefa2a4c3d2d7dc0f3277034b802e1fc504c/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:37f525a7e7e50c017fdebe58b787be310ad59357ae43a053943a6e1a6c526001", size = 196820, upload-time = "2026-07-06T21:33:57.288Z" }, + { url = "https://files.pythonhosted.org/packages/ed/da/4bbe583a3b3a5c8c60892124fe17f3fa3656523faf0d3484eae90f091853/cffi-2.1.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:95f2954c2c9473d892eca6e0409f3568b37ab62a8eedb122461f73cc273476e3", size = 184936, upload-time = "2026-07-06T21:33:58.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/4b/1f4c36ab273980d7aa75bb126ea4f8971f24a96108acad3a0a084028c57b/cffi-2.1.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:cdf2448aab5f661c9315308ec8b93f4e8a1a67a3c733f8631067a2b67d5913dc", size = 185045, upload-time = "2026-07-06T21:34:00.085Z" }, + { url = "https://files.pythonhosted.org/packages/ef/c3/ad299dc38f3583f8d916b299f028af418a9ec98bc695fcbebeae7420691c/cffi-2.1.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:90bec57cf82089383bd06a605b3eb8daebf7e5a668520beaf6e327a83a947699", size = 222342, upload-time = "2026-07-06T21:34:01.814Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d8/df4543cc087245044ed02ef3ad8e0a26619d0075ac7a77a12dc81177851b/cffi-2.1.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6274dcb2d15cef48daa73ed1be5a40d501d74dccd0cd6db364776d12cb6ba022", size = 210073, upload-time = "2026-07-06T21:34:03.255Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0e/fac738d73728c6cea2a88a2883dca54892496cbba88a1dc1f2909cb8a6f5/cffi-2.1.0-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2b71d409cccee78310ab5dec549aed052aaea483346e282c7b02362596e01bb0", size = 208551, upload-time = "2026-07-06T21:34:04.433Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3f/0b04a700dd64f465c93020253a793a82c9b4dff9961f48facd0df945d9b8/cffi-2.1.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7d3538f9c0e50670f4deb93dbb696576e60590369cae2faf7de681e597a8a1f1", size = 221649, upload-time = "2026-07-06T21:34:06.157Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/b7379a5704c79eda57ce075869ba70a0368d1c850f803b3c0d078d39dcaf/cffi-2.1.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:8f9ec95b8a043d3dfbc74d9abc6f7baf524dd27a8dc160b0a32ff9cdab650c28", size = 225203, upload-time = "2026-07-06T21:34:07.489Z" }, + { url = "https://files.pythonhosted.org/packages/5a/02/d5e6c43ea85c41bda2a184a3418f195fe7cf602967a8d2b94e085b83deef/cffi-2.1.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:af5e2915d41fe6c961694d7bfdc8562942638200f3ce2765dfb8b745cf997629", size = 223263, upload-time = "2026-07-06T21:34:08.712Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d8/772b8259bf75749adffb1c546828978381fb516f60cf701f6c83daf60c85/cffi-2.1.0-cp315-cp315-win32.whl", hash = "sha256:0a42c688d19fca6e095a53c6a6e2295a5b050a8b289f109adab02a9e61a25de6", size = 177696, upload-time = "2026-07-06T21:34:26.355Z" }, + { url = "https://files.pythonhosted.org/packages/2f/dd/afa2191fc6d57fedd26e5844a2fe2fcc0bbfa00961bbaa5a41e4921e7cca/cffi-2.1.0-cp315-cp315-win_amd64.whl", hash = "sha256:bccbbb5ee76a61f9d99b5bf3846a51d7fca4b6a732fe46f89295610edaf41853", size = 187914, upload-time = "2026-07-06T21:34:27.58Z" }, + { url = "https://files.pythonhosted.org/packages/05/ef/6cd4f8c671517162379dc79cfae5aea9106bc38abb89628d5c16adf6a838/cffi-2.1.0-cp315-cp315-win_arm64.whl", hash = "sha256:8d35c139744adb3e727cd51b1a18324bbe44b8bd41bf8322bca4d41289f48eda", size = 183004, upload-time = "2026-07-06T21:34:28.905Z" }, + { url = "https://files.pythonhosted.org/packages/11/b6/12fc55092817a5faa26fb8c40c7f9d662e11a46ee248c137aafc42517d92/cffi-2.1.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:f9912624a0c0b834b7520d7769b3644453aabc0a7e1c839da7359f050750e9bc", size = 188378, upload-time = "2026-07-06T21:34:09.926Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2e/cdac88979f295fde5daa69622c7d2111e56e7ceb94f211357fbe452339e4/cffi-2.1.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:df92f2aba50eb4d96718b68ef76f2e57a57b54f2fa62333496d16c6d585a85ca", size = 188319, upload-time = "2026-07-06T21:34:11.101Z" }, + { url = "https://files.pythonhosted.org/packages/e0/27/1d0b408497e41a74795af122d7b603c418c5fed0171450f899afd04e594f/cffi-2.1.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0520e1f4c35f44e209cbbb421b67eec42e6a157f59444dfb6058874ff3610e5d", size = 223904, upload-time = "2026-07-06T21:34:12.606Z" }, + { url = "https://files.pythonhosted.org/packages/8b/31/e115c985105dd7ffb32444505f18ceb874bb42d992af05d5dced7ecf1980/cffi-2.1.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3681e031db29958a7502f5c0c9d6bbc4c36cb20f7b104086fa642d1799631ff8", size = 211554, upload-time = "2026-07-06T21:34:13.987Z" }, + { url = "https://files.pythonhosted.org/packages/5a/67/9e6e09409336d9e515c58367e7cfcf4f89df06ad25252675595a58eb59d5/cffi-2.1.0-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:762f99479dcb369f60ab9017ad4ab97a36a1dd7c1ee5a3b15db0f4b8659120cd", size = 210795, upload-time = "2026-07-06T21:34:15.972Z" }, + { url = "https://files.pythonhosted.org/packages/19/e5/d3cc82a4a0be7902af279c04181ad038449c096734464a5ae1de3e1401bd/cffi-2.1.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0611e7ebf90573a535ebdc33ae9da222d037853983e13359f580fab781ca017f", size = 223843, upload-time = "2026-07-06T21:34:17.509Z" }, + { url = "https://files.pythonhosted.org/packages/b9/65/b434abc97ce7cecc2c640fde160507c0ecc7e21544b483ba3325d2e2ea17/cffi-2.1.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:86cf8755a791f72c85dc287128cc62d4f24d392e3f1e15837245623f4a33cccc", size = 226773, upload-time = "2026-07-06T21:34:19.05Z" }, + { url = "https://files.pythonhosted.org/packages/b5/9f/d4dc66ca651eb1145a133314cda721abf13cfac3d28c4a0402263ae6ad75/cffi-2.1.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:ba00f661f8ba35d075c937174e27c2c421cec3942fd2e0ea3e66996757c0fdd9", size = 225719, upload-time = "2026-07-06T21:34:20.576Z" }, + { url = "https://files.pythonhosted.org/packages/68/5a/e536c528bc8057496c360c0978559a2dc45653f89dd6151078aa7d8fca1a/cffi-2.1.0-cp315-cp315t-win32.whl", hash = "sha256:cb96698e3c7413d906ce83f8ffd245ec1bd94707541f299d0ce4d6b0193e982b", size = 182760, upload-time = "2026-07-06T21:34:22.059Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0b/0ffe8b82d3875bced5fa1e7986a7a46b748262a40ab7f60b475eb9fb1bb3/cffi-2.1.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f146d154428a2523f9cc7936c02353c2459b8f6cf07d3cd1ee1c0a611109c5d5", size = 193769, upload-time = "2026-07-06T21:34:23.589Z" }, + { url = "https://files.pythonhosted.org/packages/a0/17/1073b53b68c9b5ca6914adf5f8bf55aacc2d3be102418c90700160ea8605/cffi-2.1.0-cp315-cp315t-win_arm64.whl", hash = "sha256:cbb7640ce37159548d2147b5b8c241f962143d4c71231431820783f4dc78f210", size = 186405, upload-time = "2026-07-06T21:34:24.857Z" }, ] [[package]] name = "charset-normalizer" -version = "3.4.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/26/08/0f303cb0b529e456bb116f2d50565a482694fbb94340bf56d44677e7ed03/charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d", size = 315182, upload-time = "2026-04-02T09:25:40.673Z" }, - { url = "https://files.pythonhosted.org/packages/24/47/b192933e94b546f1b1fe4df9cc1f84fcdbf2359f8d1081d46dd029b50207/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8", size = 209329, upload-time = "2026-04-02T09:25:42.354Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b4/01fa81c5ca6141024d89a8fc15968002b71da7f825dd14113207113fabbd/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790", size = 231230, upload-time = "2026-04-02T09:25:44.281Z" }, - { url = "https://files.pythonhosted.org/packages/20/f7/7b991776844dfa058017e600e6e55ff01984a063290ca5622c0b63162f68/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc", size = 225890, upload-time = "2026-04-02T09:25:45.475Z" }, - { url = "https://files.pythonhosted.org/packages/20/e7/bed0024a0f4ab0c8a9c64d4445f39b30c99bd1acd228291959e3de664247/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393", size = 216930, upload-time = "2026-04-02T09:25:46.58Z" }, - { url = "https://files.pythonhosted.org/packages/e2/ab/b18f0ab31cdd7b3ddb8bb76c4a414aeb8160c9810fdf1bc62f269a539d87/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153", size = 202109, upload-time = "2026-04-02T09:25:48.031Z" }, - { url = "https://files.pythonhosted.org/packages/82/e5/7e9440768a06dfb3075936490cb82dbf0ee20a133bf0dd8551fa096914ec/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af", size = 214684, upload-time = "2026-04-02T09:25:49.245Z" }, - { url = "https://files.pythonhosted.org/packages/71/94/8c61d8da9f062fdf457c80acfa25060ec22bf1d34bbeaca4350f13bcfd07/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34", size = 212785, upload-time = "2026-04-02T09:25:50.671Z" }, - { url = "https://files.pythonhosted.org/packages/66/cd/6e9889c648e72c0ab2e5967528bb83508f354d706637bc7097190c874e13/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1", size = 203055, upload-time = "2026-04-02T09:25:51.802Z" }, - { url = "https://files.pythonhosted.org/packages/92/2e/7a951d6a08aefb7eb8e1b54cdfb580b1365afdd9dd484dc4bee9e5d8f258/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752", size = 232502, upload-time = "2026-04-02T09:25:53.388Z" }, - { url = "https://files.pythonhosted.org/packages/58/d5/abcf2d83bf8e0a1286df55cd0dc1d49af0da4282aa77e986df343e7de124/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53", size = 214295, upload-time = "2026-04-02T09:25:54.765Z" }, - { url = "https://files.pythonhosted.org/packages/47/3a/7d4cd7ed54be99973a0dc176032cba5cb1f258082c31fa6df35cff46acfc/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616", size = 227145, upload-time = "2026-04-02T09:25:55.904Z" }, - { url = "https://files.pythonhosted.org/packages/1d/98/3a45bf8247889cf28262ebd3d0872edff11565b2a1e3064ccb132db3fbb0/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a", size = 218884, upload-time = "2026-04-02T09:25:57.074Z" }, - { url = "https://files.pythonhosted.org/packages/ad/80/2e8b7f8915ed5c9ef13aa828d82738e33888c485b65ebf744d615040c7ea/charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374", size = 148343, upload-time = "2026-04-02T09:25:58.199Z" }, - { url = "https://files.pythonhosted.org/packages/35/1b/3b8c8c77184af465ee9ad88b5aea46ea6b2e1f7b9dc9502891e37af21e30/charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943", size = 159174, upload-time = "2026-04-02T09:25:59.322Z" }, - { url = "https://files.pythonhosted.org/packages/be/c1/feb40dca40dbb21e0a908801782d9288c64fc8d8e562c2098e9994c8c21b/charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008", size = 147805, upload-time = "2026-04-02T09:26:00.756Z" }, - { url = "https://files.pythonhosted.org/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705, upload-time = "2026-04-02T09:26:02.191Z" }, - { url = "https://files.pythonhosted.org/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419, upload-time = "2026-04-02T09:26:03.583Z" }, - { url = "https://files.pythonhosted.org/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901, upload-time = "2026-04-02T09:26:04.738Z" }, - { url = "https://files.pythonhosted.org/packages/fb/73/77486c4cd58f1267bf17db420e930c9afa1b3be3fe8c8b8ebbebc9624359/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", size = 222742, upload-time = "2026-04-02T09:26:06.36Z" }, - { url = "https://files.pythonhosted.org/packages/a1/fa/f74eb381a7d94ded44739e9d94de18dc5edc9c17fb8c11f0a6890696c0a9/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", size = 214061, upload-time = "2026-04-02T09:26:08.347Z" }, - { url = "https://files.pythonhosted.org/packages/dc/92/42bd3cefcf7687253fb86694b45f37b733c97f59af3724f356fa92b8c344/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", size = 199239, upload-time = "2026-04-02T09:26:09.823Z" }, - { url = "https://files.pythonhosted.org/packages/4c/3d/069e7184e2aa3b3cddc700e3dd267413dc259854adc3380421c805c6a17d/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", size = 210173, upload-time = "2026-04-02T09:26:10.953Z" }, - { url = "https://files.pythonhosted.org/packages/62/51/9d56feb5f2e7074c46f93e0ebdbe61f0848ee246e2f0d89f8e20b89ebb8f/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", size = 209841, upload-time = "2026-04-02T09:26:12.142Z" }, - { url = "https://files.pythonhosted.org/packages/d2/59/893d8f99cc4c837dda1fe2f1139079703deb9f321aabcb032355de13b6c7/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", size = 200304, upload-time = "2026-04-02T09:26:13.711Z" }, - { url = "https://files.pythonhosted.org/packages/7d/1d/ee6f3be3464247578d1ed5c46de545ccc3d3ff933695395c402c21fa6b77/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", size = 229455, upload-time = "2026-04-02T09:26:14.941Z" }, - { url = "https://files.pythonhosted.org/packages/54/bb/8fb0a946296ea96a488928bdce8ef99023998c48e4713af533e9bb98ef07/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", size = 210036, upload-time = "2026-04-02T09:26:16.478Z" }, - { url = "https://files.pythonhosted.org/packages/9a/bc/015b2387f913749f82afd4fcba07846d05b6d784dd16123cb66860e0237d/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", size = 224739, upload-time = "2026-04-02T09:26:17.751Z" }, - { url = "https://files.pythonhosted.org/packages/17/ab/63133691f56baae417493cba6b7c641571a2130eb7bceba6773367ab9ec5/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", size = 216277, upload-time = "2026-04-02T09:26:18.981Z" }, - { url = "https://files.pythonhosted.org/packages/06/6d/3be70e827977f20db77c12a97e6a9f973631a45b8d186c084527e53e77a4/charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", size = 147819, upload-time = "2026-04-02T09:26:20.295Z" }, - { url = "https://files.pythonhosted.org/packages/20/d9/5f67790f06b735d7c7637171bbfd89882ad67201891b7275e51116ed8207/charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", size = 159281, upload-time = "2026-04-02T09:26:21.74Z" }, - { url = "https://files.pythonhosted.org/packages/ca/83/6413f36c5a34afead88ce6f66684d943d91f233d76dd083798f9602b75ae/charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", size = 147843, upload-time = "2026-04-02T09:26:22.901Z" }, - { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, - { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, - { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, - { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, - { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, - { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, - { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, - { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, - { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, - { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, - { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, - { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, - { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, - { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, - { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, - { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, - { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, - { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, - { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, - { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, - { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, - { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, - { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, - { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, - { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, - { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, - { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, - { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, - { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, - { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, - { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, - { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, - { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, - { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, - { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, - { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, - { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, - { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, - { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, - { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, - { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, - { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, - { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, - { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, - { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, - { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, - { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, - { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, - { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, - { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, - { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, - { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, - { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, - { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, - { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, - { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, - { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, - { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, - { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, - { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, - { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, - { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, +version = "3.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/2a/23f34ec9d04624958e137efdc394888716353190e75f25dd22c7a2c7a8aa/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", size = 152439, upload-time = "2026-07-07T14:34:58.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/81/8e983840c6e5b93b33c2ba81aa3d52c2e42f0e9a690ce7607a2e61da4a5c/charset_normalizer-3.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd6280cf040f233bd7d3407b743b4b4c74f70e8e1c4199cb112a62c941c0772a", size = 322240, upload-time = "2026-07-07T14:32:36.236Z" }, + { url = "https://files.pythonhosted.org/packages/de/d1/b4319dc3229d8272fba305e206fc0a148e2de8d4087917ce62ae6382f359/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa99adc8f081b475a12843953db36831eaf83ec33eb46a90629ca6a5de45a616", size = 216475, upload-time = "2026-07-07T14:32:38.142Z" }, + { url = "https://files.pythonhosted.org/packages/80/33/6c99c1b3e6b8bf730e1bc809b9a2608f224145069114c479a2e9e1494346/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c1225416b463483160e4af85d5fc3a9690ccb53fd4b1865a6437825f5ede3209", size = 238670, upload-time = "2026-07-07T14:32:39.658Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f4/ffbb83546e1f198ecc70ecd372b65cf2b50f9068b380abd67640f17a8e18/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:16d10d789dd9bcca1173c95af82c58433122564b7bc39385124be735a35cbe99", size = 233476, upload-time = "2026-07-07T14:32:41.155Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5f/b98b8da398637b551e427e7be922bdec19177dc54d6811dcdaa503f23aac/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bb41182d93ea91f60b4bc8fbf4c820c69ef8a12ab2d917f3f1834f1acad07e8", size = 223817, upload-time = "2026-07-07T14:32:42.592Z" }, + { url = "https://files.pythonhosted.org/packages/36/31/a276bb2e66243072a3fd06fdcab9cbb61a305b02143d70d2bda21d888fa8/charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:bcf74c1df76758a395bf0af608c04c82257523f55c9868b334f06270d0f2112b", size = 207974, upload-time = "2026-07-07T14:32:44.258Z" }, + { url = "https://files.pythonhosted.org/packages/5e/be/7ee4453d7e88dfbc4104ccd34900b9f2c7c17dac22881865fe0e82424a25/charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b5314963fce9b0b12743891de876e724997864ee22aa496f903f426c7e2fa5b2", size = 221655, upload-time = "2026-07-07T14:32:45.64Z" }, + { url = "https://files.pythonhosted.org/packages/1d/85/181c652953eb5276d198f375b1dd641047392050098100a3a02d6534f657/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e9701d0049d92c16703a42771b98d560b95248949f23f8cf7b4eddd201814fb9", size = 219229, upload-time = "2026-07-07T14:32:47.376Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e7/aaf6da33fc9f4691cda8f7efbc9f69179d3d39ec8a4799baf273ee1d8db0/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:65a7ff3f705e57d392f7261b6d0550fe137c3019477431f1c355e0db0a7d3e15", size = 209704, upload-time = "2026-07-07T14:32:48.855Z" }, + { url = "https://files.pythonhosted.org/packages/63/01/f2fb3bd3a73be48b173ee0c6aa8d2497af97d5663a8c4c4b491de4c62f7a/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79580094b00d1789d1f93ea55bc43cb2f611910c72235b7657f3482ddcc1b22d", size = 226243, upload-time = "2026-07-07T14:32:50.239Z" }, + { url = "https://files.pythonhosted.org/packages/c4/02/c57a22739fe05246b0b5783b3bfb6afaac4eebb46f3ececdfb2f048f780e/charset_normalizer-3.4.9-cp310-cp310-win32.whl", hash = "sha256:432786d3561e69aeeae6c7e8648964ce0ad05736120135601f87ac26b9c83381", size = 150935, upload-time = "2026-07-07T14:32:51.676Z" }, + { url = "https://files.pythonhosted.org/packages/37/8d/ca39a7559a4797505530d084fd3a49a2c959efbbbff146302fb7be4e3b35/charset_normalizer-3.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:8c041122946b7ba21bb32c45b1aa57b1be35527690aeb3c5c234521085632eee", size = 162314, upload-time = "2026-07-07T14:32:53.193Z" }, + { url = "https://files.pythonhosted.org/packages/01/da/a44bd7a13d426e69e4894557106cd58669097bfad4a8681123b618fbfc5d/charset_normalizer-3.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:375b83ed0aecfce76c16d198fbc21f3b11b337d68662bea0a995046682a11419", size = 153075, upload-time = "2026-07-07T14:32:54.554Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e3/85ec501f206fb049259288c1f3506e53876937fb00edb47009348e66756b/charset_normalizer-3.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5", size = 317075, upload-time = "2026-07-07T14:32:56.021Z" }, + { url = "https://files.pythonhosted.org/packages/c3/69/2a5385192e67175f7d8bd5ce4f57c24bc956439adeae5c13a99aa28a53d1/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a441ea71902098ffe78c5abe6c494f44160b4af614ed16c3d9a3b1d17fd8ee2", size = 213837, upload-time = "2026-07-07T14:32:57.78Z" }, + { url = "https://files.pythonhosted.org/packages/b3/46/03ddc7da576d814fe0a36dd1f0fd3258e95404b4b2e3c026b7923d7e133f/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:304b13570067b2547562e308af560b3963857b1fa90bd6afd978130130fe2d6a", size = 235503, upload-time = "2026-07-07T14:32:59.205Z" }, + { url = "https://files.pythonhosted.org/packages/4e/6e/de0229a7ef40f6f9d28a837eebf4ec47bdca5dab4e900c84f22919af636a/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4773092f8019072343a7447203308b176e10199920eb02d6195e81bbb3274c29", size = 229944, upload-time = "2026-07-07T14:33:00.803Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/49b9060e8418b14fb5cba9cf6bfb383111e2538a03a1fb18e66a95aeb3d5/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c", size = 221276, upload-time = "2026-07-07T14:33:02.199Z" }, + { url = "https://files.pythonhosted.org/packages/44/95/80282cce0fae9c3061203d723ee87da996aed79679e65d8935050ee7ca1f/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:c0323c9daef75ef2e5083624b4585018a0c9d5e3b40f607eed81a311270b934b", size = 205260, upload-time = "2026-07-07T14:33:03.698Z" }, + { url = "https://files.pythonhosted.org/packages/0c/74/2f62c8821b969ea3bd67cc2e6976834f48ca5d12664d2559ebcd9bcfbed7/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:871ff67ea1aad4dfd91736464934d56b32dac49f9fbe16cddba36198a7b3a0db", size = 217786, upload-time = "2026-07-07T14:33:05.12Z" }, + { url = "https://files.pythonhosted.org/packages/d9/8d/feabb82cb49fcad14515b1d7d1ca4787b0da7fc723a212bf89bc9e0fac52/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:67830fc78e67501f47bb950471b2dcb9b35b140084429318e862895a8e89c993", size = 216798, upload-time = "2026-07-07T14:33:06.629Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ff/c946d63bc3786d5b84d960b0f7ab7e25b828486a946b5aa997625bcaf6a6/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d92613ec25e43b05f042302531ec0f00b8445190e43325880cbd6ab7c2581da", size = 206429, upload-time = "2026-07-07T14:33:08.006Z" }, + { url = "https://files.pythonhosted.org/packages/af/ba/5e5007c370702f85d2ef75791fac7943ed41e080364a673b20142e430e3e/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:280081916dc341820640489a66e4696049401ef1cf6dd672f672e70ad915aca3", size = 223066, upload-time = "2026-07-07T14:33:09.783Z" }, + { url = "https://files.pythonhosted.org/packages/83/d5/9096aa3cf532dfad237861544eb47a0f20d5adbf1039760fed8eaae935d9/charset_normalizer-3.4.9-cp311-cp311-win32.whl", hash = "sha256:ac351b3b8014eead140e77e9717e2992c6bbe30b63bc3422422eb84865412e3d", size = 150456, upload-time = "2026-07-07T14:33:11.217Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a1/e29995109e455dc8eff8d0fac6ae509be39561318a7cfeac5d33ad029213/charset_normalizer-3.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:6366a16e1a25018694d6a5d784d09b046edc9eac40ea2b54065c3052672516a1", size = 161410, upload-time = "2026-07-07T14:33:12.743Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8d/1569f4d0032d6ba2a4fe4591c35bf87868c600c41a71eb5c2e1ffa8464c2/charset_normalizer-3.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:1d22856ffbe153a602df38e4a5464f0b748a54002e0d69ac6d2ad0a197cc99ec", size = 152649, upload-time = "2026-07-07T14:33:14.173Z" }, + { url = "https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0", size = 319300, upload-time = "2026-07-07T14:33:15.666Z" }, + { url = "https://files.pythonhosted.org/packages/ec/96/5d9364e3342d69f3a045e1777bc47c85c383e6e9466d561b33fdb419d1f9/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9", size = 215802, upload-time = "2026-07-07T14:33:17.031Z" }, + { url = "https://files.pythonhosted.org/packages/4b/4c/5361f9aa7f2cb58d94f2ab831b3d493f69efb1d239654b4744e3c09527cb/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44", size = 237171, upload-time = "2026-07-07T14:33:18.576Z" }, + { url = "https://files.pythonhosted.org/packages/50/78/ce342ca4ff30b2eb49fe6d9578df85974f90c67d294113e94efdd9664cbd/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9", size = 233075, upload-time = "2026-07-07T14:33:20.084Z" }, + { url = "https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd", size = 224256, upload-time = "2026-07-07T14:33:21.747Z" }, + { url = "https://files.pythonhosted.org/packages/87/3a/ad914516df7e358a81aae018caa5e0470ba827fa6d763b1d2e87d920a5f6/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84", size = 208784, upload-time = "2026-07-07T14:33:23.313Z" }, + { url = "https://files.pythonhosted.org/packages/d7/74/3c12f9755717dfe5c5c87da63f35d765fa0c00382ec26bf23f7fae34f2ba/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b", size = 219928, upload-time = "2026-07-07T14:33:24.814Z" }, + { url = "https://files.pythonhosted.org/packages/33/9a/895095b83e7907abd6d3d99aad3a38ad0d9686cc186cb0c94c24320fe63e/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde", size = 218489, upload-time = "2026-07-07T14:33:26.42Z" }, + { url = "https://files.pythonhosted.org/packages/a1/34/ef5c05f412f42520d7709b7d3784d19640839eb7366ded1755511585429f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39", size = 210267, upload-time = "2026-07-07T14:33:27.952Z" }, + { url = "https://files.pythonhosted.org/packages/83/dc/9b29fa4412b318bf3bfea985c35d67eb55e04b59a7c3f2237168b0e0be6f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62", size = 226030, upload-time = "2026-07-07T14:33:29.397Z" }, + { url = "https://files.pythonhosted.org/packages/0e/42/6dbc00b8cd16011691203e33570fa42ed5746599a2e878112d16eab403a3/charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642", size = 151185, upload-time = "2026-07-07T14:33:30.781Z" }, + { url = "https://files.pythonhosted.org/packages/80/cc/f920afd1a23c58ccd53c1d36085a71893a4737ff5e66e0371efab6809850/charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0", size = 162557, upload-time = "2026-07-07T14:33:32.176Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/0386d43a261ff4e4b30c5857af7df877254b46bec7b9d1b74b6bf969a90b/charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2", size = 152665, upload-time = "2026-07-07T14:33:33.711Z" }, + { url = "https://files.pythonhosted.org/packages/b2/06/97ec2aeae780b31d742b6352218b43841a6871e2564578ca522dce4a45c3/charset_normalizer-3.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614", size = 317688, upload-time = "2026-07-07T14:33:35.408Z" }, + { url = "https://files.pythonhosted.org/packages/d0/39/8ff066c672434225f8d25f8b739f992af250944392173dcc88362681c9bf/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698", size = 214982, upload-time = "2026-07-07T14:33:36.996Z" }, + { url = "https://files.pythonhosted.org/packages/92/8f/3a47a3667c83c2df9483d91644c6c107de3bf8874aa1793da9d3012eb986/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b", size = 236460, upload-time = "2026-07-07T14:33:38.536Z" }, + { url = "https://files.pythonhosted.org/packages/f1/60/b22cdbee7e4013dab8b0d7647fc6181120fbbbc8f7025c226d15bd5a47fc/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9", size = 232003, upload-time = "2026-07-07T14:33:40.059Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f8/72eb13dcabe7257035cea8aefd922caad2f110d252bf9f67c4c2ca763aee/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33", size = 223149, upload-time = "2026-07-07T14:33:41.631Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3e/faee8f9de92b14ee1198e9163252bb15efee7301b31256a3b6d9ebfdd0dd/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63", size = 207901, upload-time = "2026-07-07T14:33:43.209Z" }, + { url = "https://files.pythonhosted.org/packages/3a/25/45f30093ae27dd7b92a793b61882a38685f993700113ca36e0c9c14965e1/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0", size = 219176, upload-time = "2026-07-07T14:33:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/48/18/c8f397329c35e32f6a837e488986f4ae03bd2abebc453b48714991630c2f/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe", size = 217356, upload-time = "2026-07-07T14:33:46.192Z" }, + { url = "https://files.pythonhosted.org/packages/86/7e/5ce0bba863470fd1902d5e5843968951bddf38abe4742fc97116ef4598b3/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35", size = 209614, upload-time = "2026-07-07T14:33:47.705Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ef/2473d3c4d869155be4af1191111d59c4d5c4e0173026f7e85b176e23bf65/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8", size = 224991, upload-time = "2026-07-07T14:33:49.238Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a3/53ddae3db108a088156aa8ddfafd411ebbc1340f48c5573f697b27f69a39/charset_normalizer-3.4.9-cp313-cp313-win32.whl", hash = "sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9", size = 150622, upload-time = "2026-07-07T14:33:50.711Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ef/6953a77c7cf2c2ff9998e6f575ab3e380119f100223381565a4f94c1f836/charset_normalizer-3.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115", size = 161947, upload-time = "2026-07-07T14:33:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/6e/fb/d560d1d1555debbfe7849d9cac6145c1b537709d79576bf22557ed803b82/charset_normalizer-3.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012", size = 152594, upload-time = "2026-07-07T14:33:53.486Z" }, + { url = "https://files.pythonhosted.org/packages/7e/8d/496817fa0944239ecae662dd57ea765cfeaec6a735f9f025d4b7b72e7143/charset_normalizer-3.4.9-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380", size = 317253, upload-time = "2026-07-07T14:33:54.994Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/ef4a69ea338ad3c0deceea0f5f7d2380ae8b52132b06d652cb0d2cd86706/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9", size = 215898, upload-time = "2026-07-07T14:33:56.334Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e7/5ddfd76fc061eb52de219658a4aa431cbacadf0a0219c8854f00da50d289/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4", size = 236718, upload-time = "2026-07-07T14:33:57.9Z" }, + { url = "https://files.pythonhosted.org/packages/49/ba/768fa3f36048d81c477a0ce61f813bc1454d80917ccfe550abd9f44f5e24/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a", size = 232519, upload-time = "2026-07-07T14:33:59.811Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c4/b3e049d2aa3766180c78507110543d9d50894cc97f57de543f1be521dcdc/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046", size = 223143, upload-time = "2026-07-07T14:34:01.517Z" }, + { url = "https://files.pythonhosted.org/packages/19/79/55c32d06d76ae4feafe053f061f3e3ab70bcf19f4007797ce8c3efda7830/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81", size = 206742, upload-time = "2026-07-07T14:34:03.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/e0/47c079dd82d217c807479cd59ffd30af56307ea31c108b75758970459ad3/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917", size = 219191, upload-time = "2026-07-07T14:34:04.657Z" }, + { url = "https://files.pythonhosted.org/packages/42/ab/b9bc2e77d6b44a7e46ef62ec5cac1c9a6ba7b9135a5d560f002696ec9995/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41", size = 218328, upload-time = "2026-07-07T14:34:06.115Z" }, + { url = "https://files.pythonhosted.org/packages/f1/78/c9c71d599f5aa2d42bcdd35cbbd46d7f535351a57e40ff7d8e5a7e219401/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1", size = 207406, upload-time = "2026-07-07T14:34:07.554Z" }, + { url = "https://files.pythonhosted.org/packages/f6/39/c914445c321a845097ce4f6ac7de9a18228a77b766272125a1ce00d851eb/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf", size = 225157, upload-time = "2026-07-07T14:34:09.061Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f2/c0d4b8508565a36bc5c624e88ed297f5b0b1095011034d7f5b83a69908b5/charset_normalizer-3.4.9-cp314-cp314-win32.whl", hash = "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48", size = 151095, upload-time = "2026-07-07T14:34:10.901Z" }, + { url = "https://files.pythonhosted.org/packages/49/fd/a1d26144398c67486422a72bf5812cda22cb4ccfcd95a290fb41ceb4b8e2/charset_normalizer-3.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b", size = 162796, upload-time = "2026-07-07T14:34:12.47Z" }, + { url = "https://files.pythonhosted.org/packages/20/95/d75e82f8ce9fd323ebf059c16c9aadefb22a1ecde13b7840b35835e4886c/charset_normalizer-3.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519", size = 153334, upload-time = "2026-07-07T14:34:14.044Z" }, + { url = "https://files.pythonhosted.org/packages/00/5e/17398df3a139985ba9d11ed072531986f408c8fca952835ef1ab1820c02b/charset_normalizer-3.4.9-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198", size = 338848, upload-time = "2026-07-07T14:34:15.688Z" }, + { url = "https://files.pythonhosted.org/packages/cd/91/7253a32e86b7e1d1239b1b36ba6dd0f021a21107ab33054b53119cc083b9/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32", size = 223022, upload-time = "2026-07-07T14:34:17.248Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/2e64bd2be10e89c61e57ebe6a93fd98ae88eb7ebe414b5121f22c96c69eb/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632", size = 241590, upload-time = "2026-07-07T14:34:18.813Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ef/d96ec496cfea0c21db43b0ad03891308b02388d054cc902cf0e5a1ad6a88/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf", size = 239584, upload-time = "2026-07-07T14:34:20.52Z" }, + { url = "https://files.pythonhosted.org/packages/d4/ce/9af95f7876194bd7a14e3dfe4a4de2e0bff02666a3910d72beafd06cc297/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990", size = 230224, upload-time = "2026-07-07T14:34:22.189Z" }, + { url = "https://files.pythonhosted.org/packages/52/94/af74dde74a3996bd959c350709bfe50e297823d70a8c1cbd54b838880863/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d", size = 212667, upload-time = "2026-07-07T14:34:23.857Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f0/f1c4fe746c395922961b5916ed1d7d6e7d4c84851d19ed43cc89980ec953/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e", size = 227179, upload-time = "2026-07-07T14:34:25.586Z" }, + { url = "https://files.pythonhosted.org/packages/e4/56/6c745619ac397e8871e2bcd3cea1eec86b877488f33888b3aef5c3ed506e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c", size = 225372, upload-time = "2026-07-07T14:34:27.212Z" }, + { url = "https://files.pythonhosted.org/packages/78/ad/98aae8630ac71f16711968e38a5acfecce41b778bf2f0312851020f565a8/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2", size = 215222, upload-time = "2026-07-07T14:34:28.774Z" }, + { url = "https://files.pythonhosted.org/packages/f7/40/9593d54209765207a7f11073c06494c1721e4ca4a0a426c597679bf7f91e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534", size = 231958, upload-time = "2026-07-07T14:34:30.345Z" }, + { url = "https://files.pythonhosted.org/packages/b1/27/693ee5e8a18191eb38647360c51cd505013e2bd3b366aa43fd5344c21e3c/charset_normalizer-3.4.9-cp314-cp314t-win32.whl", hash = "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226", size = 155580, upload-time = "2026-07-07T14:34:31.884Z" }, + { url = "https://files.pythonhosted.org/packages/80/3f/bd97d3d9c613013d07cb7733d299385b41df37f0471310f5a73dc359f0b8/charset_normalizer-3.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177", size = 167620, upload-time = "2026-07-07T14:34:33.438Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c6/eee9dca4439b1061f76373f06ea855678cc4a64c1c3c90b50e479edbb8eb/charset_normalizer-3.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501", size = 158037, upload-time = "2026-07-07T14:34:35.018Z" }, + { url = "https://files.pythonhosted.org/packages/98/2b/f97f1c193fb855c345d678f5077d6926034db0722df74c8f057020e05a25/charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5", size = 64538, upload-time = "2026-07-07T14:34:56.993Z" }, ] [[package]] @@ -350,14 +360,14 @@ wheels = [ [[package]] name = "colorlog" -version = "6.10.1" +version = "6.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a2/61/f083b5ac52e505dfc1c624eafbf8c7589a0d7f32daa398d2e7590efa5fda/colorlog-6.10.1.tar.gz", hash = "sha256:eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321", size = 17162, upload-time = "2025-10-16T16:14:11.978Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/55/ba79756cb90c8d69d599d57785398ac87bba7b19c80e87f4e8a562197c93/colorlog-6.12.0.tar.gz", hash = "sha256:2a7924c1dadf18b22a0eb8b06d1c7b01d5341707ec1641eb6fcc4fde0c3e8e5f", size = 18151, upload-time = "2026-07-23T13:40:40.71Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/c1/e419ef3723a074172b68aaa89c9f3de486ed4c2399e2dbd8113a4fdcaf9e/colorlog-6.10.1-py3-none-any.whl", hash = "sha256:2d7e8348291948af66122cff006c9f8da6255d224e7cf8e37d8de2df3bad8c9c", size = 11743, upload-time = "2025-10-16T16:14:10.512Z" }, + { url = "https://files.pythonhosted.org/packages/d4/19/0b6647bf5e331521e55d2b63bfbdc210bd9cd605189273f03614a05f702d/colorlog-6.12.0-py3-none-any.whl", hash = "sha256:30d392604e9110045a2c2aeefc27d7a017abbab63f3a8aee594eac0801df784e", size = 12239, upload-time = "2026-07-23T13:40:39.562Z" }, ] [[package]] @@ -377,7 +387,7 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" } wheels = [ @@ -458,8 +468,8 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } wheels = [ @@ -538,100 +548,100 @@ wheels = [ [[package]] name = "coverage" -version = "7.14.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b4/91/0a7c28934e50d8ac9a7b117712d176f2953c3170bccced5eaacfa3e96175/coverage-7.14.3.tar.gz", hash = "sha256:1a7563a443f3d53fdeb040ec8c9f7466aed7ca3dc5891aa09d3ca3625fa4387f", size = 924398, upload-time = "2026-06-22T23:10:25.584Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/bd/b01188f0de73ee8b6597cf20c63fccd898ad31405772f15165cb61a62c00/coverage-7.14.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:360bec1f58e7243e3405d3bdf7a1a8115aa9b448d54dc7cd6f7b7e0e9406b62e", size = 220378, upload-time = "2026-06-22T23:07:38.925Z" }, - { url = "https://files.pythonhosted.org/packages/33/eb/f7aa3cb46500b709070c8d12335446971ec8b8c2ea155fea05d2000b4b1f/coverage-7.14.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed68faa5e85de2f3e400bc3f122e5c82735a58c8bb24b9f63a2215954ba17b2d", size = 220895, upload-time = "2026-06-22T23:07:41.536Z" }, - { url = "https://files.pythonhosted.org/packages/c3/c0/b41b8499fc9060ca40ad2a197d301155be1ead398f0f0bfdb27b2b4a660f/coverage-7.14.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:830c1fca669c572dec37ce9c838224ee45aac5be0f6961edf871e82e49d6537c", size = 247631, upload-time = "2026-06-22T23:07:43.244Z" }, - { url = "https://files.pythonhosted.org/packages/da/bb/e9ecea1307c6a549c223842cccbd5d55193cc27b82f26338782d4355047c/coverage-7.14.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a64caee2193563601dbaaa55fe2dcf597debef04a2f8f1fa8a07aa4bb7ac7a1e", size = 249460, upload-time = "2026-06-22T23:07:45.147Z" }, - { url = "https://files.pythonhosted.org/packages/59/cb/3821542809b7b726296fd364ed1c23d10a5770f1469957010c3b4bc5d408/coverage-7.14.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0096fd7559178f0cc9cf088f2dbd2a02ef85bacaa69732c633517286b4494610", size = 251324, upload-time = "2026-06-22T23:07:46.875Z" }, - { url = "https://files.pythonhosted.org/packages/76/27/f34f66f0ff152189ccc7b3f0582cf7909e239cb3b8c214362ed2149719b8/coverage-7.14.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6197e5a00183c11a8ce7c6abd18be1a9189fd8399084ffc95196f4f0db4f2137", size = 253237, upload-time = "2026-06-22T23:07:48.352Z" }, - { url = "https://files.pythonhosted.org/packages/22/81/aa363fa95d14fc892bd5de80edadc8d7cce584a0f6376f6336e492618e67/coverage-7.14.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7dfe427045520d6abca33687dfef767b4f635015893a1816c5decb12eb72ce18", size = 248344, upload-time = "2026-06-22T23:07:49.896Z" }, - { url = "https://files.pythonhosted.org/packages/66/fe/dc8a149441a3fea611cbbaf46bb12099adbe08f69903df1794581b0504b8/coverage-7.14.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9a3f142070eb7b82fc4085a55d887396f9c4e21250bccebe2ba22502c45b9647", size = 249365, upload-time = "2026-06-22T23:07:51.464Z" }, - { url = "https://files.pythonhosted.org/packages/f8/a2/0004127deee122e020be24a4d86ce72fa14ae28198811b945aabf91293b5/coverage-7.14.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64b2055bb6e0dc945af35cdeceb3633e6ed9273475ef3af85592410fd6803803", size = 247369, upload-time = "2026-06-22T23:07:53.064Z" }, - { url = "https://files.pythonhosted.org/packages/1e/72/3654c004f4df4f0c5a9643d9abaed5b26e5d3c1d0ecabe788786cb425efa/coverage-7.14.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1551b4caac3e3ec9f2bfcec6bf3776e01c0edbdd2e240431a50ca1a1aac72c27", size = 251182, upload-time = "2026-06-22T23:07:54.789Z" }, - { url = "https://files.pythonhosted.org/packages/a5/2f/7bdcdf1e7c4d0632648852768063c25582a0a747bb5f8036a04e211e7eb7/coverage-7.14.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:583d50d59142f8549470bd6390471d0fe8b8c8d69d6a0f28ac71e05380cef640", size = 247639, upload-time = "2026-06-22T23:07:56.254Z" }, - { url = "https://files.pythonhosted.org/packages/03/dc/0e01b071f69021d262a51ce39345dd6bc194465db0acfc7b34fd89e6b787/coverage-7.14.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0bb8a6bc7015efdf8a928753b25da1b9ca2d6f24ef04d2ee0688e486f32aae7", size = 248242, upload-time = "2026-06-22T23:07:57.692Z" }, - { url = "https://files.pythonhosted.org/packages/1c/51/08279e6ebe3479bf705db5fdc1a968e44ba1567e4cbc567f76b45f5e646e/coverage-7.14.3-cp310-cp310-win32.whl", hash = "sha256:d48400185564042287dc487c1f016a3397f18ab4f4c5d5ec36edc218f7ffa35b", size = 222431, upload-time = "2026-06-22T23:07:59.094Z" }, - { url = "https://files.pythonhosted.org/packages/40/2f/5c56670781fee5722ef0c415a74750c9a033bfacdb9d07b1493a0308108d/coverage-7.14.3-cp310-cp310-win_amd64.whl", hash = "sha256:eadea7aba74e40adee867a8c0eec17b820b061d308a4b014f7a0e118c2b0aa61", size = 223059, upload-time = "2026-06-22T23:08:00.662Z" }, - { url = "https://files.pythonhosted.org/packages/f1/24/efb17eb94018dd3415d0e8a76a4786a866e8964aa9c50f033399d23939c2/coverage-7.14.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e574801e1d643561594aa021206c46d80b257e9853087090ba97bed8b0a509d3", size = 220501, upload-time = "2026-06-22T23:08:02.182Z" }, - { url = "https://files.pythonhosted.org/packages/76/93/32f1bfca6cdd34259c8af42820a034b7a28dfb44969a13ed38c17e0ba5b0/coverage-7.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f82b6bb7d75a2613e85d07cefa3a8c973d0544a8993337f6e2728e4a1e94c305", size = 221008, upload-time = "2026-06-22T23:08:03.701Z" }, - { url = "https://files.pythonhosted.org/packages/eb/88/0d0f974855ff905d15a64f7873d00bdc4182e2736267486c6634f4af293c/coverage-7.14.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a2335ea5fed26af2e831094964fa3f8fae60b45f7e37fcc2d3b615b2add3ad87", size = 251420, upload-time = "2026-06-22T23:08:05.211Z" }, - { url = "https://files.pythonhosted.org/packages/39/7f/117dd2ec65e4140576f8ef991d88220f9b806769f7a8c20e0550c0f924e2/coverage-7.14.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fbb8c3a98e779013786ae01d229662aeacbc77100efbd3f2f245219ace5af700", size = 253331, upload-time = "2026-06-22T23:08:06.672Z" }, - { url = "https://files.pythonhosted.org/packages/87/55/f0bd6d6538e3f16829fb8a44b6c0d2fe9da638bbfdd6a20f8b5da8f4fa81/coverage-7.14.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac082660de8f429ba0ea363595abb838998570b9a7546777c60f413ab902bbde", size = 255441, upload-time = "2026-06-22T23:08:08.208Z" }, - { url = "https://files.pythonhosted.org/packages/1e/98/aa71f7879019c846a8a9662579ea4484b0202cf1e252ffeed647075e7eca/coverage-7.14.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ac012839ff7e396030f1e94e10553a431d14e4de2ab65cb3acb72bbd5628ca2", size = 257398, upload-time = "2026-06-22T23:08:09.749Z" }, - { url = "https://files.pythonhosted.org/packages/f3/4f/5fd367e59844190f5965015d7bee899e67a89d13eb2760118479bf836f2f/coverage-7.14.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5952f8c1bda2a5347154450379316e6dfa4d934d62ca35f6784451e6f55074fb", size = 251558, upload-time = "2026-06-22T23:08:11.37Z" }, - { url = "https://files.pythonhosted.org/packages/8f/de/5383a6ee5a6376701fe07d980fa8e4a66c0c377fead16712720340d701a3/coverage-7.14.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8cf0f2509acb4619e2471a1951089054dd58ebea7a912066d2ea56dd4c24ca4a", size = 253134, upload-time = "2026-06-22T23:08:13.04Z" }, - { url = "https://files.pythonhosted.org/packages/01/99/09542b1a99f788e3daec7f0fadc288821e71aca9ea298d51bfa1ba79fed5/coverage-7.14.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2e41fd3aab806770008279a93879b0924b16247e09ab537c043d08bbca53b4ab", size = 251195, upload-time = "2026-06-22T23:08:14.606Z" }, - { url = "https://files.pythonhosted.org/packages/02/9d/722fe8c13f0fbb064491b9e8656e56a606286792e5068c47ca1042e773e8/coverage-7.14.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f0a47095963cfe054e0df178daca95aec21e680d6076da807c3add28dfe920f7", size = 254959, upload-time = "2026-06-22T23:08:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/fb/58/943627179ff1d82da9e54d0a5b0bb907bb19cf19515599ccd921de50b469/coverage-7.14.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a090cbf9521e78ffdb2fcf448b72902afe9f5923ff6a12d5c0d0120200348af9", size = 250914, upload-time = "2026-06-22T23:08:18.03Z" }, - { url = "https://files.pythonhosted.org/packages/a5/d4/803efcbf9ae5567454a0c71e983589529448e2704ee0da2dc0163d482f18/coverage-7.14.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d310baf69a4fbe8a098ce727e4808a34866ac718a6f759ae659cbd3221358bc", size = 251824, upload-time = "2026-06-22T23:08:19.704Z" }, - { url = "https://files.pythonhosted.org/packages/32/79/3f78ea9563132746eed5cecb75d2e576f9d8fec45a47242b5ae0950b82a3/coverage-7.14.3-cp311-cp311-win32.whl", hash = "sha256:74fdd718d88fe144f4579b8747873a07ec3f04cb837d5faec5a25d9e22fa31a8", size = 222594, upload-time = "2026-06-22T23:08:21.311Z" }, - { url = "https://files.pythonhosted.org/packages/85/22/9ebbc5a2ab42ac5d0eea1f48648629e1de9bbe41ec243ed6b93d55a5a53f/coverage-7.14.3-cp311-cp311-win_amd64.whl", hash = "sha256:cc96aa922e21d4bc5d5ed3c915cef27dfcbc13686f47d5e378d647fbfba655a2", size = 223073, upload-time = "2026-06-22T23:08:23.318Z" }, - { url = "https://files.pythonhosted.org/packages/71/af/69d5fcc16cb555153f99cec5467922f226be0369f7335a9506856d2a7bd0/coverage-7.14.3-cp311-cp311-win_arm64.whl", hash = "sha256:c66f9f9d4f1e9712eb9b1de5310f881d4e2188cfcba5065e1a8490f38687f2c4", size = 222617, upload-time = "2026-06-22T23:08:25.054Z" }, - { url = "https://files.pythonhosted.org/packages/bd/b0/8a911f6ffe6974dac4df95b468ab9a2899d0e59f0f99a489afeec39f00bc/coverage-7.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3d74ff26299c4879ce3a4d826f9d3d4d556fd285fde7bbce3c0ef5a8ab1cec24", size = 220672, upload-time = "2026-06-22T23:08:26.621Z" }, - { url = "https://files.pythonhosted.org/packages/36/16/0fc0cb52538783dbbae0934b834f5a58fd5354380ee6cad4a07b15dc845d/coverage-7.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:96150a9cf3468ea20f0bc5d0e21b3df8972c31480ef90fa7614b773cc6429665", size = 221035, upload-time = "2026-06-22T23:08:28.372Z" }, - { url = "https://files.pythonhosted.org/packages/77/e2/421ccfbb48335ac49e93301478cf5d623b0c2bf1c0cadd8e2b2fc6c0c710/coverage-7.14.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:27d07a46500ba23515b838dbcf52512026af04090755cf6cc64166d88c9b9a1a", size = 252540, upload-time = "2026-06-22T23:08:30.226Z" }, - { url = "https://files.pythonhosted.org/packages/06/c2/05b8c890097c61a7f4406b35396b997a635200ded0339eda83dfbe526c5f/coverage-7.14.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:621e13c6108234d7960aaf5762ab5c3c00f33c30c15af06dcbff0c73bf112727", size = 255274, upload-time = "2026-06-22T23:08:31.876Z" }, - { url = "https://files.pythonhosted.org/packages/dc/be/b6d9efe447f8ba3c3c854195f326bd64c54b907d936cd2fdebf8767ec72e/coverage-7.14.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b60ca6d8af70473491a15a343cbabab2e8f9ea66a4376e81c7aa24876a6f977", size = 256389, upload-time = "2026-06-22T23:08:33.843Z" }, - { url = "https://files.pythonhosted.org/packages/d4/3c/f26e50acc429e608bc534ac06f0a3c169019c798178ec5e9de3dbc0df9c9/coverage-7.14.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c90a7cdd5e380e1ce02f19792e2ac2fbfbf177e35a27e69fd3e873b30d895c0c", size = 258648, upload-time = "2026-06-22T23:08:35.481Z" }, - { url = "https://files.pythonhosted.org/packages/9e/a2/01c1fabf816c8e1dae197e258edf878a3d3ddc86fbda34b76e5794277d8f/coverage-7.14.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5d788e5fd55347eef06ca0732c77d04a264de67e8ff24631270cdff3767a60cf", size = 252949, upload-time = "2026-06-22T23:08:37.562Z" }, - { url = "https://files.pythonhosted.org/packages/89/c6/941166dd79c31fd44a13063780ae8d552eee0089a0a0930b9bdb7df554ed/coverage-7.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62c7f79db2851c95ef020e5d28b97afde3daf9f7febcd35b53e05638f729063f", size = 254310, upload-time = "2026-06-22T23:08:39.174Z" }, - { url = "https://files.pythonhosted.org/packages/10/31/80b1fd028201a961033ce95be3cd1e39e521b3762e6b4a1ac1616cb291e7/coverage-7.14.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:90f7608aeb5d9b60b523b9fb2a4ee1973867cc4865a3f26fe6c7577073b70205", size = 252453, upload-time = "2026-06-22T23:08:40.84Z" }, - { url = "https://files.pythonhosted.org/packages/5f/85/c3d9addd94c4b524f3f4af0232075f5fe7170ce99a1386edff803e5934db/coverage-7.14.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1e3b91f9c4740aeb571ecf82e5e8d8e4ab62d34fcb5a5d4e5baa38c6f7d2857c", size = 256522, upload-time = "2026-06-22T23:08:42.494Z" }, - { url = "https://files.pythonhosted.org/packages/91/14/e5a0575f73795af3a7a9ae13dadf812e17d32422896839987dc3f86947e1/coverage-7.14.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c946099774a7699de03cbd0ff0a64e21aed4525eed9d959adde4afe6d15758ef", size = 252023, upload-time = "2026-06-22T23:08:44.243Z" }, - { url = "https://files.pythonhosted.org/packages/38/9b/9652ee531937ce3b8a63a8896885b2b4a2d56adc30e53c9540c666286d88/coverage-7.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:16b206e521feb8b7133a45754643dead0538489cf8b783b90cf5f4e3299625fd", size = 253893, upload-time = "2026-06-22T23:08:46.113Z" }, - { url = "https://files.pythonhosted.org/packages/b1/05/42678841c8c38e4b08bdfc48269f5a16dfbf5806000fe6a89b4cece3c691/coverage-7.14.3-cp312-cp312-win32.whl", hash = "sha256:ea3169c7116eb6cdf7608c6c7da9ecfcb3da40688e3a510fac2d1d2bafd6dc35", size = 222734, upload-time = "2026-06-22T23:08:47.858Z" }, - { url = "https://files.pythonhosted.org/packages/df/87/07a4fcee55177a25f1b52331a8e92cf4f2c53b1a9c75ce2981fd59c684ad/coverage-7.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:7ea52fc08f007bcc494d4bb3df3851e95843d881860ba38fe2c64dc100db5e7d", size = 223266, upload-time = "2026-06-22T23:08:49.494Z" }, - { url = "https://files.pythonhosted.org/packages/aa/34/2b8b66a989282ea7b370beb49f50bab29470dc30bb0b03935b6b802782f7/coverage-7.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:8cec0ad652ec57790970d817490105bd917d783c2f7b38d6b58a0ca312e1a336", size = 222655, upload-time = "2026-06-22T23:08:51.766Z" }, - { url = "https://files.pythonhosted.org/packages/a9/83/7fefbf5df23ed2b7f489907564a7b34b9b07098128e12e0fdfa92626e456/coverage-7.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:47968988b367990ae4ab17523790c38cd125e02c6bfd379b6022be2d40bdc38c", size = 220699, upload-time = "2026-06-22T23:08:53.522Z" }, - { url = "https://files.pythonhosted.org/packages/31/e6/38c3653ff6d56d704b29241362387ca824e38e15b76fdcb7096538195790/coverage-7.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0ee68f5c34812780f3a7063382c0a9fcbb99985b7ddcdcaa626e4f3fb2e0783a", size = 221068, upload-time = "2026-06-22T23:08:55.571Z" }, - { url = "https://files.pythonhosted.org/packages/20/86/4f5c45d51c5cd10a128933f0fd235393c9146abbfd2ce2dfa68b3267ead3/coverage-7.14.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fa9e5c6857a7e80fa22ace5cf3550ae392bbfc322f1d8dd2d2d5a8be38cec027", size = 252060, upload-time = "2026-06-22T23:08:57.464Z" }, - { url = "https://files.pythonhosted.org/packages/82/50/dfce42eff2cecabcd5a9bbad5489449c87db3415f408d23ffee417ce01f6/coverage-7.14.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:98a0859b0e98e43e1178a9402e19c8127766b14f7109a374d976e5a62c0e5c73", size = 254657, upload-time = "2026-06-22T23:08:59.453Z" }, - { url = "https://files.pythonhosted.org/packages/ba/d2/639ceb1bc8038fd0d66768278d5dc22df3391918b8278c2a21aa2602a531/coverage-7.14.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69918344541ed9c8368566c2adc03c0e33d4550d7faa87d1b35e49b6a3286ea9", size = 255892, upload-time = "2026-06-22T23:09:01.291Z" }, - { url = "https://files.pythonhosted.org/packages/8b/96/002094a10e113512500dc1e10430a449417e17b0f90f7d496bcb820208b7/coverage-7.14.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b7f300ac92cd4b570724c8ffbbd0c130fee298d2447f41d5a3abf58976fae1de", size = 258026, upload-time = "2026-06-22T23:09:03.017Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ec/286a5d2fad9c4bee59bd724feeb7d5bf8303c6c9200b51d1dd945a9c72b0/coverage-7.14.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11a7ec9f97ab950f4c5af62229befc7faf208fdbc0116d3902d7e306cf2c5abd", size = 252285, upload-time = "2026-06-22T23:09:04.773Z" }, - { url = "https://files.pythonhosted.org/packages/d9/7d/a17753a0b12dd48d0d50f5fab079ad99d3be1eac790494d89f3a417ca0b9/coverage-7.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a571bd889cd36c5922ce8e42e059f9d37d02301531d11374afa4c87a578625d5", size = 254023, upload-time = "2026-06-22T23:09:06.513Z" }, - { url = "https://files.pythonhosted.org/packages/86/ef/a76c6ceba6a2c313f905310abf2701d534cada22d372db11731831e9e209/coverage-7.14.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:de76caefc8deabb0dd1678b6a980be97d14c8d87e213ac194dbf8b09e96d63fb", size = 251989, upload-time = "2026-06-22T23:09:08.382Z" }, - { url = "https://files.pythonhosted.org/packages/d9/39/353013a75fec0fb49f7553519f9d52b4441e902e5178c93f38eb6c07cedb/coverage-7.14.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d20a15c622194234161535459affa8f7905830391c9ccfa060d495dbfe3a1c7f", size = 256144, upload-time = "2026-06-22T23:09:10.369Z" }, - { url = "https://files.pythonhosted.org/packages/29/0e/613878555d734def11c5b20a2701a15cb3781b9e9ea749da27c5f436e928/coverage-7.14.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b488bd4b23397db62e7a9459129d01ff06a846582a732efd24834b24a6ada498", size = 251808, upload-time = "2026-06-22T23:09:12.057Z" }, - { url = "https://files.pythonhosted.org/packages/af/76/359c058c9cfdcf1e8b107663881225b03b364a320017eda24a2a66e55102/coverage-7.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a3693b4153394d265f44fb855fdc80e72403024d4d6f91c4871b334d028e4e0", size = 253579, upload-time = "2026-06-22T23:09:13.858Z" }, - { url = "https://files.pythonhosted.org/packages/1d/d9/4ba2f060933a30ebe363cef9f67a365b0a317e580c0d5d9169d56a73ef1c/coverage-7.14.3-cp313-cp313-win32.whl", hash = "sha256:338b19131ab1a6b767b462bfcbaa692e7ae22f24463e39d49b02a83410ff6b37", size = 222741, upload-time = "2026-06-22T23:09:15.636Z" }, - { url = "https://files.pythonhosted.org/packages/76/e8/196ebc25d8f34c06d43a6e9c8513c9266ef8dbf3b5672beb1a00cf5e29fa/coverage-7.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:b3d77f7f196abdef7e01415de1bce09f216189e83e58159cfeef2b92d0464994", size = 223283, upload-time = "2026-06-22T23:09:17.478Z" }, - { url = "https://files.pythonhosted.org/packages/7c/af/51d2aac6417523a286f10fb25f09eb9518a84df9f1151e93ff6871f34849/coverage-7.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:e6230e688c7c3e65cedd41a774eb4ec221adc6bfee13768231015b702d5e4150", size = 222678, upload-time = "2026-06-22T23:09:19.7Z" }, - { url = "https://files.pythonhosted.org/packages/61/56/14e3b97facbfa1304dd19e676e26599ad359f04714bed32f7f1c5a88efdc/coverage-7.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:605ab2b566a22bd94834529d66d295c364aba84afd3e5498285c7a524017b1fc", size = 220741, upload-time = "2026-06-22T23:09:21.616Z" }, - { url = "https://files.pythonhosted.org/packages/12/1d/db378b5cca433b90b893f26dab728b280ddd89f272a1fdfed4aeaa05c686/coverage-7.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3c2134809e80fac091bfed18a6991b5a5eb5df5ae32b17ac4f4f99864b73dd7", size = 221068, upload-time = "2026-06-22T23:09:23.452Z" }, - { url = "https://files.pythonhosted.org/packages/47/f0/3f8421b20d9c4fcd39be9a8ca3c3fda8bc204b44efbd09fede153afd3e2f/coverage-7.14.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c02efd507227bde9969cab0db8f48890eb3b5dcad6afac57a4792df4133543ce", size = 252117, upload-time = "2026-06-22T23:09:25.458Z" }, - { url = "https://files.pythonhosted.org/packages/27/ca/59ea35fb99743549ec8b37eff141ece4431fea590c89e536ed8032ef45cf/coverage-7.14.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1bb93c2aa61d2a5b38f1526546d95cf4132cb681e541a337bf8dfd092be816e5", size = 254622, upload-time = "2026-06-22T23:09:27.523Z" }, - { url = "https://files.pythonhosted.org/packages/c8/25/ec6de51ae7493b92a1cf74d1b763121c29636759167e2a593ba4db5881e4/coverage-7.14.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f502e948e03e866538048bba081c075caaa62e5bda6ea5b7432e45f587eb462a", size = 255968, upload-time = "2026-06-22T23:09:29.43Z" }, - { url = "https://files.pythonhosted.org/packages/5d/05/c8bfc77823f42b4664fb25842f13b567022f6f84a4c83c8ecbb16734b7cb/coverage-7.14.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9973ef2463f8e6cfb61a6324126bb3e17d67a85f22f58d856e583ea2e3ca6501", size = 258284, upload-time = "2026-06-22T23:09:31.397Z" }, - { url = "https://files.pythonhosted.org/packages/f6/15/1d1b242027124a32b26ef01f82018b8c4ef34ef174aa6aeba7b1eeef48e8/coverage-7.14.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9be4e7d4c5ca0427889f8f9d614bd630c2be741b1de7699bca3b2b6c0e41003e", size = 252143, upload-time = "2026-06-22T23:09:33.256Z" }, - { url = "https://files.pythonhosted.org/packages/74/b6/d2a9842fd2a5d7d27f1ac851c043a734a494ad75402c5331db3da79ed691/coverage-7.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a574912f3bde4b0619f6e97d01aa590b70998859244793769eb3a6df78ee56d3", size = 253976, upload-time = "2026-06-22T23:09:35.351Z" }, - { url = "https://files.pythonhosted.org/packages/fd/30/e1600ddf7e226db5558bb5323d2186fff00f505c4b764643ec89ce5d8175/coverage-7.14.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:e343fb086c9cd780b38622fea7c369acd64c1a0724312149b5d769c387a2b1f5", size = 251942, upload-time = "2026-06-22T23:09:37.313Z" }, - { url = "https://files.pythonhosted.org/packages/d9/2c/9159de64f9dd648e324328d588a44cfab1e331eb5259ce1141afe2a92dfb/coverage-7.14.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:3c68df8e61f1e09633fefc7538297145623957a048534368c9d212782aa5e845", size = 256220, upload-time = "2026-06-22T23:09:39.165Z" }, - { url = "https://files.pythonhosted.org/packages/91/67/b7f536cc2c124f48e91b22fbb741d2261f4e3d310faf6f76007f47566e5d/coverage-7.14.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3e5b550a128419373c2f6cec28a244207013ef15f5cbcff6a5ca09d1dfaaf027", size = 251756, upload-time = "2026-06-22T23:09:41.056Z" }, - { url = "https://files.pythonhosted.org/packages/dd/ec/f3718038e2d4860c715a55428377ca7f6c75872caf98cabd982e1d76967d/coverage-7.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2bfc4dd0a912329eccc7484a7d0b2a38032b38c40663b1e1ac595f10c457954b", size = 253413, upload-time = "2026-06-22T23:09:43.306Z" }, - { url = "https://files.pythonhosted.org/packages/b8/a5/91f11efeef89b3cc9b30461128db15b0511ef813ab889a7b7ab636b3a497/coverage-7.14.3-cp314-cp314-win32.whl", hash = "sha256:0423d64c013057a06e70f070f073cec4b0cbc7d2b27f3c7007292f2ff1d52965", size = 222946, upload-time = "2026-06-22T23:09:45.261Z" }, - { url = "https://files.pythonhosted.org/packages/58/fd/98ac9f524d9ec378de831c034dbdeb544ca7ef7d2d9c9996daf232a037fd/coverage-7.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:92c22e19ce64ca3f2ad751f16f14df1468b4c231bd6af97185063a9c292a0cb3", size = 223436, upload-time = "2026-06-22T23:09:47.177Z" }, - { url = "https://files.pythonhosted.org/packages/b4/a0/7cd612d650a772a0ae80144443406bf61981c896c3d57c9e6e79fb2cdbd1/coverage-7.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:41de778bd41780586e2b04912079c73089ab5d839624e28db3bdb26de638da92", size = 222861, upload-time = "2026-06-22T23:09:49.384Z" }, - { url = "https://files.pythonhosted.org/packages/55/57/017353fab573779c0d00448e47d102edd36c792f7b6f233a4d89a7a08384/coverage-7.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8427f370ca67db4c975d2a26acfc0e5783ca0b52444dbc50278ace0f35445949", size = 221474, upload-time = "2026-06-22T23:09:51.417Z" }, - { url = "https://files.pythonhosted.org/packages/69/92/90cf1f1a5c468a9c1b7ba2716e0e205293ad9b02f5f573a6de4318b15ba1/coverage-7.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d8e88f335544a47e22ae2e45b344772925ec65166555c958720d5ed971880891", size = 221738, upload-time = "2026-06-22T23:09:53.487Z" }, - { url = "https://files.pythonhosted.org/packages/a4/c0/4df964fa539f8399fd7679c09c472d73744de334686fd3f01e3a2465ce4e/coverage-7.14.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:beaab199b9e5ceaf5a225e16a9d4df136f2a1eae0a5c20de1e277c8a5225f388", size = 263101, upload-time = "2026-06-22T23:09:55.895Z" }, - { url = "https://files.pythonhosted.org/packages/06/76/e5d33b2576ae3bf2be2058cd1cae57774b61e400f2c3c58f3783dc2ffb4a/coverage-7.14.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3ff255799f5a1676c71c1c32ec01fd043aa09d57b3d95764b24992757184784", size = 265225, upload-time = "2026-06-22T23:09:57.904Z" }, - { url = "https://files.pythonhosted.org/packages/61/d2/e52419afe391a39ba27fdefaf0737d8e34bf03faef6ab3b3006545bbd0d0/coverage-7.14.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:878832eaac515b62decfa76965aed558775f86bf1fc8cca76993c0c84ae31aed", size = 267643, upload-time = "2026-06-22T23:09:59.938Z" }, - { url = "https://files.pythonhosted.org/packages/58/7a/f2625d8d5006b6b20fba5afaef00b24a763fe96476ea798a3076cbc1f84e/coverage-7.14.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:611e62cb9386096d81b63e0a05330750268617231e7bd598e1fe77482a2c58a5", size = 268762, upload-time = "2026-06-22T23:10:01.943Z" }, - { url = "https://files.pythonhosted.org/packages/7d/bf/908024006bba57127354d74e938954b9c3cd765cc2e0412dc9c37b415cda/coverage-7.14.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:02c41de2a88011b893050fc9830267d927a50a215f7ad5ec17349db7090ccf26", size = 262208, upload-time = "2026-06-22T23:10:03.954Z" }, - { url = "https://files.pythonhosted.org/packages/34/a0/d4f9296441b909817442fdb26bd77a698f08272ec683a7394b00eb2e47a0/coverage-7.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:526ce9721116af23b1065089f0b75046fe521e7772ab94b641cd66b7a0421889", size = 265096, upload-time = "2026-06-22T23:10:05.936Z" }, - { url = "https://files.pythonhosted.org/packages/e8/da/4ae4f3f4e477b56a4ce1e5c48a35eff38a94b50130ce5bdc897024741cfc/coverage-7.14.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e4ed44705ca4bead6fc977a8b741f2145608289b33c8a9b42a95d0f15aedbf4d", size = 262699, upload-time = "2026-06-22T23:10:07.973Z" }, - { url = "https://files.pythonhosted.org/packages/d8/7a/6927148073ff32856d78baa77b4ddc07a9be7e90020f9db0661c4ca523a1/coverage-7.14.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2415902f385a23dcc4ccd26e0ba803249a169af6a930c003a4c715eeb9a5444e", size = 266433, upload-time = "2026-06-22T23:10:10.145Z" }, - { url = "https://files.pythonhosted.org/packages/f7/a7/774f658dbe9c4c3f5daa86a87e0459ac3832e4e3cc67affe078547f727b9/coverage-7.14.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:b75ee850fc2d7c831e883220c445b035f2224de2ba6103f1e56dbd237ab913f7", size = 261547, upload-time = "2026-06-22T23:10:12.191Z" }, - { url = "https://files.pythonhosted.org/packages/3d/14/a0c18c0376c43cbf973f43ef6ca20019c950597180e6396232f7b6a27102/coverage-7.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dc9b4e35e7c3920e925ba7f14886fd5fbe481232754624e832ddba66c7535635", size = 263859, upload-time = "2026-06-22T23:10:14.492Z" }, - { url = "https://files.pythonhosted.org/packages/10/ac/43a3d0f460af524b131a6191805bc5d18b806ab4e828fbf82e8c8c3af446/coverage-7.14.3-cp314-cp314t-win32.whl", hash = "sha256:7b27c822a8161afbe48e99f1adfb098d270ae7e0f7d7b0555ce110529bdb69cc", size = 223250, upload-time = "2026-06-22T23:10:16.758Z" }, - { url = "https://files.pythonhosted.org/packages/3f/5f/d5e5c56b0712e96ce8f69fe7dbf229ff938b437bc50862743c8a0d2cea84/coverage-7.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:39e1dbbb6ff2c338e0196a482558a792a1de3aa64261196f5cdb3da016ad9cda", size = 224082, upload-time = "2026-06-22T23:10:19.23Z" }, - { url = "https://files.pythonhosted.org/packages/62/35/947cbd5be1d3bcbbdc43d6791de8a56c6501903311d42915ae06a82815f0/coverage-7.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:68520c90babfa2d560eca6d497921ed3a4f469623bd709733124491b2aa8ef3f", size = 223400, upload-time = "2026-06-22T23:10:21.24Z" }, - { url = "https://files.pythonhosted.org/packages/eb/e3/a0aa32bfa3a081951f60a23bc0e7b512891ef0eecda1153cf1d8ba36c6b1/coverage-7.14.3-py3-none-any.whl", hash = "sha256:fb7e18afb6e903c1a92401a2f0501ac277dca527bb9ca6fe1f691a8a0026a0e8", size = 212469, upload-time = "2026-06-22T23:10:23.405Z" }, +version = "7.15.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/d0/55fe630f4cf94e3fcba868240fad8c8cdd1f764e2a932f8926347e6ec4cd/coverage-7.15.2.tar.gz", hash = "sha256:3df60dc267f0a2ca23cb7a9ab1109c62b9335ffbf519fcfe167157c28c09b81d", size = 927741, upload-time = "2026-07-15T18:56:19.558Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/03/060ce69008ac97bbc01b1411b3e55b61f6f015659400b46749b662107831/coverage-7.15.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b5bd92ff1ec22e535eab0de75fa6db021992791f461a2aceb7822c625a1187d", size = 221284, upload-time = "2026-07-15T18:53:29.52Z" }, + { url = "https://files.pythonhosted.org/packages/fc/a3/d936e8b53edd9684100a6aefaf3fcabaa54728fe33324436c8d279c047aa/coverage-7.15.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:44826758cfe73fcd0e6af5deb4ba6d5417cc1d13df3acb35c93484a11160f846", size = 221799, upload-time = "2026-07-15T18:53:31.708Z" }, + { url = "https://files.pythonhosted.org/packages/ae/a3/ca234b06aec7ee28226f11d39a696b4481fe5eddfce8e03bf39979bb8ffb/coverage-7.15.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:09f5c6ec5901f667bd97dd140b5b9a2586b10efec66f46fb1e6d8135f8b95bdf", size = 248544, upload-time = "2026-07-15T18:53:33.212Z" }, + { url = "https://files.pythonhosted.org/packages/2b/89/dda79527bb7573ba91828b2fb91b3105d87378d6a2749ca0c0924ce0addd/coverage-7.15.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1d16e3a7104ea84f03e614611b3edbf6fb6892554b3ab0fe7fbb3f2b2ef04376", size = 250374, upload-time = "2026-07-15T18:53:34.683Z" }, + { url = "https://files.pythonhosted.org/packages/67/c6/c33755a34572f81f49a8c0cdf6b622f35ccb3238b136e1909daf0cdd4319/coverage-7.15.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d46e62cb35d91e6e2589fda6d28074426b0e276422b5d2ebef2c6b11dc60dbfd", size = 252239, upload-time = "2026-07-15T18:53:36.205Z" }, + { url = "https://files.pythonhosted.org/packages/b9/6f/dc341741b375be53a5baeee5b4bf0f0e525d38caed428f7932d23bb7bcb1/coverage-7.15.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dfd3db045e95960ae3683059571e597fda7cc610106a8916f77c5839048c1deb", size = 254150, upload-time = "2026-07-15T18:53:37.863Z" }, + { url = "https://files.pythonhosted.org/packages/e9/8d/966a18a5b195cb4e77b14c53f5f3dce22b5da05e6de7fafd1e08f2d2067a/coverage-7.15.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:affd532502d34c0472d0cdb181325c89f1d2c44992fef0c17e88e7b1576259a1", size = 249234, upload-time = "2026-07-15T18:53:39.394Z" }, + { url = "https://files.pythonhosted.org/packages/c5/8b/8b2e367496ab48484d48e79984fec76cdc1b7cb5d3a00ee799a5602e3ec9/coverage-7.15.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d17d7512151fedfcc64c1821a8977fc9be0dbf495754669afcab7b57abc98ae9", size = 250276, upload-time = "2026-07-15T18:53:41.027Z" }, + { url = "https://files.pythonhosted.org/packages/63/92/1199318a200eb6c8c6ce0192c892c8710ac791abbe0f35099294620bbfda/coverage-7.15.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e26ff680768b8095e8874aabe0e9d3a47a2a9f176a8340d05f8604c56457c23a", size = 248283, upload-time = "2026-07-15T18:53:42.557Z" }, + { url = "https://files.pythonhosted.org/packages/56/da/be284a55c5619bda891a89c27dfd59324a2c6a14d755cf6aac6960ceebeb/coverage-7.15.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:7e8f27131dc7cd53de2c137dd207b3720919320b3c20d499dc30aa9ee6173287", size = 252093, upload-time = "2026-07-15T18:53:44.271Z" }, + { url = "https://files.pythonhosted.org/packages/d4/53/ee112da833ddd77b73c6d781a98029b45b584b136615b4900ed0569f887e/coverage-7.15.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:728a33676d4c3f0db977990a4bd421dcaa3be3e53b5b6273036fff6666008e89", size = 248552, upload-time = "2026-07-15T18:53:45.7Z" }, + { url = "https://files.pythonhosted.org/packages/82/6a/802cfc802e9113494c80bf3f284cd4d72faeb1f24e244f61046af364f2ca/coverage-7.15.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:29c052f7c83ccfcc5c577eaae025d2e4a9bb80daf03c0ac31c996e83b000ce88", size = 249154, upload-time = "2026-07-15T18:53:47.256Z" }, + { url = "https://files.pythonhosted.org/packages/2c/65/529808e91d651147edae408fd9e894abc3b8cad7f3e594bbc36719a3e13a/coverage-7.15.2-cp310-cp310-win32.whl", hash = "sha256:1268ac8fb9ddcd783d3948dbabaf80a5d53bfdaa0575e873e2139a692f797443", size = 223334, upload-time = "2026-07-15T18:53:48.768Z" }, + { url = "https://files.pythonhosted.org/packages/68/0f/0e1829d7001130876dfbc0b4e1c737ea7c155b809e3e4a98a0aa268e2369/coverage-7.15.2-cp310-cp310-win_amd64.whl", hash = "sha256:9f4432898c4bf2fba0435bbe35dd4437d7264565e5a88a21f5b49d8662a6b629", size = 223959, upload-time = "2026-07-15T18:53:50.429Z" }, + { url = "https://files.pythonhosted.org/packages/7d/3a/54536704f507d4573bf9161c4d0dd3dd59b6d85e48c664e901b6844d8e33/coverage-7.15.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f1ec6f304b156669cfde653b4e9a953f5de87e247ea02ac599bce0ab2744036", size = 221414, upload-time = "2026-07-15T18:53:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d9/8ba925d29743e3577b21e4d8c11a702b76bc93c41e7fdfd1177af63d4b8d/coverage-7.15.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d3361879d736f469f45723c11ea1a5bbdaf1f6928f0e632c940378b5aa9b660", size = 221913, upload-time = "2026-07-15T18:53:53.682Z" }, + { url = "https://files.pythonhosted.org/packages/09/54/a855f3aa0187f2b431ade4e4791b77b56282cfb5d201c83ec26a31b5b36a/coverage-7.15.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c6a98d698f9e2c8008d0370ec7fc452ebfcc530002ae2d0061170d768b992589", size = 252332, upload-time = "2026-07-15T18:53:55.467Z" }, + { url = "https://files.pythonhosted.org/packages/8e/d3/13ac97b4370640ba3452fc8559b06cc2f479ce3ba4a0b632a73e44c38a7d/coverage-7.15.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d50dd325e18ec25bfcc10cd7f99b04df1ab9ec76b0918c260e60817ad0643dee", size = 254243, upload-time = "2026-07-15T18:53:57.055Z" }, + { url = "https://files.pythonhosted.org/packages/88/83/5eca144942d8d0659d3f55176517f4a59cdc65eefd17146a0770935a3ebd/coverage-7.15.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67d7602480a47bdf5b675635403625553ebaa70d5a62a657c035149fd401cea0", size = 256352, upload-time = "2026-07-15T18:53:58.83Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ba/d3db2e01a50fc88cdb4c0f19542bcf6f61489e34dc9aa3538413e2459a38/coverage-7.15.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cee0f89f4767a6057c8fbf168f8135f18be651300496086bd873e3189fed0487", size = 258313, upload-time = "2026-07-15T18:54:00.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/b3/aba83416e9177df28e5186d856c19158c59fc0e7e814aaa61a4a2354ad1b/coverage-7.15.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a29ec5305a7335aacee2d799e3422e91e1c8a12474986e2b3b07e315c91be82f", size = 252449, upload-time = "2026-07-15T18:54:02.456Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a5/4b00ecac0194431ab451b0f6710f8e2517d04cef60f821b14dec4637d575/coverage-7.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:48ccc6395958eda89093ecdc35644c86f23a8b23a7f4d44958812b721aad67c1", size = 254043, upload-time = "2026-07-15T18:54:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/75/b6/cfa209b4313ee7f1b34da47efcd789ea51c024ad35af390e00f5a3c10a2e/coverage-7.15.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:81f382c5a94b434ec1f6da607edb904c76d7212e618cd4d1bc9f97bed4120ef5", size = 252107, upload-time = "2026-07-15T18:54:06.745Z" }, + { url = "https://files.pythonhosted.org/packages/36/67/e8cac5a6954038c98d7fe7eb9802afe7ab3ecb637bb7cc00e69b4148b56d/coverage-7.15.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bbc808daf4f5cd567af8075ecc72d21c6dfef9a254709a621a84c217c935ebc0", size = 255873, upload-time = "2026-07-15T18:54:08.48Z" }, + { url = "https://files.pythonhosted.org/packages/2c/92/395cca9f330a86c3fe3471d73e2c102116c4c58fdc619dbbc125c6e93a54/coverage-7.15.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a4c46b247b5d4b78f613bd89fea926d32b25c6cc61a50bd1e99ba310348f3dad", size = 251826, upload-time = "2026-07-15T18:54:10.083Z" }, + { url = "https://files.pythonhosted.org/packages/51/60/3e91b20295439652424f426b7086ec5bf4fbe3f604c73eda22b986c4fd6b/coverage-7.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:094dd37f3ef7b2da8b068b583d1f4c40f91c65197e16c52a71962d5d537fc5db", size = 252735, upload-time = "2026-07-15T18:54:11.878Z" }, + { url = "https://files.pythonhosted.org/packages/a5/eb/8c07839005e5e3c6b3877d3a6e2a80ce766589f31dd2b6882b78d59a7b8c/coverage-7.15.2-cp311-cp311-win32.whl", hash = "sha256:a63b9e190711134d581c4d703df5df09851b1acf99792c7aacbbe9f41f0283c9", size = 223500, upload-time = "2026-07-15T18:54:13.525Z" }, + { url = "https://files.pythonhosted.org/packages/2e/98/59d83c257cd59f0fbaf9d9ddb26b744a576760dfd1ae16e516408894a02b/coverage-7.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:8bb9f4b4279187560796a4cdaca3b0a93dd97e48ee667df005f4ed9a97403688", size = 223973, upload-time = "2026-07-15T18:54:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/ea/09/2d285c8bef5c4f695d120c1c96dc11715638aa8e134069f210bb6a62a9fe/coverage-7.15.2-cp311-cp311-win_arm64.whl", hash = "sha256:8c726b232659cbd2ae57ade46509eb068c9bd7a06df9fcbff6fe484870006934", size = 223519, upload-time = "2026-07-15T18:54:16.803Z" }, + { url = "https://files.pythonhosted.org/packages/6a/50/eb5bf42e531611a9f8d272556b1ed4de503f84a91413584094487cf69f8f/coverage-7.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1adac78e5abc7c5438f7a209c9ca69d06542f0bf481d728b6989ea80b813fdf9", size = 221587, upload-time = "2026-07-15T18:54:18.439Z" }, + { url = "https://files.pythonhosted.org/packages/06/d1/da99af464c335d4e023a6efcd7ec30f63b88a43c93745154ab74ffb31cea/coverage-7.15.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b868acc62aa5de3be7a9d05c2333bf8359ca987e43f9cb30ff8fbda6a024ab73", size = 221943, upload-time = "2026-07-15T18:54:20.062Z" }, + { url = "https://files.pythonhosted.org/packages/5b/8a/13c42723d61ca447eafa18732e8141dd6a63f2732e1c7e1502c182dd88d7/coverage-7.15.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6f6966fc30e6f06ca8f98fb0ce51eda6b111b3ee8d066a8b1ec9e77fa06ab55d", size = 253450, upload-time = "2026-07-15T18:54:21.765Z" }, + { url = "https://files.pythonhosted.org/packages/d7/29/99021303f98fbdcb63504b4d07bea4cc025b9b2dd907c4f07c85d50a0dab/coverage-7.15.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:68af907f595ab01a78f794932ff3bdf929c316d3000810d38dbc247129e26f8b", size = 256187, upload-time = "2026-07-15T18:54:23.4Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a8/fd503715ed6ca9c5d742923aa5209257340b367a867b2ced0c7d4ba8a0b9/coverage-7.15.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:afa29e2eff3d5729267e2cb2fd4ce9d61c952932fb2694e34ccb5d9540c6a296", size = 257301, upload-time = "2026-07-15T18:54:25.183Z" }, + { url = "https://files.pythonhosted.org/packages/da/40/3f4b8fb409810036ebc2857d36adc0498c6e957b5df0290c5036b2e143f1/coverage-7.15.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bbf44513ceb1589e31948e20eafbde9deaface90e1a1afa5f5f77b4423d17ce6", size = 259562, upload-time = "2026-07-15T18:54:27.204Z" }, + { url = "https://files.pythonhosted.org/packages/0b/8a/9bdffbef47db77cce3d6b02a28f7e919b19f0106c4b080c2c2246040f885/coverage-7.15.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9deddf09eecb717b7f980414b43d90a5b22ff3967d2949ab29cb0aa83d9e9098", size = 253841, upload-time = "2026-07-15T18:54:29.134Z" }, + { url = "https://files.pythonhosted.org/packages/1b/1e/9031efde019d31a06646261fce6dfc5c3c74e951e27a71e5c9a424563178/coverage-7.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ae901f7e55ba405c84ee1cab3d3e962e4e871e4a2bcb9c90911adbd69b42ac5a", size = 255221, upload-time = "2026-07-15T18:54:31.142Z" }, + { url = "https://files.pythonhosted.org/packages/56/db/787acde872389fc84a9ef9d8cd1ccc658e391ab4cb5b28092a714426a394/coverage-7.15.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a0f47002c6eeb7c280228467a4cb0cc15ca2103a8421b986b2d3ec04a0f9bd8b", size = 253366, upload-time = "2026-07-15T18:54:32.886Z" }, + { url = "https://files.pythonhosted.org/packages/2f/9b/6f57bc4b93c842eef1695f8cdaf2318e35e7ba54f5ba80d84be213ab7858/coverage-7.15.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1cd7a5beb7af3e864a13b1f0fb26efd3695da43ef0daf71e586adfffaf34d5b2", size = 257434, upload-time = "2026-07-15T18:54:34.7Z" }, + { url = "https://files.pythonhosted.org/packages/88/26/b3186a21b2acc83e451118978905c81c7072c3333707804db09a78c096a2/coverage-7.15.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:97a5c5457a9fb1d6c4e06cfb5dc835871fbfb6a6a51addc9e925bdeff5ef7440", size = 252935, upload-time = "2026-07-15T18:54:36.548Z" }, + { url = "https://files.pythonhosted.org/packages/20/c2/c9f3376b2e717ea69ed7a6e9a5fcab968fb0b290db6cf4bd9a1fc7541b75/coverage-7.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0901cfe6c13bcd2302da4f83e884555d2a22bda6e4c476f09ef204ba20ca536e", size = 254807, upload-time = "2026-07-15T18:54:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e1/dfc15401f4a8aaeb486e1ba3e9e3c40522a6e38bd0ecf0b3f29cb8082957/coverage-7.15.2-cp312-cp312-win32.whl", hash = "sha256:b171bdd71cb7ff792bf32e376173b0ace7e7963e7e57c58dfc42063a6a7174cd", size = 223641, upload-time = "2026-07-15T18:54:40.103Z" }, + { url = "https://files.pythonhosted.org/packages/91/40/81b6d809d320cd366ec5bdf8176575e897dcb8efe7fb4b489ef9e93e4d13/coverage-7.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:582edc45c2040543fef83341be23c43024a3ab3ae0c2d8bc498a06282905ad40", size = 224172, upload-time = "2026-07-15T18:54:41.882Z" }, + { url = "https://files.pythonhosted.org/packages/ef/28/9f14ec438149f7de557f45518f09b4a7917b795cc37083aa7db482693f8c/coverage-7.15.2-cp312-cp312-win_arm64.whl", hash = "sha256:a638db90c61cd219aeee65e83a24fdaa57269a741ae0cf773309208ac862cee3", size = 223556, upload-time = "2026-07-15T18:54:43.674Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d5/f8c838e6b7282976f7c918884b792df7a0c42c5bba5d99c60ad2d221d56d/coverage-7.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1121caa19159a38b5463eaae4b1e1fde81e525b15ecc5e000cd5b1a108f743a8", size = 221606, upload-time = "2026-07-15T18:54:45.448Z" }, + { url = "https://files.pythonhosted.org/packages/bf/37/97c926376364f66298cc44893b89cdf17b8bc406376497c4061ae4b8a8ff/coverage-7.15.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a300c6934e0989c327b9e8a1e110329da4641149f872bbe9f70168be66da76c1", size = 221982, upload-time = "2026-07-15T18:54:47.341Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/a36050a6e83c2135ee0776f452ca3948224befc6d7f26acecc082d0c106a/coverage-7.15.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2617f8799d268fabdeef42a7e89ac3a23e1deee9025427db2df970f99a89a578", size = 252972, upload-time = "2026-07-15T18:54:49.2Z" }, + { url = "https://files.pythonhosted.org/packages/31/d3/06b5f1daf95f0f15ab05bd75f26ba5f3c8b33d0bb72f3aaa3cf41d1bad3a/coverage-7.15.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7dc2950a2992cd676d35c20ae63522836deeb034f08874699d14068710af3dc1", size = 255569, upload-time = "2026-07-15T18:54:51.098Z" }, + { url = "https://files.pythonhosted.org/packages/81/1c/9afb3f8de2b8d36960391c48559a2e3ff96594b58099f115921549ea8d0d/coverage-7.15.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e36686f7a442185db2400b3df171aac520869faf9deb59df687d28659eda2a6", size = 256806, upload-time = "2026-07-15T18:54:53.145Z" }, + { url = "https://files.pythonhosted.org/packages/64/d8/b989f96061a5e32d82fddd1b1b9ff48a7c8f8ae7606f0e80fd9de54b1e33/coverage-7.15.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d29ca7bd67af6e12e74632d65f026eabc1364da5c254494cd914446a28a3ef7", size = 258936, upload-time = "2026-07-15T18:54:55.015Z" }, + { url = "https://files.pythonhosted.org/packages/b8/fa/f99771f5110457c7b511c1935ca49ddf288218eaa84322e028b9334146ae/coverage-7.15.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:db9c8438057e5b0f6a22a0af99c0c1d26b57fbbdbd1be5861ddb8f897fcc3a2d", size = 253178, upload-time = "2026-07-15T18:54:57.527Z" }, + { url = "https://files.pythonhosted.org/packages/f6/96/c098a6044d119c751ceede7be91035fa8310170ec24a6523aff72f0a5793/coverage-7.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:63022c4c8dec1d0342f05c3ede99842fe3d007689acc45e86f123a1746e4a026", size = 254934, upload-time = "2026-07-15T18:54:59.41Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a2/1457b3a7a50c8d77500103b97a046db863e2f59a1cf6d2f814595f349885/coverage-7.15.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6c0be82b4d4aa5b2704e08518e2252f3e3d110164bcca826816801052e48a7aa", size = 252898, upload-time = "2026-07-15T18:55:01.338Z" }, + { url = "https://files.pythonhosted.org/packages/6c/0e/76958874c471ecfcdde0d2b2747bb2c61bdbf34a40636f4ce9db9923e643/coverage-7.15.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4510fb9cdf6bb02dfa6af0be4a534b8102d086e22e4a33f8836df663da3d660d", size = 257056, upload-time = "2026-07-15T18:55:03.243Z" }, + { url = "https://files.pythonhosted.org/packages/7c/7c/3d7c4e3bf58baa40327dc7edc2272b17cf02299366d52763db1b0ca1556a/coverage-7.15.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:42ec3d989421b174a2ab607c1539f24127ad362757b7f1c0c0d7a2993f7eb37b", size = 252718, upload-time = "2026-07-15T18:55:05.029Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b8/1cecffed9ce14fb25be9ba42d37b6bb61485c9a3ddd43cd3dde36b6087d8/coverage-7.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8f91bce78e32343af184c3b7fa28fcf5a9e2641f4b6623d392038f804939188", size = 254490, upload-time = "2026-07-15T18:55:06.889Z" }, + { url = "https://files.pythonhosted.org/packages/6c/2c/42984561bc7f4c045dca67516a0c50ee5ef8d84352dbeb5559dc86c4823e/coverage-7.15.2-cp313-cp313-win32.whl", hash = "sha256:434e68d531858205895eb0d74b73d20b84260de426387d53c422a5acda2cf050", size = 223647, upload-time = "2026-07-15T18:55:08.941Z" }, + { url = "https://files.pythonhosted.org/packages/41/9f/39c7c9245efc583beddf89a87683574e663ed93637f3afb6cd7b88405676/coverage-7.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:26c3b04a6377fd7c09800921fa934e3a17c0020439cd59df73e73ae1d4b6a78c", size = 224190, upload-time = "2026-07-15T18:55:10.789Z" }, + { url = "https://files.pythonhosted.org/packages/c7/de/3a2883cf8a213659280ef4b403059e17a9acaeb7fc7fd4105e1226ff2e6d/coverage-7.15.2-cp313-cp313-win_arm64.whl", hash = "sha256:3ed010aa1b69cda8e827aabfca9866216c980e2dca82ab9a78c5f83689964c8b", size = 223583, upload-time = "2026-07-15T18:55:12.678Z" }, + { url = "https://files.pythonhosted.org/packages/81/5f/aed265fd7a3551a394f36dfe41868aee709b7f95db4052205b4ad1563ac3/coverage-7.15.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:40f633c5c5fc783732f6312280122e859538fa24461235597c13d803ea9a108a", size = 221650, upload-time = "2026-07-15T18:55:14.527Z" }, + { url = "https://files.pythonhosted.org/packages/6b/2c/222ba12a545189017120f8eddfc1a0bd4616b47d5d4a8d99421edb2fe4c6/coverage-7.15.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:075560438765b7a2ef43bf7aa7758661b53d889df47f062a31bda6c1ade553a2", size = 221988, upload-time = "2026-07-15T18:55:16.674Z" }, + { url = "https://files.pythonhosted.org/packages/aa/38/304b5877ab46e6c290b4292cfcf3fe28245f0e5597cad7f6acc91fc7e0a4/coverage-7.15.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:25fd15dd40a0a2c51a500d664ca29053c09c3259d998407bf982b6e114696138", size = 253029, upload-time = "2026-07-15T18:55:18.856Z" }, + { url = "https://files.pythonhosted.org/packages/6c/58/821b533b8db9e44cf1d8a97bd525149ced40dde1d0093da02cb78e715244/coverage-7.15.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b9a6367e4aff723e8ee8190836836124284e8fcd4265e307c844010cfa074f3f", size = 255536, upload-time = "2026-07-15T18:55:21.027Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f2/7aa06604c389d32ea7f0a6a988359a7eafc3cd3f8e7bc2e88cd2fdf0b877/coverage-7.15.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9854ca62c152874b2060772503535be2e8f53f70b8aaa7686b094888d872f984", size = 256881, upload-time = "2026-07-15T18:55:23.125Z" }, + { url = "https://files.pythonhosted.org/packages/a2/4f/1ef342339c7916d0096bc5888cc0f653882cc7bc8f897d5cb89143287c9b/coverage-7.15.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:913b6c56e110da40e035bbd168353bf7aaa2544a5eaccea5d98a4629aac156c7", size = 259196, upload-time = "2026-07-15T18:55:25.099Z" }, + { url = "https://files.pythonhosted.org/packages/fe/f4/7ed055d7a9c5ec13b161773a115a5ccc6b0081d568c31fad830806306cc7/coverage-7.15.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aaccad4129d735a8a4d526f26929894c9a4e8ef7034566f210b176749d6906e3", size = 253036, upload-time = "2026-07-15T18:55:27.018Z" }, + { url = "https://files.pythonhosted.org/packages/14/79/ea82cca18c242a3a38b6c017da39726aa62dcb64aa635abf79b92009975c/coverage-7.15.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a164b50081fc7357331c4024ef4d17b78ba325f8380d05f5a69599a7e05257ee", size = 254887, upload-time = "2026-07-15T18:55:29.084Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ba/a136db3c0d9562b00e10b72540dbf3a33cd3bc5b95060c9308e247494623/coverage-7.15.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:bfd341ccf78128e72c094bc70cc25b3ef309c33c7c2c66ba3ed4309549e02de1", size = 252852, upload-time = "2026-07-15T18:55:31.184Z" }, + { url = "https://files.pythonhosted.org/packages/17/17/ea334246b16b7d059953fad6fdefa11e33c68efbd3fe37b1098120a1fac2/coverage-7.15.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:1473b3ba8e7ee0f076117b1a72c23f579a2b9e2bb742f48a8d86ea27ca93f91a", size = 257128, upload-time = "2026-07-15T18:55:33.163Z" }, + { url = "https://files.pythonhosted.org/packages/ed/c3/074fb66d46d607855f710876b117cbda562c5ab08363528e78820449f937/coverage-7.15.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:17c432b5f73ad52ef46fb06019f6fa7c66ce381961cf0f7dfd1d3a4bd3a98145", size = 252668, upload-time = "2026-07-15T18:55:35.063Z" }, + { url = "https://files.pythonhosted.org/packages/e1/c1/f620850ada9b36435921c9a3a8057013422b1d964eb4bf37fe138724d192/coverage-7.15.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:77f0ef5011df53a4bd1b35211ab122287f8d9b8d7aa1c4553e5c2deb24b1d446", size = 254325, upload-time = "2026-07-15T18:55:37.125Z" }, + { url = "https://files.pythonhosted.org/packages/cc/31/a729ca3689404493af82ef8e6ff70bd88bdda8da89aeef6ca9b387aeb2b4/coverage-7.15.2-cp314-cp314-win32.whl", hash = "sha256:f653e5d7248c1191ec988a85c72edeab46c3ff44f90639a4ed4874ec0be90243", size = 223844, upload-time = "2026-07-15T18:55:39.078Z" }, + { url = "https://files.pythonhosted.org/packages/c6/83/5d809dc808fb1698c671f3e372259bb9158e64b7ea526fc6ab7de64de9fe/coverage-7.15.2-cp314-cp314-win_amd64.whl", hash = "sha256:9911f31aad8906abe337c271343485cf20df5e70df5d2f57f9f136e7b55f26bc", size = 224331, upload-time = "2026-07-15T18:55:41.346Z" }, + { url = "https://files.pythonhosted.org/packages/16/4e/35e488548e952795829e129995c4174df33bf432b591d1aa42c8d9e4e7ad/coverage-7.15.2-cp314-cp314-win_arm64.whl", hash = "sha256:e38def96ad59853824c97953fdcd2c320a84ba3ce99b417db78af8bb6c3db635", size = 223760, upload-time = "2026-07-15T18:55:43.518Z" }, + { url = "https://files.pythonhosted.org/packages/ed/49/dd2c86cd6374038f6e415fb5bfb86db5218553209c081384a020369dee79/coverage-7.15.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:835ec4e20b45f0a7f63ed78f94065aca00de033403df8377bfe8b9c6abc0a7be", size = 222384, upload-time = "2026-07-15T18:55:45.569Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/173ff17a1c0808e5a438f549f6f145d5ac7528f2791310b63523e3200ac7/coverage-7.15.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7466cc7ab6dc0db871d264bf99e8779f0917ee63d40730af0552f71535a6e072", size = 222647, upload-time = "2026-07-15T18:55:47.544Z" }, + { url = "https://files.pythonhosted.org/packages/84/f8/b8cba872162356fb44ac79c10309d987206a4461e32072fc29228dad7331/coverage-7.15.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e370c12133095ff18432de8c044962be85a5a96d90c6fcbce8e17e76236d2328", size = 264013, upload-time = "2026-07-15T18:55:49.768Z" }, + { url = "https://files.pythonhosted.org/packages/ee/67/a807a7586d0b8cae485308ddd55756f0806c92f8e0b411bacbf23c48edf3/coverage-7.15.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fe41909c9515c3bfdb5f02c4d1f857dba322d9a9a1178069b91eea77889df63a", size = 266135, upload-time = "2026-07-15T18:55:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/ce/67/cd78771dc985f7e4ebdcc82b1a96d9a932af9e806f01f2f91a89f4c72e80/coverage-7.15.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6aa28cfb6488e5453b5b762d65f73aa586380f6693a04d58078ce228a29b06c0", size = 268555, upload-time = "2026-07-15T18:55:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/18/3e/10134cf81275188c58568f324fc74aedff32c63ca4d5bbc513a91944a6f0/coverage-7.15.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bcc0aae933921d03096f53b0b03eeb702129fd406dee59f08d2efacc68681fa5", size = 269674, upload-time = "2026-07-15T18:55:56.066Z" }, + { url = "https://files.pythonhosted.org/packages/75/4a/771b77de446cba985dc414bbc5844bd21604da05dbc044286df8318a48a7/coverage-7.15.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7c63387e21ab21f512c69c9756a8c7dadd322c7275edb064064433c9a09c3743", size = 263101, upload-time = "2026-07-15T18:55:58.107Z" }, + { url = "https://files.pythonhosted.org/packages/5f/b5/70a7011da15f4071943361183aefa27847f3e3aec4fd335f1cb3d3a622b1/coverage-7.15.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e55510bc98ae943cece9e667a6c0fe94c6a92913720dea34243657a17993d0c", size = 266007, upload-time = "2026-07-15T18:56:00.468Z" }, + { url = "https://files.pythonhosted.org/packages/b4/0d/f9547e804ce7ad49646ffeffac26699510efbe6c0f751b66fdc960c4e825/coverage-7.15.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2ff08701be2d1556fc78b326c80a3e8042da09352ecb3819105f8e386c8a3071", size = 263611, upload-time = "2026-07-15T18:56:02.615Z" }, + { url = "https://files.pythonhosted.org/packages/ac/59/f576a396659c0efd351f5c1544f67c3560e89c7761cabf7f65e412beeda5/coverage-7.15.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:38c9518b7103826c403a461544e3c2e77151e8676d06eaed85911a97e962584a", size = 267344, upload-time = "2026-07-15T18:56:04.622Z" }, + { url = "https://files.pythonhosted.org/packages/7c/5d/c2e4fce3579c0cb635024293f1a32bbe26df101b3e3a69f22243d1352b6c/coverage-7.15.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:dee88b1ed88587abd8c0269a1fc1f4cc77f7750d1dfde2869e2a123af420e67d", size = 262456, upload-time = "2026-07-15T18:56:06.641Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/956287d69436b66094bc4b57ac2da71e43bfd2a5524e958900b9f582fcf8/coverage-7.15.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fbeeeecea279727f8ac16c8e1133ddfeee793e985c86ae343d6a5ce744eef8c", size = 264771, upload-time = "2026-07-15T18:56:08.795Z" }, + { url = "https://files.pythonhosted.org/packages/2c/5a/6f979530c2734c575de77cf58f5f28d51f7123a94b5030fd9156fe5f363c/coverage-7.15.2-cp314-cp314t-win32.whl", hash = "sha256:cb0fddaa6884be6aae36ced9544b5e90f7d5f03845a2853bf47a14953a4e8688", size = 224151, upload-time = "2026-07-15T18:56:10.856Z" }, + { url = "https://files.pythonhosted.org/packages/54/7e/27f6b2a74d484742f4017553e710b01e396b23d809df3e95ca0bb9a2824b/coverage-7.15.2-cp314-cp314t-win_amd64.whl", hash = "sha256:77f091ea3a9cc611cd29f433565476bc1936c084ac8eee00ea0e7e70c27e4199", size = 224981, upload-time = "2026-07-15T18:56:12.928Z" }, + { url = "https://files.pythonhosted.org/packages/b1/48/284863423aa474240f6842bd00d680da22f4e6ea2e466618ef7c9c9e69a9/coverage-7.15.2-cp314-cp314t-win_arm64.whl", hash = "sha256:6fc448c377d6eeb00a47c673494bd9bae29280ca53987e1869e67ebedfe20658", size = 224294, upload-time = "2026-07-15T18:56:15.156Z" }, + { url = "https://files.pythonhosted.org/packages/ec/82/32e3bd191d498e64f6f911ad55d14006a0861e54869d2d32452326399e65/coverage-7.15.2-py3-none-any.whl", hash = "sha256:eb6bcae8d1a9d305351ecb108232441d11c5cfe9de840a04388ba5d2db8d735c", size = 213375, upload-time = "2026-07-15T18:56:17.305Z" }, ] [package.optional-dependencies] @@ -757,7 +767,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -784,20 +794,20 @@ wheels = [ [[package]] name = "fastjsonschema" -version = "2.21.2" +version = "2.22.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/b5/23b216d9d985a956623b6bd12d4086b60f0059b27799f23016af04a74ea1/fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de", size = 374130, upload-time = "2025-08-14T18:49:36.666Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/98/474719c58eddaf77fa443b063693e76d49db32bbe851bcbaf58d2700119f/fastjsonschema-2.22.1.tar.gz", hash = "sha256:0b83d1ce8d7845b959dcb20e1a5c3c8883b6541d9c52ab02cce5166b75ec805f", size = 382291, upload-time = "2026-07-27T13:31:08.515Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463", size = 24024, upload-time = "2025-08-14T18:49:34.776Z" }, + { url = "https://files.pythonhosted.org/packages/17/e1/62cc96341f01bdff2ba967441939178fcd1900d11ce7e6554d9954a5d7ec/fastjsonschema-2.22.1-py3-none-any.whl", hash = "sha256:cf377ff5c9a6f4f3125fb35f75a2c5767bd824ffbcf62c209a93cd48d1453999", size = 26239, upload-time = "2026-07-27T13:31:03.251Z" }, ] [[package]] name = "filelock" -version = "3.29.4" +version = "3.32.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e6/dc/be6cbe99670cd6e4ad387123647cb08e0c32975e223f82551e914c5568a6/filelock-3.29.4.tar.gz", hash = "sha256:10cdb3656fc44541cdf30652a93fb10ec6b05325620eb316bd26893e4201538a", size = 63028, upload-time = "2026-06-13T16:12:00.744Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/80/8232b582c4b318b817cf1274ba74976b07b34d35ef439b3eb948f98645a1/filelock-3.32.0.tar.gz", hash = "sha256:7be2ad23a14607ccc71808e68fe30848aeace7058ace17852f68e2a68e310402", size = 213757, upload-time = "2026-07-21T13:17:42.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/37/a065dc3bd6e49423a6532c642ca7378d3f467b1ef44c2800c937af7f9739/filelock-3.29.4-py3-none-any.whl", hash = "sha256:dac1648087d5115554850d113e7dd8c83ab2d38e3435dde2d4f163847e57b767", size = 42757, upload-time = "2026-06-13T16:11:59.582Z" }, + { url = "https://files.pythonhosted.org/packages/06/79/b4c714bef36bc4ec2beeae1e0c124f0223888cd8c6feb1cdc56038116920/filelock-3.32.0-py3-none-any.whl", hash = "sha256:d396bea984af47333ef05e50eae7eff88c84256de6112aea0ec48a233c064fe3", size = 97732, upload-time = "2026-07-21T13:17:41.55Z" }, ] [[package]] @@ -886,72 +896,72 @@ wheels = [ [[package]] name = "greenlet" -version = "3.5.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e2/f1/fbbfef6af0bad0548f09bc28948ea3c275b4edb19e17fc5ca9900a6a634d/greenlet-3.5.3.tar.gz", hash = "sha256:a61efc018fd3eb317eeca31aba90ee9e7f26f22884a79b6c6ec715bf71bb62f1", size = 200270, upload-time = "2026-06-26T19:28:24.832Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/a1/1f7f0c555f5858fd2906fe9f7b0a3554fddb85cb70df7a6aaec41dc292c2/greenlet-3.5.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:c180d22d325fb613956b443c3c6f4406eb70e6defc70d3974da2a7b59e06f48c", size = 285838, upload-time = "2026-06-26T18:21:05.167Z" }, - { url = "https://files.pythonhosted.org/packages/0a/29/be9f43ed61677a5759b38c8a9389248133c8c731bbfc0574ecdff66c99fc/greenlet-3.5.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:483d08c11181c83a6ce1a7a61df0f624a208ec40817a3bb2302714592eee4f04", size = 602342, upload-time = "2026-06-26T19:07:06.908Z" }, - { url = "https://files.pythonhosted.org/packages/b9/42/ba41c97ec36aa4b3ec25e5aa691d79561254805fad7f2f826dd6770587e2/greenlet-3.5.3-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1dae6e0091eae084317e411f047f0b7cb241c6db570f7c45fd6b900a274914ce", size = 615541, upload-time = "2026-06-26T19:10:04.909Z" }, - { url = "https://files.pythonhosted.org/packages/f5/c7/28747042e1df8a9cd120a1ebe15529fc4be3b486e13e8d551ff307a82412/greenlet-3.5.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bcd2d72ccd70a1ec68ba6ef93e7fbb4420ef9997dabc7010d893bd4015e0bec", size = 615675, upload-time = "2026-06-26T18:32:14.444Z" }, - { url = "https://files.pythonhosted.org/packages/cc/a8/b85525a6c8fba9f009a5f7c8df1545de8fb0f0bf3e0179194ef4e500317f/greenlet-3.5.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:73f152c895e09907e0dbe24f6c2db37beb085cd63db91c3825a0fcd0064124a8", size = 1575057, upload-time = "2026-06-26T19:09:00.264Z" }, - { url = "https://files.pythonhosted.org/packages/03/79/fb76edb218fe6735ab0edeba176c7ab80df9618f7c02ce4208979f3ae7db/greenlet-3.5.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8bdb43e1a1d1873721acab2be99c5befd4d2044ddfd52e4d610801019880a702", size = 1641692, upload-time = "2026-06-26T18:31:41.454Z" }, - { url = "https://files.pythonhosted.org/packages/6b/79/86fe3ee50ed55d9b3907eecd3208b5c3fe8a79515519aae98b4753c3fa1d/greenlet-3.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:0909f9355a9f24845d3299f3112e266a06afb68302041989fd26bd68894933db", size = 238742, upload-time = "2026-06-26T18:20:40.758Z" }, - { url = "https://files.pythonhosted.org/packages/51/58/5404031044f55afad7aad1aff8be3f22b1bed03e237cfeabbc7e5c8cfde0/greenlet-3.5.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:aca9b4ce85b152b5524ef7d88170efdff80dc0032aa8b75f9aaf7f3479ea95b4", size = 287424, upload-time = "2026-06-26T18:20:31.469Z" }, - { url = "https://files.pythonhosted.org/packages/b4/bf/1c65e9b94a54d547068fa5b5a8a06f221f3316b48908e08668d29c77cb50/greenlet-3.5.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f71be4920368fe1fabeeaa53d1e3548337e2b223d9565f8ad5e392a75ba23fc", size = 606523, upload-time = "2026-06-26T19:07:08.859Z" }, - { url = "https://files.pythonhosted.org/packages/b8/c7/b66baacc95775ad511287acb0137b95574a9ce5491902372b7564799d790/greenlet-3.5.3-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d77e67f65f98449e3fb83f795b5d0a8437aead2f874ca89c96576caf4be3af6", size = 618315, upload-time = "2026-06-26T19:10:06.055Z" }, - { url = "https://files.pythonhosted.org/packages/78/2b/28ed29463522fdbe4c15b1f63922041626a7478316b34ab4adda3f0a4aba/greenlet-3.5.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8540f1e6205bd13ca0ce685581037219ca54a1b41a0a15d228c6c9b8ad5903d7", size = 617381, upload-time = "2026-06-26T18:32:16.077Z" }, - { url = "https://files.pythonhosted.org/packages/2a/7b/ad04e9d1337fc04965dc9fc616b6a72cb65a24b800a014c011ec812f5489/greenlet-3.5.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7ef56fe650f50575bf843acde967b9c567687f3c22340941a899b7bc56e956a8", size = 1577771, upload-time = "2026-06-26T19:09:01.537Z" }, - { url = "https://files.pythonhosted.org/packages/d8/33/6c87ab7ba663f70ca21f3022aad1ffe56d3f3e0521e836c2415e13abcc3c/greenlet-3.5.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5121af01cf911e70056c00d4b46d5e9b5d1415550038573d744138bacb59e6b8", size = 1644048, upload-time = "2026-06-26T18:31:42.996Z" }, - { url = "https://files.pythonhosted.org/packages/1c/35/f0d8ee998b422cf8693b270f098e55d8d4ec8006b061b333f54f177d28d9/greenlet-3.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:0f41e4a05a3c0cb31b17023eff28dd111e1d16bf7d7d00406cd7df23f31398a7", size = 239137, upload-time = "2026-06-26T18:23:21.664Z" }, - { url = "https://files.pythonhosted.org/packages/fb/96/b9820295576ef18c9edc404f10e260ae7215ceaf3781a54b720ed2627862/greenlet-3.5.3-cp311-cp311-win_arm64.whl", hash = "sha256:ec6f1af59f6b5f3fc9678e2ea062d8377d22ac644f7844cb7a292910cf12ff44", size = 237630, upload-time = "2026-06-26T18:24:00.281Z" }, - { url = "https://files.pythonhosted.org/packages/5d/6e/4c37d51a2b7f82d2ff11bb6b5f7d766d9a011726624af255e843727627a3/greenlet-3.5.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:719757059f5a53fd0dde23f78cffeafcdd97b21c850ddb7ca684a3c1a1f122e2", size = 288685, upload-time = "2026-06-26T18:22:08.977Z" }, - { url = "https://files.pythonhosted.org/packages/7a/73/815dd90131c1b71ebdf53dbc7c276cafec2a1173b97559f97aba72724a87/greenlet-3.5.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:efa9f765dd09f9d0cdac651ffdf631ee59ec5dc6ee7a73e0c012ba9c52fbdf5b", size = 604761, upload-time = "2026-06-26T19:07:10.114Z" }, - { url = "https://files.pythonhosted.org/packages/9f/57/079cfe76bcef36b153b25607ee91c6fcb58f17f8b23c86bbbeabe0c88d72/greenlet-3.5.3-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7faba15ac005376e02a0384504e0243be3370ce010296a44a820feb342b505ab", size = 617044, upload-time = "2026-06-26T19:10:07.25Z" }, - { url = "https://files.pythonhosted.org/packages/37/87/b4d095775a3fb1bcafbb483fc206b27ebb785724c83051447737085dc54e/greenlet-3.5.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:87142215824be6ac05e2e8e2786eec307ccbc27c36723c3881959df654af6861", size = 614244, upload-time = "2026-06-26T18:32:17.594Z" }, - { url = "https://files.pythonhosted.org/packages/8a/70/7559b609683650fa2b95b8ab84b4ab0b26556a635d19675e12aa832d826d/greenlet-3.5.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:215275b1b49320987352e6c1b054acca0064f965a2c66992bed9a6f7d913f149", size = 1574210, upload-time = "2026-06-26T19:09:03.077Z" }, - { url = "https://files.pythonhosted.org/packages/ae/73/be55392074c60fc37655ca40fa6022457bfbf6718e9e342a7b0b41f96dd2/greenlet-3.5.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6b1b0eed82364b0e32c4ea0f221452d33e6bb17ae094d9f72aed9851812747ea", size = 1638627, upload-time = "2026-06-26T18:31:44.748Z" }, - { url = "https://files.pythonhosted.org/packages/14/40/c57489acf8e37d74e2913d4eff63aa0dba17acccc4bdeef874dde2dbbec9/greenlet-3.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:cde8adafa2365676f74a979744629589999093bc86e2484214f58e61df08902c", size = 239882, upload-time = "2026-06-26T18:23:27.518Z" }, - { url = "https://files.pythonhosted.org/packages/71/fd/6fea0e3d6600f785069481ee637e09378dd4118acdfd38ad88ae2db31c98/greenlet-3.5.3-cp312-cp312-win_arm64.whl", hash = "sha256:c4e7b79d83805475f0102008843f6eb45fd3bb0b2e88c774adab5fbaab27117d", size = 238211, upload-time = "2026-06-26T18:22:37.671Z" }, - { url = "https://files.pythonhosted.org/packages/9b/ff/a620267401db30a50cc8450ee90730e2d4a85658c055c0e760d4ed47fb13/greenlet-3.5.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:c8d87c2134d871df96ecdea9cec7cbaab286dadab0f56476e57aaf9e8ac11550", size = 287609, upload-time = "2026-06-26T18:21:14.724Z" }, - { url = "https://files.pythonhosted.org/packages/d6/fa/5401ac78021c826a25b6dde0c705e0a8f29b617509f9185a31dac15fbe1b/greenlet-3.5.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2d185dd1621757e70c3861cceffd5317ab4e7ed7eb09c82994828468527ade5", size = 607435, upload-time = "2026-06-26T19:07:11.412Z" }, - { url = "https://files.pythonhosted.org/packages/e9/76/1dc144a2e56e65d36405078ed774224375ea520a1870a6e46e08bb4ac7bf/greenlet-3.5.3-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1c514a468149bf8fbbab874188a3535cd8a48a3e353eb53a3d424296f8dbacd3", size = 619787, upload-time = "2026-06-26T19:10:08.396Z" }, - { url = "https://files.pythonhosted.org/packages/bf/87/c298cee62df1de4ad7fec32abda73526cff347fd143a6ed4ac369246668a/greenlet-3.5.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:915f887cf2682b66419b879423a2e072634aa7b7dce6f3ada4957cfced3f1e9a", size = 616786, upload-time = "2026-06-26T18:32:19.128Z" }, - { url = "https://files.pythonhosted.org/packages/9e/2e/e6f009885ed0705ccf33fe0583c117cfd03cde77e31a596dd5785a30762b/greenlet-3.5.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:766cfd421c13e450feb340cd472a3ed9957d438727b7b4593ad7c76c5d2b0deb", size = 1574316, upload-time = "2026-06-26T19:09:04.273Z" }, - { url = "https://files.pythonhosted.org/packages/ef/fe/43fd110b01e40da0adb7c90ac7ea744bef2d43dca00de5095fd2351c2a68/greenlet-3.5.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2ecda9ec22edf38fa389369eaed8c3d37c05f3c54e69f69438dbb2cc1de1458b", size = 1638614, upload-time = "2026-06-26T18:31:46.297Z" }, - { url = "https://files.pythonhosted.org/packages/0f/7c/062447147a61f8b4337b156fe70d32a165fcf2f89d7ca6255e572806705c/greenlet-3.5.3-cp313-cp313-win_amd64.whl", hash = "sha256:c82304750f057167ff60d188df1d0cc1764ce9567eadf03e6a7443bcedd0b30b", size = 239850, upload-time = "2026-06-26T18:21:54.613Z" }, - { url = "https://files.pythonhosted.org/packages/c7/7e/220a7f5824a64a60443fc03b39dfac4ea63a7fb6d481efa27eafa928e7f4/greenlet-3.5.3-cp313-cp313-win_arm64.whl", hash = "sha256:dc133a1569ee667b2a6ef56ce551084aeefd87a5acbc4736d336d1e2edc6cfc4", size = 238141, upload-time = "2026-06-26T18:22:48.507Z" }, - { url = "https://files.pythonhosted.org/packages/c3/93/43e116ee114b28737ba7e12952a0d4e2f55944d0f84e42bc91ba7192a3c9/greenlet-3.5.3-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:fd2e02fa07485778536a036222d616ab957b1d533f36b3ed98ce725d9c9d3117", size = 288202, upload-time = "2026-06-26T18:23:49.604Z" }, - { url = "https://files.pythonhosted.org/packages/82/2f/146d218299046a43d1f029fd544b3d110d0f175a09c715c7e8da4a4a345d/greenlet-3.5.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df0a0628d1597eb0897b62f55d1343f772405fd25f3b2a796c76874b0c2e22e8", size = 654096, upload-time = "2026-06-26T19:07:12.71Z" }, - { url = "https://files.pythonhosted.org/packages/a0/cc/04738cafb3f45fa991ea44f9de94c47dcec964f5a972300988a6751f49d9/greenlet-3.5.3-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ebd933a6adabc298bab47731a130fe6bfb888bd934eee37810f151159544540d", size = 666304, upload-time = "2026-06-26T19:10:09.503Z" }, - { url = "https://files.pythonhosted.org/packages/ce/aa/4e0dad5e605c270c784ab911c43da6adb136ccd4d81180f763ca429a723d/greenlet-3.5.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b9d501b40e80b70e32323c799dd9b420a5577a9601469d362ae1ffb690f3a7c", size = 663635, upload-time = "2026-06-26T18:32:20.802Z" }, - { url = "https://files.pythonhosted.org/packages/d1/50/13efdbea246fe3d3b735e191fec08fb50809f53cd2383ebe123d0809e44b/greenlet-3.5.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a1fad1d11e7d6aab184107baa8e4ece11ccba3ec9599cd7efa5ff4d70d43256a", size = 1621252, upload-time = "2026-06-26T19:09:05.647Z" }, - { url = "https://files.pythonhosted.org/packages/f7/22/c0a336ae4a1410fd5f5121098e5bfbf1865f64c5ef80b4b5412886c4a332/greenlet-3.5.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fad5aec764399f1b5cc347ad250a59660f20c8f8888ea6bae1f93b769cce1154", size = 1684824, upload-time = "2026-06-26T18:31:47.738Z" }, - { url = "https://files.pythonhosted.org/packages/7a/94/91aec0030bea75c4b3244251d0de60a1f3432d1ecb53ab6c437fb5c3ba61/greenlet-3.5.3-cp314-cp314-win_amd64.whl", hash = "sha256:7669aa24cf2a1041d6f7899575b494a3ab4cf68bfcc8609b1dc0be7272db835e", size = 240754, upload-time = "2026-06-26T18:22:15.669Z" }, - { url = "https://files.pythonhosted.org/packages/e5/06/68d0983e79e02138f64b4d303c500c27ddb48e5e77f3debb80888a921eae/greenlet-3.5.3-cp314-cp314-win_arm64.whl", hash = "sha256:5b4807c4082c9d1b6d9eed56fcd041863e37f2228106eef24c30ca096e238605", size = 239549, upload-time = "2026-06-26T18:22:42.996Z" }, - { url = "https://files.pythonhosted.org/packages/91/95/3e161213d7f1d378d15aa9e792093e9bfe01844680d04b7fd6e0107c9098/greenlet-3.5.3-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:271a8ea7c1024e8a0d7dd2be66dd66dda8a07193f41a17b9e924f7600f5b62be", size = 296389, upload-time = "2026-06-26T18:22:20.657Z" }, - { url = "https://files.pythonhosted.org/packages/00/92/715c44721abe2b4d1ae9abde4179411868a5bff312479f54e105d372f131/greenlet-3.5.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19131729ae0ddc3c2e1ef85e650169b5e37ee32e400f215f78b94d7b0d567310", size = 653382, upload-time = "2026-06-26T19:07:14.209Z" }, - { url = "https://files.pythonhosted.org/packages/a0/83/37a10372a1090a6624cca8e74c12df1a36c2dc36429ed0255b7fb1aeee23/greenlet-3.5.3-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1540dd8e5fc2a5aec40fbb98ef8e149fa47c89a4b4a1cf2575a14d3d1869d7a8", size = 659401, upload-time = "2026-06-26T19:10:10.876Z" }, - { url = "https://files.pythonhosted.org/packages/db/e2/d1509cad4207da559cc42986ecdd8fc67ad0d1bba2bf03023c467fd5e0f3/greenlet-3.5.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e81fa194a1d20967877bdf9c7794db2bc99063e5be36aee710c08f04c5bb087f", size = 656969, upload-time = "2026-06-26T18:32:22.272Z" }, - { url = "https://files.pythonhosted.org/packages/86/7d/eaf70de20aadca3a5884aec58362861c64ce45e7b277f47ed026926a3b89/greenlet-3.5.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:55cf4d777485d43110e47133cbba6d74a8885a87ec1227ef0267f9ee80c5aa21", size = 1617822, upload-time = "2026-06-26T19:09:06.893Z" }, - { url = "https://files.pythonhosted.org/packages/8a/f9/414d38fc400ae4350d4185eaad1827676f7cf5287b9136e0ed1cbbe20a7f/greenlet-3.5.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:12a248ba75f6a9a236375f52296c498c89ff1d8badf32deb9eca7abd5853f7da", size = 1677983, upload-time = "2026-06-26T18:31:49.396Z" }, - { url = "https://files.pythonhosted.org/packages/e4/15/7edb977e08f9bff702fe42d6c902702786ff6b9694058b4e6a2a6ac90e57/greenlet-3.5.3-cp314-cp314t-win_amd64.whl", hash = "sha256:efc6bd60ea02e085862c74a3ef64b147ffc6f1a5ea7d9f26e7a939943f68c1e3", size = 243626, upload-time = "2026-06-26T18:24:41.485Z" }, - { url = "https://files.pythonhosted.org/packages/2c/8a/93928dce91e6b3598b5e779e8d1fd6576a504640c58e78627077f6a7a91a/greenlet-3.5.3-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:ea03f2f04367845d6b58eeed276e1e56e51f0b97d8ad5a88a7d20a91dc9056cc", size = 288860, upload-time = "2026-06-26T18:22:48.07Z" }, - { url = "https://files.pythonhosted.org/packages/4f/ca/69db42d447a1378043e2c8f19c09cbbd1263371505053c496b49066d3d16/greenlet-3.5.3-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78dbef602fda6d97d957eb7937f70c9ce9e9527330347f8f6b6f9e554a9e7a47", size = 659747, upload-time = "2026-06-26T19:07:15.565Z" }, - { url = "https://files.pythonhosted.org/packages/a8/0b/af7ac2ef8dd41e3da1a40dda6305c23b9a03e13ba975ec916357b50f8575/greenlet-3.5.3-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f73857adb8fee13fa56c172bd11262f888c0c648f9fea113e777bb2c7904a81", size = 670419, upload-time = "2026-06-26T19:10:12.293Z" }, - { url = "https://files.pythonhosted.org/packages/51/1e/1d51640cacbfc455dbe9f9a9f594c49e4e244f63b9971a2f4764e46cc53d/greenlet-3.5.3-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:232fec92e823addaf02d9472cf7381e24a1d046a6ced1103c5caa4c21b9dfc1d", size = 668787, upload-time = "2026-06-26T18:32:24.298Z" }, - { url = "https://files.pythonhosted.org/packages/21/66/4030d5b0b5894500023f003bb054d9bb354dfbd1e186c3a296759172f5f5/greenlet-3.5.3-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:2421c3564da9429d5586d46ca31ebb26516b5498a802cf65c041a8e8a8980d34", size = 1626305, upload-time = "2026-06-26T19:09:08.281Z" }, - { url = "https://files.pythonhosted.org/packages/0e/50/5221371c7550108dfa3c378debc41d032aa9c78e89abb01d8011cfc93289/greenlet-3.5.3-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e0f0d160f0b2e558e6c75f7930967183255dc9735e5f5b8cae58ee09c9576d8b", size = 1688631, upload-time = "2026-06-26T18:31:51.278Z" }, - { url = "https://files.pythonhosted.org/packages/68/5d/00d469daae3c65d2bf620b10eee82eb022127d483c6bc8c69fae6f3fbf17/greenlet-3.5.3-cp315-cp315-win_amd64.whl", hash = "sha256:dd99329bbc15ca78dcc583dba05d0b1b0bae01ab6c2174989f5aaee3e41ac930", size = 241027, upload-time = "2026-06-26T18:22:38.203Z" }, - { url = "https://files.pythonhosted.org/packages/e7/e8/883785b44c5780ed71e83d3e4437e710470be17a2e181e8b601e2da0dc4a/greenlet-3.5.3-cp315-cp315-win_arm64.whl", hash = "sha256:499fef2acede88c1864a57bb586b4bf533c81e1b82df7ab93451cdb47dfec227", size = 240085, upload-time = "2026-06-26T18:23:54.217Z" }, - { url = "https://files.pythonhosted.org/packages/1c/da/4f4a8450962fad137c1c8981a3f1b8919d06c829993d4d476f9c525d5173/greenlet-3.5.3-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:176bc16a721fa5fc294d70b87b4dfa5fbdd251b3da5d5372735ecef9bd7d6d0c", size = 297221, upload-time = "2026-06-26T18:23:27.176Z" }, - { url = "https://files.pythonhosted.org/packages/57/66/b3bfae3e220a9b63ea539a0eea681800c69ab1aada757eae8789f183e7ce/greenlet-3.5.3-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:629b614d2b786e89c50440e246f33eea78f58a962d0bdbbcc809e6d13605903f", size = 657221, upload-time = "2026-06-26T19:07:16.973Z" }, - { url = "https://files.pythonhosted.org/packages/7b/81/b6d4d73a709684fc77e7fa034d7c2fe82cffa9fc920fadcaa659c2626213/greenlet-3.5.3-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2b2e857ae16f5f72142edf75f9f176fe7526ba19a2841df1420516f83831c9f2", size = 663226, upload-time = "2026-06-26T19:10:13.723Z" }, - { url = "https://files.pythonhosted.org/packages/f5/07/e210b02b589f16e74ff48b730690e4a34ffe984219fce4f3c1a0e7ec8545/greenlet-3.5.3-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e515757e2e36bcbf1fad09a46e1557e8b1ae1797d4b44d09da7deed88ad28608", size = 660802, upload-time = "2026-06-26T18:32:26.081Z" }, - { url = "https://files.pythonhosted.org/packages/eb/2e/5303eb3fa06bca089060f479707182a93e360683bc252acf846c3090d34e/greenlet-3.5.3-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:b363d46ed1ea431825fdb01471bb024fc08399bad1572a616e853c7684415adb", size = 1622157, upload-time = "2026-06-26T19:09:09.527Z" }, - { url = "https://files.pythonhosted.org/packages/54/70/50de47a488f14df260b50ae34fb5d56016e308b098eab02c878b5223c26a/greenlet-3.5.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:e44da2f5bbdaabaf7d80b73dbb430c7035771e9f244e3c8b769715c9d8fa0a16", size = 1681159, upload-time = "2026-06-26T18:31:52.986Z" }, - { url = "https://files.pythonhosted.org/packages/a7/13/1055e1dda7882073eda533e2b96c62e55bbd2db7fda6d5ece992febc7071/greenlet-3.5.3-cp315-cp315t-win_amd64.whl", hash = "sha256:8ff8bed3e3baa20a3ea261ce00526f1898ad4801d4886fd2220580ee0ad8fadf", size = 244007, upload-time = "2026-06-26T18:22:04.353Z" }, - { url = "https://files.pythonhosted.org/packages/b4/0d/ca7d15afbdc397e3401134c9e1800d51d12b829661786187a4ad08fe484f/greenlet-3.5.3-cp315-cp315t-win_arm64.whl", hash = "sha256:b7068bd09f761f3f5b4d214c2bed063186b2a86148c740b3873e3f56d79bac31", size = 242586, upload-time = "2026-06-26T18:23:37.93Z" }, +version = "3.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/74/b13368064b09053253555d3f2839cc2684d22d5aed0d2ccffbf7a6736558/greenlet-3.5.4.tar.gz", hash = "sha256:0232ae1de90a8e07867bb127d7a6ba2301e859145489f25cda8a6096dabe1d20", size = 206538, upload-time = "2026-07-22T12:47:14.468Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/9d/58f80897f4121f5c218bb931cf6d3b6514873f02ad0b729f744352926b9f/greenlet-3.5.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:ac5bf81d79d2c8eeb2ef6359b2e1687a1e9ebf46c2b1f970da9a9255df51d190", size = 293072, upload-time = "2026-07-22T11:38:14.299Z" }, + { url = "https://files.pythonhosted.org/packages/dd/9f/b4bc9bbd6a7855cbd8ad8a83c874eeeca56c24de9132b3323f81c03a30ba/greenlet-3.5.4-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89f3738167bab8c1084b94e23023d41d247117ac149fa0fbcb5bd4cf6262b353", size = 609393, upload-time = "2026-07-22T12:26:37.964Z" }, + { url = "https://files.pythonhosted.org/packages/05/0e/744b5e063af127d2e3c74fe0f1aef15573064c83b6066883524f5b258b17/greenlet-3.5.4-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e9a5e3406e3ed8125ae1a3b37c12f3434e2b1f0fa053197c5557895b4fb09606", size = 622750, upload-time = "2026-07-22T12:28:59.546Z" }, + { url = "https://files.pythonhosted.org/packages/d6/6b/d78ea2908e8e08985348f28ac396c2950be7ab66321dfe0054c73bd1f456/greenlet-3.5.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4ab9f0704bccf6d3b38e0d2130b7b33271cff11453690da074fa280c3aa8e8e7", size = 622920, upload-time = "2026-07-22T11:51:06.83Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e4/3ce7009c948920b01527f8d9da29f501a31ac3d98318829e981fd879b850/greenlet-3.5.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2cdaadc3d31445a8f782bde3cd37e49a2c2a9c6da6daf76a3e34c683b271a3c7", size = 1582262, upload-time = "2026-07-22T12:25:01.131Z" }, + { url = "https://files.pythonhosted.org/packages/f2/4c/0408366102a33829f7bdd6a992dad75abbf75e86cc1e76caf19e57311d29/greenlet-3.5.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:70bdfacdc183dac838b2a0aaff2dd6134a457c52fe68a9c6bbab435483d2b9df", size = 1648906, upload-time = "2026-07-22T11:51:08.627Z" }, + { url = "https://files.pythonhosted.org/packages/13/52/ebfe8f6a1aeb8e430540b406c844ecc4e3367072b0192f69dcb85eeeec2b/greenlet-3.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:69173331fbc5d64bfac0065d7e22c39cfcd089e9b18d125bdcd5079363b09616", size = 246036, upload-time = "2026-07-22T11:38:30.073Z" }, + { url = "https://files.pythonhosted.org/packages/61/16/71eefcf68267bbf06a9b6bff57d0b222e49432326e85d74348b67694b8d4/greenlet-3.5.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e883de250e299654b1f1680f72a1a9f9ba62c9bd1bce84099c90657349a8dfbb", size = 294266, upload-time = "2026-07-22T11:37:56.142Z" }, + { url = "https://files.pythonhosted.org/packages/36/ea/a0b19adfc35d07e10acb626e9d22a3893b95f1309c42c4a20161dec16800/greenlet-3.5.4-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32802705c2c1ff25e8237b3bdacf2594fa02be80af8a66703eb7853ea7e68686", size = 613712, upload-time = "2026-07-22T12:26:39.375Z" }, + { url = "https://files.pythonhosted.org/packages/54/76/a121978b3337407d05a1ce5f79b4aa5998a43a9d8422f9726029b90b4471/greenlet-3.5.4-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:57aa201b351f7c7c75627c60d29e4d5b97a07d37efeb62b903466fca42c097d7", size = 625582, upload-time = "2026-07-22T12:29:00.814Z" }, + { url = "https://files.pythonhosted.org/packages/34/c2/080f16cf870e929e592f55767f01d6c98d2ee83bfdc36c3b892f2d0459ab/greenlet-3.5.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c3fe76c2cac86b4f7a1e92865ac0a54384deb05c92986287c1a7110d9bd53071", size = 624663, upload-time = "2026-07-22T11:51:08.016Z" }, + { url = "https://files.pythonhosted.org/packages/9e/bb/8f3ca88370b817369008faeceeee85970adc16c92a70a3e5fe5fea495a57/greenlet-3.5.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1e1a4a684b16c45ba324e60b32a4386a87722bcb815d2a149d2182f9b401ca72", size = 1585010, upload-time = "2026-07-22T12:25:02.539Z" }, + { url = "https://files.pythonhosted.org/packages/51/c2/45877154689709ebce9a0b83c2235e6ca0f31577889b02af308c8cc5f8fb/greenlet-3.5.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e849e6e139b9671adeac505f72fc05f4af7fd1921faef40295e214fc3b361b59", size = 1651283, upload-time = "2026-07-22T11:51:10.408Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7d/8711a75cb61d85246277c07ff6e1a6504621ba473d808c11ad225ffca43f/greenlet-3.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:dc418cf4c873357964d6624445ed09472e50def990c65dd4e76fc3ba8cd9cef6", size = 246434, upload-time = "2026-07-22T11:43:15.557Z" }, + { url = "https://files.pythonhosted.org/packages/00/62/e290b3bce433da8f0324ac02da0b128d683482229f1a8b789fa47818a4cd/greenlet-3.5.4-cp311-cp311-win_arm64.whl", hash = "sha256:c38c902a0986eba1f6e7ba1ab39ad5195926abde90f3fe080e08212db62176da", size = 244990, upload-time = "2026-07-22T11:39:22.626Z" }, + { url = "https://files.pythonhosted.org/packages/f3/04/81bd731d6d1e3a469d9a4c36f5eb069bcf0cbb2d5d342c9fec22245b91fc/greenlet-3.5.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3d66250e8b09f182ede05490998c818b5961f7a3640332d44c4927caec7bbfe4", size = 295909, upload-time = "2026-07-22T11:38:09.261Z" }, + { url = "https://files.pythonhosted.org/packages/cc/dd/f5f22903a6ae70f5ea328ed0beaec92ad903f0e3b7d2845133b354abc4b8/greenlet-3.5.4-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c90e930c9c192e5b3ee9fb8bcd920ea3926155e2e3ded39fc697323addecee17", size = 612011, upload-time = "2026-07-22T12:26:40.69Z" }, + { url = "https://files.pythonhosted.org/packages/8e/10/92a4a88d12b915d74ea5b6d288e4afefda4771647caa34442c156f7a454f/greenlet-3.5.4-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:791fdfeeb9c6e0c7b10fa151bf110d2a6974866f13dcb5b1c7efae698245893a", size = 624299, upload-time = "2026-07-22T12:29:02.089Z" }, + { url = "https://files.pythonhosted.org/packages/50/6d/0b14bb9db2989f32cd9fe7f76afedea01ee8bee3f87c07e69f24adfe7e63/greenlet-3.5.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f88193799d43dbf8c8a806d6405c9c52fe2af40bf75072a606357b33cc336c7f", size = 621541, upload-time = "2026-07-22T11:51:09.464Z" }, + { url = "https://files.pythonhosted.org/packages/48/3d/25e9a2d9eb6b2e8b7ca4e80a3a26cb887cce6c8e0a87c921164f11bc5574/greenlet-3.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b7a5f095767c4493afcd06067f2bb3b8716e3f3f9e92b99c88e7e99f885b3d4d", size = 1581444, upload-time = "2026-07-22T12:25:03.818Z" }, + { url = "https://files.pythonhosted.org/packages/b9/96/4c9bf2e2c408dcc0556edce69efa9f802e82223573c53240136a086821f1/greenlet-3.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:42afdc1ab5f66da8c586c32af9224a74a706b4f0ea0dc3a4188a0860a09c65c9", size = 1645842, upload-time = "2026-07-22T11:51:12.295Z" }, + { url = "https://files.pythonhosted.org/packages/b5/41/303ecb26a3a56122c0f4d4073ee078881847bd6b6f463ae0ec57ec20223b/greenlet-3.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:60149df8f462d1b230038e6590c23c3b4768bb5d6c022b3b6e82532b34b0b8a3", size = 247169, upload-time = "2026-07-22T11:38:19.893Z" }, + { url = "https://files.pythonhosted.org/packages/a4/e3/ef56864b4c35fcb3eb3b41b869f6cc46f4cd3f5e2c68e74acde8ac433951/greenlet-3.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:77d6ce04fed0d9aeed42e0f37923cc43eba9b027bdd9c34546bb4ccd143d0fe0", size = 245565, upload-time = "2026-07-22T11:38:27.061Z" }, + { url = "https://files.pythonhosted.org/packages/c0/9a/e51225dcd58713f16ccbdcc501a8da21098ea14515b7870f1f94459e5ff5/greenlet-3.5.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:24e61b88cb7e1b1d794b32a10cc346ac779681d6d74ff137a3e0a444d2bf1f02", size = 294831, upload-time = "2026-07-22T11:38:53.389Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ea/de50a50fadf979713ab18b46f22ad5ff5f2dcfc637a3ebdecf669801e1a5/greenlet-3.5.4-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:870d730fec833f5a06906a32596cc099b9161594642a92a520b7a88911c95356", size = 614619, upload-time = "2026-07-22T12:26:42.282Z" }, + { url = "https://files.pythonhosted.org/packages/db/c7/2aae27fea41205b8650294c301f042a2a4bb6155eea48c995b890a92f2c1/greenlet-3.5.4-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ec5ff0d1878df6af3bf9b638a5a92a7d5693291de77c91bff10fa48519c604ef", size = 627021, upload-time = "2026-07-22T12:29:03.445Z" }, + { url = "https://files.pythonhosted.org/packages/eb/56/79fd826f9ccaae0b84e1b4ef68dabba5e105bb044ffcd448a0b782fcba9a/greenlet-3.5.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d84d993f6e575c950d91a23c1345d18fe1a4310d447bf630849d7809196b52f0", size = 624002, upload-time = "2026-07-22T11:51:11.391Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1a/27319f97e731298513dcba1a2e91b63e9d8811d9de22130f960b129b1bf1/greenlet-3.5.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:58023945f421093de5e6fa108c0985a8659d43f49e0216da25099369a121bcbd", size = 1581533, upload-time = "2026-07-22T12:25:05.322Z" }, + { url = "https://files.pythonhosted.org/packages/b1/6d/24240bf562e9786dd2799ee0a4a4dadb4ded22510f41b20245099159ac8c/greenlet-3.5.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bae2728e1897aa8df8cb1af38cd48b3a743aefe29372de7b8b7a9f532501e69f", size = 1645781, upload-time = "2026-07-22T11:51:14.805Z" }, + { url = "https://files.pythonhosted.org/packages/c1/5a/442ab1a9ef7ca6bf7210e5397a95972206a91a31033a03c8900866a10039/greenlet-3.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:ca5726c0b08ca35ae873557266a78b2c3f3b2b7d7401aa5ff886c2045dd0111c", size = 247133, upload-time = "2026-07-22T11:39:20.661Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e6/9160210222386b1a378ff94db846b9508ca24a121cf684991561fdb69280/greenlet-3.5.4-cp313-cp313-win_arm64.whl", hash = "sha256:7c1303791d603080cac6fc3b34df51c3b75b723739c282c8029e48a0d241672f", size = 245500, upload-time = "2026-07-22T11:40:22.185Z" }, + { url = "https://files.pythonhosted.org/packages/a5/a7/6ab1d4f9cd548d15ab90da29947f2076100130bb179b0bde59f795a459e3/greenlet-3.5.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:7e8afa5eac028f8140ceafe5ceec66e6aa127ddcb21452d2a564dcd2900b5f22", size = 295410, upload-time = "2026-07-22T11:40:35.747Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7a/422f63b4715cbc0b24385305407adf38b48f6bb68b3e6b04090e994d0f5a/greenlet-3.5.4-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73b37afe369021423ea53dd3123e04bffa7e93ac64429b9f50835b2e4fcae7cf", size = 661286, upload-time = "2026-07-22T12:26:43.8Z" }, + { url = "https://files.pythonhosted.org/packages/d0/31/5a1cac663bf5582190c5a714ef81364f03cde232227f39748f8ae4c11da5/greenlet-3.5.4-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3ef964f56dfcb6f9bbef2a190d9126795eac408716aeae47b5e7c73c32aafca9", size = 673517, upload-time = "2026-07-22T12:29:04.815Z" }, + { url = "https://files.pythonhosted.org/packages/15/4a/2a82a1e3f8aaca020853ac8d12211280ca2b231aa08ea39f636f1060c319/greenlet-3.5.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c53ff01a5c53a40f2c16820ebc56d7c61a77f5fbe009dadd96292d5682f80f8", size = 670917, upload-time = "2026-07-22T11:51:13.589Z" }, + { url = "https://files.pythonhosted.org/packages/68/b0/e379a152b17bfdfa95795af4049e37c0fd1b4d81f020d426db104ed07c77/greenlet-3.5.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecca4d80d55a01ad6b23b33262662956149fbb7b2c6be2910f1705921958cbf3", size = 1628478, upload-time = "2026-07-22T12:25:06.678Z" }, + { url = "https://files.pythonhosted.org/packages/5e/43/bffdfa64f7317f954c5c1230b5dd5922676ce198689a68c1ac1ed4b1b1a5/greenlet-3.5.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2ffbc533e0eaf8e80d8471411646ab88fe58f641d508c0b02b24494479f4d9ec", size = 1692021, upload-time = "2026-07-22T11:51:17.008Z" }, + { url = "https://files.pythonhosted.org/packages/d0/11/f799f9637e2c6e9b0b716015e339040598b058cf7654dfc0d67468b177ed/greenlet-3.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:305f69e6c4523d7f6979ed001cff4e5853c063e5da04880296603aa0227e544c", size = 248031, upload-time = "2026-07-22T11:40:11.007Z" }, + { url = "https://files.pythonhosted.org/packages/05/75/625bcdd74d5e6b2dca1ecba3c3ac77bcf8a026c21a649a46cef23e421f97/greenlet-3.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:f260930bbbbcf9caee661211235a5111c86dfe5832fdf6ae4570da1e0995320f", size = 246892, upload-time = "2026-07-22T11:40:27.357Z" }, + { url = "https://files.pythonhosted.org/packages/ec/69/35c62ed49c320cb4d98e14698ccca5467d3bfe683984172be9cb564d9ce3/greenlet-3.5.4-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:41ddab54e4b238f4a6c323f39b4e59e176affd5a94d461a9fb7583dac74240a3", size = 305571, upload-time = "2026-07-22T11:40:31.659Z" }, + { url = "https://files.pythonhosted.org/packages/5b/6c/64d60216b3640dcb0b62d913dd9e0d80030c09115bb2e4ba70c95d10ca45/greenlet-3.5.4-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3dabe3e2809013052c68bdf0b7fa5f5f2859c43a80803131ad61af9cabd7867", size = 672568, upload-time = "2026-07-22T12:26:45.298Z" }, + { url = "https://files.pythonhosted.org/packages/5c/de/ba3ab0a96292e53039530333b0d2ae18d9e508f3a325cd7bf15f8172944c/greenlet-3.5.4-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:27d3f00718634d4520a3a150154ac5da36f257869d41321953375b90bfbbc72c", size = 680076, upload-time = "2026-07-22T12:29:06.125Z" }, + { url = "https://files.pythonhosted.org/packages/a5/be/aeada79083c6f1c15f45d77a332f9c441af263ee298e3eb17522cd337d22/greenlet-3.5.4-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cbd60b5763c6543c1827e48faaf14ea9bfbad245f52b1a4d76a2a2d8884c6c66", size = 676733, upload-time = "2026-07-22T11:51:16.027Z" }, + { url = "https://files.pythonhosted.org/packages/e9/10/2392fc3a98948652ef5fd1e7275c04f861dd13f74b78a2b4309f4ee4d090/greenlet-3.5.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f00f910f0e7b35416c63b23ad78b769aeccfc1775f712b43c4ee525624a2eef7", size = 1637327, upload-time = "2026-07-22T12:25:07.879Z" }, + { url = "https://files.pythonhosted.org/packages/55/c6/e7237a3dfa1f205ed0d9ea1e46d70bd2811b32d516266399fb59d28ab90a/greenlet-3.5.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:91c26423753b92caf41ab3f98fd547d7374d4d9fc2d85be041886c1579d9255e", size = 1697493, upload-time = "2026-07-22T11:51:19.214Z" }, + { url = "https://files.pythonhosted.org/packages/55/e3/4ba8154ba2a3d43729e499f72471b4b5c993f3826d3e24da81d5f06d6572/greenlet-3.5.4-cp314-cp314t-win_amd64.whl", hash = "sha256:ee032b91fd8ec29ec6c4cea2b8c561b178435134bd0752c7334b94e9c736c132", size = 251637, upload-time = "2026-07-22T11:40:37.44Z" }, + { url = "https://files.pythonhosted.org/packages/90/03/e3f96dfc100261a29545ddc8270cafe58f9195b6651466910e820910de77/greenlet-3.5.4-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:178111881dd7a6c946471fda85485ec796e1043c2b939f694b096e2ecf986809", size = 296076, upload-time = "2026-07-22T11:39:38.364Z" }, + { url = "https://files.pythonhosted.org/packages/a4/3d/da52d208e5c977bce8667e784729e584e38b5785f4c1ec0f4c836e9a1c42/greenlet-3.5.4-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d92df08dd65fede97fc37aad36c2e9dcda3b31c467f8e0c2c096456cb818e927", size = 666870, upload-time = "2026-07-22T12:26:46.691Z" }, + { url = "https://files.pythonhosted.org/packages/2d/8a/7e6dee25cb8a8cf9b362c8e597cc269593378bd916f16c736c059a52e85a/greenlet-3.5.4-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:99e8f8c4ebc4fd80aa26c1280ae9ad43a0976e786349703a181cf0bae60413e5", size = 677678, upload-time = "2026-07-22T12:29:07.508Z" }, + { url = "https://files.pythonhosted.org/packages/6c/21/5a38699fa45de749e3857d93b8f07e4c20489e77c2d35d915a2e1c456606/greenlet-3.5.4-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:394de08dad5ffcb1f50c2159d93e398d9d2da3ed437645eaa54771fa720db9f0", size = 676067, upload-time = "2026-07-22T11:51:18.163Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ba/863116ab8ff1ca7a729e327800268939d182db47aa433db70e216e7d9194/greenlet-3.5.4-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:c883d61f2282d72c767a14936641b3efcbde9d82f1080712aaea0b1d3126cb88", size = 1633489, upload-time = "2026-07-22T12:25:09.605Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/7466ced82818d6132462d7f26b3f83c66ea15d2b193a6c0088d558ed7d95/greenlet-3.5.4-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:2a924f15d17957e252a810acefcb5942f5ca712298e8b6fcaed9a307d357522c", size = 1696584, upload-time = "2026-07-22T11:51:21.304Z" }, + { url = "https://files.pythonhosted.org/packages/f9/4d/55b638489260065de9ffce606c8b5d04507bef705de4b212a0c3d6a1a0df/greenlet-3.5.4-cp315-cp315-win_amd64.whl", hash = "sha256:ed17e5f3420360d5b459de8462efb52060399a5326a613d4cde31cef63ef95da", size = 248297, upload-time = "2026-07-22T11:42:04.055Z" }, + { url = "https://files.pythonhosted.org/packages/bb/08/9dd4ae635da93d41dc268bc34bd62a9d711ed8b8825c5d22ac910c7d6e6d/greenlet-3.5.4-cp315-cp315-win_arm64.whl", hash = "sha256:f908898d6fa484ce4b6f447ce70ea99b52c503fee419e53cf74d60a16bc9e667", size = 247423, upload-time = "2026-07-22T11:44:00.764Z" }, + { url = "https://files.pythonhosted.org/packages/19/66/7c87ed9cdbf1d49c2c6cd1c7b9dd4d16c33b24235ca03972293a1876b30c/greenlet-3.5.4-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:1833637f17d5e7472548a48575c394fe39f1b1890d676d162d86593610f44d8c", size = 306487, upload-time = "2026-07-22T11:41:25.118Z" }, + { url = "https://files.pythonhosted.org/packages/5b/05/0a4201e7c0054866eefc05da234f236dd4c950d0fbf9ca0517141f01b269/greenlet-3.5.4-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:12cda9122e03341f1cb6b8207a19d7a9d375e52f1b4e9243918375f40fd7b4b9", size = 676479, upload-time = "2026-07-22T12:26:48.129Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c0/4b6b8c5a3aec70f0649cd89662d120fdd6421e2bdc8e3b15c3ab5ec568d8/greenlet-3.5.4-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d83ae0e32d14957ab7170785a20f582635c8474deab1bfbb552b17e769a6ce25", size = 684321, upload-time = "2026-07-22T12:29:08.925Z" }, + { url = "https://files.pythonhosted.org/packages/24/c9/b49c31c9a972eee91e260445770e922244a7efc542697f51a012ec046d0f/greenlet-3.5.4-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9f1467de1bb767f75db0aa34c195e3a496d8d1278c796e70c24ce205d3e99cde", size = 681293, upload-time = "2026-07-22T11:51:20.43Z" }, + { url = "https://files.pythonhosted.org/packages/95/6f/7f2d4653770500eee667866016d42d7a68e3d3462f80df6b8e3fcd48a0eb/greenlet-3.5.4-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:0fa53040b78b578120eecdc0265e3f1051487cc425d11a2b7c761daadf4feaa8", size = 1642474, upload-time = "2026-07-22T12:25:10.819Z" }, + { url = "https://files.pythonhosted.org/packages/5a/d8/8cba31036a4caae448087ba5d150660ab03b4a0f54d9150f6495a3be7262/greenlet-3.5.4-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:60e0bc961d367df506660e9ac0177a76bc6d81305300704b0977d1634f76efe2", size = 1701012, upload-time = "2026-07-22T11:51:23.17Z" }, + { url = "https://files.pythonhosted.org/packages/e3/cd/3f77a4cce3bae631b08eb52f53a82a976669600337e21dfdba811cb50267/greenlet-3.5.4-cp315-cp315t-win_amd64.whl", hash = "sha256:f680e549edb3eaf21eea4e7fe101e15ec180c74b7879ab46adc080f22d4015d2", size = 251977, upload-time = "2026-07-22T11:41:38.125Z" }, + { url = "https://files.pythonhosted.org/packages/93/e8/65e8707d00fe2a49bf12f609a9b2b39ba6dd23c2810eacad877c4fc94bfe/greenlet-3.5.4-cp315-cp315t-win_arm64.whl", hash = "sha256:08fc36de8442d5c3e95b044550dbea9bf144d31ec0cc58e36fb241cb6ef6a994", size = 250538, upload-time = "2026-07-22T11:40:17.985Z" }, ] [[package]] @@ -1035,17 +1045,17 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version < '3.11'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "jedi", marker = "python_full_version < '3.11'" }, - { name = "matplotlib-inline", marker = "python_full_version < '3.11'" }, - { name = "pexpect", marker = "python_full_version < '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version < '3.11'" }, - { name = "pygments", marker = "python_full_version < '3.11'" }, - { name = "stack-data", marker = "python_full_version < '3.11'" }, - { name = "traitlets", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "exceptiongroup" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/40/18/f8598d287006885e7136451fdea0755af4ebcbfe342836f24deefaed1164/ipython-8.39.0.tar.gz", hash = "sha256:4110ae96012c379b8b6db898a07e186c40a2a1ef5d57a7fa83166047d9da7624", size = 5513971, upload-time = "2026-03-27T10:02:13.94Z" } wheels = [ @@ -1071,18 +1081,18 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version >= '3.11'" }, - { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.11'" }, - { name = "jedi", marker = "python_full_version >= '3.11'" }, - { name = "matplotlib-inline", marker = "python_full_version >= '3.11'" }, - { name = "pexpect", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version >= '3.11'" }, - { name = "psutil", marker = "python_full_version >= '3.11' and sys_platform != 'cygwin' and sys_platform != 'emscripten'" }, - { name = "pygments", marker = "python_full_version >= '3.11'" }, - { name = "stack-data", marker = "python_full_version >= '3.11'" }, - { name = "traitlets", marker = "python_full_version >= '3.11'" }, - { name = "typing-extensions", marker = "python_full_version == '3.11.*'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "ipython-pygments-lexers" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "psutil", marker = "sys_platform != 'cygwin' and sys_platform != 'emscripten'" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/53/59/165d3b4d75cc34add3122c4417ecb229085140ac573103c223cd01dde96f/ipython-9.15.0.tar.gz", hash = "sha256:da2819ce2aa83135257df830660b1176d986c3d2876db24df01974fa955b2756", size = 4442580, upload-time = "2026-06-26T11:03:35.913Z" } wheels = [ @@ -1094,7 +1104,7 @@ name = "ipython-pygments-lexers" version = "1.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pygments", marker = "python_full_version >= '3.11'" }, + { name = "pygments" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } wheels = [ @@ -1352,7 +1362,7 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "mdurl", marker = "python_full_version < '3.11'" }, + { name = "mdurl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } wheels = [ @@ -1378,7 +1388,7 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "mdurl", marker = "python_full_version >= '3.11'" }, + { name = "mdurl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } wheels = [ @@ -1478,15 +1488,15 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "contourpy", version = "1.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "cycler", marker = "python_full_version < '3.11'" }, - { name = "fonttools", marker = "python_full_version < '3.11'" }, - { name = "kiwisolver", marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "packaging", marker = "python_full_version < '3.11'" }, - { name = "pillow", marker = "python_full_version < '3.11'" }, - { name = "pyparsing", marker = "python_full_version < '3.11'" }, - { name = "python-dateutil", marker = "python_full_version < '3.11'" }, + { name = "contourpy", version = "1.3.2", source = { registry = "https://pypi.org/simple" } }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, ] sdist = { url = "https://files.pythonhosted.org/packages/63/1b/4be5be87d43d327a0cf4de1a56e86f7f84c89312452406cf122efe2839e6/matplotlib-3.10.9.tar.gz", hash = "sha256:fd66508e8c6877d98e586654b608a0456db8d7e8a546eb1e2600efd957302358", size = 34811233, upload-time = "2026-04-24T00:14:13.539Z" } wheels = [ @@ -1548,7 +1558,7 @@ wheels = [ [[package]] name = "matplotlib" -version = "3.11.0" +version = "3.11.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", @@ -1565,64 +1575,64 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "contourpy", version = "1.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "cycler", marker = "python_full_version >= '3.11'" }, - { name = "fonttools", marker = "python_full_version >= '3.11'" }, - { name = "kiwisolver", marker = "python_full_version >= '3.11'" }, - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "packaging", marker = "python_full_version >= '3.11'" }, - { name = "pillow", marker = "python_full_version >= '3.11'" }, - { name = "pyparsing", marker = "python_full_version >= '3.11'" }, - { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1f/24/080c99d223d158d3a8902769269ab6da5b50f7a0e6e072513907e02b7a6c/matplotlib-3.11.0.tar.gz", hash = "sha256:68c0c7be01b30dcca3638934f7f591df73401235cbdbf0d1ab1c71e7db7f8b57", size = 33251176, upload-time = "2026-06-12T02:29:15.508Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/a2/78f662f1b18968531f67d3fcde1b7ea8496920bacd4f16ddb5b79d112e46/matplotlib-3.11.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f857524b442f0f36e641868ce2171aafa88cb0bc0644f4e1d8a5df9b32649fef", size = 9436261, upload-time = "2026-06-12T02:27:34.161Z" }, - { url = "https://files.pythonhosted.org/packages/5e/92/044f1de43901310202f4c79acf4f141be53b2ca8d8380e2fcefb3d523a75/matplotlib-3.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:57baa92fdc82948ed716eae6d2579d4d6f40965cd8d2f416755b4a72580a3233", size = 9264669, upload-time = "2026-06-12T02:27:37.413Z" }, - { url = "https://files.pythonhosted.org/packages/53/f4/f0b4f9ba7ec14a7af8151f3ad71ecfe3561e6ba38cfab1db3681ba4ca112/matplotlib-3.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:630eee0e67d35cce2019a0e670719f4816e3b86aff0fa72729f6c69786fceb45", size = 10021076, upload-time = "2026-06-12T02:27:39.926Z" }, - { url = "https://files.pythonhosted.org/packages/d7/33/4d679c6dcd594a156542080ac907ddccf7b09ca11655c4b28eca8e9ee5da/matplotlib-3.11.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5106c444d0bf966eee2853548c03772af4ab7199118e086c62fbac8ccb07c055", size = 10828999, upload-time = "2026-06-12T02:27:42.433Z" }, - { url = "https://files.pythonhosted.org/packages/07/74/0a3683802037d8cd013144d77c247219b47f2aabace6fdde74faa12bacf7/matplotlib-3.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d7aea652b58e686444079be3376ef546bffa1eee9b9bb9c472b9fcf6cf410d3", size = 10913103, upload-time = "2026-06-12T02:27:44.827Z" }, - { url = "https://files.pythonhosted.org/packages/d0/9f/970fcbf381e82ec66fdf5da8ea76e2e9240f61a24011ce9fd1d42c37ac2d/matplotlib-3.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:70a5b3e9a5dab708c0f039709ae7c68d5b4d254e291ef76492cdba230c8bb5e4", size = 9310945, upload-time = "2026-06-12T02:27:46.867Z" }, - { url = "https://files.pythonhosted.org/packages/14/4e/6e7cfed23611265ded53806852343b5c59339e506e84c474a9b5afc3b249/matplotlib-3.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:3d68266213e73823ac3be90615bab0cf31f88851e114cdb1dd25dacf3b01e1a7", size = 8999304, upload-time = "2026-06-12T02:27:48.798Z" }, - { url = "https://files.pythonhosted.org/packages/da/17/f5276b496c61477a6c4fc5e7401f4bfe1c2e5ef7c6cd67896f2ade3809cb/matplotlib-3.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:06b5872e9cf11adc8f589ded3ce11bc3e1061ad498259664fabc1f6615beb918", size = 9449976, upload-time = "2026-06-12T02:27:50.989Z" }, - { url = "https://files.pythonhosted.org/packages/82/34/bdd77418adb2178a1d59f044bd67bfebb115896e91b840b8a197eb3f4f4e/matplotlib-3.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0515d495124be3124340e59f164d901ed4484e2246a5b74cfa483cac3b80bd97", size = 9279307, upload-time = "2026-06-12T02:27:53.247Z" }, - { url = "https://files.pythonhosted.org/packages/94/95/7f522393c88313336b20d70fc849555757b2e5febc22b83b3a3f0fd4bce9/matplotlib-3.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be5f93a1d21981bfb802ded0d77a0caa92d4342a47d45754fac77e314a506344", size = 10031353, upload-time = "2026-06-12T02:27:55.215Z" }, - { url = "https://files.pythonhosted.org/packages/87/ce/8f25a0e3186aefd61913e7467d1b999465bcd0d0c03ac695c1b26ca559b7/matplotlib-3.11.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41635d7909d19e52e924a521dde6d8f670b0f53ab1d0e8c331fa831554f681d1", size = 10839232, upload-time = "2026-06-12T02:27:57.746Z" }, - { url = "https://files.pythonhosted.org/packages/85/c2/db15da2bbdf9e3ca66df7db8e2c33a1dfed67be24a24d2c878efaaff01d6/matplotlib-3.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:94f5000f67ca9faa300863ea17f8bce9175cb67b88bec4bc7780502d53dd7c9e", size = 10923899, upload-time = "2026-06-12T02:28:00.223Z" }, - { url = "https://files.pythonhosted.org/packages/e5/2f/a58a4443a4d052a4ea77557478336aefc26c7981f6408d37adba763aa758/matplotlib-3.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ac6f1ef39f3d0f9e2463303013094992cdbe0f85f43bc54155bc472b2042768e", size = 9329528, upload-time = "2026-06-12T02:28:02.27Z" }, - { url = "https://files.pythonhosted.org/packages/61/0f/4b669589d47733b97ab9df4b58d6fc1e68acb5ea42a928dc7cbdd6bf5871/matplotlib-3.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:9dd11fb612ce7bc60b1de5b4fc87ff959d22317b5de42aabf392f66f97af22eb", size = 9003413, upload-time = "2026-06-12T02:28:04.49Z" }, - { url = "https://files.pythonhosted.org/packages/55/41/aa47f156b061d14c98b906f76c428507397708ec63ff94f410ae1752b426/matplotlib-3.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce3b839b34ae1f430b4616893a2945a2999debaa7e94e7e29a2a8bbf286f7b5", size = 9450532, upload-time = "2026-06-12T02:28:06.769Z" }, - { url = "https://files.pythonhosted.org/packages/8c/4f/5a9eb0375e81413953febf8af7b012a6b6357f53438a15c4f5ad86c6bbb5/matplotlib-3.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:373db8f91214e8ccaf35ac833cc1dd59dd961e148bbd55dd027141591dde1313", size = 9279760, upload-time = "2026-06-12T02:28:09.152Z" }, - { url = "https://files.pythonhosted.org/packages/a4/c0/1117d53077e3ac3152503a84e9cf7a5c239576805ee71276e80c2aaa7471/matplotlib-3.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be152b7570324dc8d01574cc9474dd2d803237acf528bcbb5b211fa347461a09", size = 10031623, upload-time = "2026-06-12T02:28:11.26Z" }, - { url = "https://files.pythonhosted.org/packages/92/7e/e937138daffad65b71bf831a377809dcbc830fb4f31a31e067dc1faa2575/matplotlib-3.11.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:126f256df600652d7e4b394cf3164ff75210a00038f287c95a012a6f58d0e83f", size = 10839372, upload-time = "2026-06-12T02:28:14.102Z" }, - { url = "https://files.pythonhosted.org/packages/1d/c2/438ecc197ffb8023b6b9922915542f2172f5fd45b76703b0b4fc47322243/matplotlib-3.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:03acfeddf87b0dddb11b081ef7740ad445a3ca8bcb6b8e3011b08f2cf802b75c", size = 10924099, upload-time = "2026-06-12T02:28:16.383Z" }, - { url = "https://files.pythonhosted.org/packages/40/2e/395883da416f378b3ed2c9f3e843ac477eae1ce731b671b79adaa6f0bacd/matplotlib-3.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:ab3722f04f3ff34c23b5012c5873d2894174e06c3822fcdac3610965a5ac7d06", size = 9329727, upload-time = "2026-06-12T02:28:18.581Z" }, - { url = "https://files.pythonhosted.org/packages/61/82/2c388956abf8bf392dfb5b8917c502f1082df6a941b781ab8c8e5ba2474b/matplotlib-3.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:c945824670fb8915b4ac879e5e61f3c58e0913022f70a0de4c082b17372f8771", size = 9003506, upload-time = "2026-06-12T02:28:20.474Z" }, - { url = "https://files.pythonhosted.org/packages/c8/c1/34454baa44da7975ada82e9aea37105ec47059514dc967d3be14426ba8dc/matplotlib-3.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3489c3dc487669b4a980bc3068f87856de7a1564248d3f6c629efb2a58b03f24", size = 9499838, upload-time = "2026-06-12T02:28:22.713Z" }, - { url = "https://files.pythonhosted.org/packages/b1/c3/98fe79a398cf232219f090163a7fa7e6766e9f2e0ad26df54d6f8934d8ee/matplotlib-3.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6a98f5476ce784a50ce09998f4ae1e6a9f25043cef8a480c98949902eda74620", size = 9332298, upload-time = "2026-06-12T02:28:24.796Z" }, - { url = "https://files.pythonhosted.org/packages/95/e4/b4b7c33151e74e5c802f3cde1ba807ebfc38401e329b44e215a5888dd76d/matplotlib-3.11.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:565af866fd63e4bd3f987d580afe27c44c2552a3b3305f4ecbb85133601ea6f3", size = 10045491, upload-time = "2026-06-12T02:28:27.141Z" }, - { url = "https://files.pythonhosted.org/packages/71/28/394548efd68354110c1a1be11fe6b6e559e06d1a23da35908a0e316c55a9/matplotlib-3.11.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e6b3e64dea5062c570f04358e2711859f3531b459f29516274fbad889079e4f3", size = 10857059, upload-time = "2026-06-12T02:28:29.222Z" }, - { url = "https://files.pythonhosted.org/packages/c8/44/e7922e6e2a4d63bdfbc9dc4a53e3850ab438d46cf42e6779bb15ec92c948/matplotlib-3.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:942b37c5db1899610bd1543ce8e13e4ecff9a4633e7f63bb6aa9205d2644ebd1", size = 10939576, upload-time = "2026-06-12T02:28:31.66Z" }, - { url = "https://files.pythonhosted.org/packages/3d/be/b1ca96003a441d619b727fee21d671fdff7a5ce2f1bb797b2521aa2f679a/matplotlib-3.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c08e649a6313e1291e713623b97a38e5bb4aa580b2a100a94a3309bc6b9c8eb3", size = 9379519, upload-time = "2026-06-12T02:28:33.888Z" }, - { url = "https://files.pythonhosted.org/packages/e3/72/4bf3b91821c34596dd6a7bdac5836d94f744144c8208939ef49d8ec43f7e/matplotlib-3.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2746cd2c113742ff6ce37a864c5ac5fd7aa644568f445e66166e457ac78e40e0", size = 9055456, upload-time = "2026-06-12T02:28:35.878Z" }, - { url = "https://files.pythonhosted.org/packages/57/52/a94102ac99eb78e2fe9b826674f9ef9ee23327110ea6ab4776c1b4eb6209/matplotlib-3.11.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3338e3e3de128cf50d0d2fb92a122815daf9c755bd882a474343c05f8fd7ec79", size = 9452137, upload-time = "2026-06-12T02:28:37.93Z" }, - { url = "https://files.pythonhosted.org/packages/7c/03/b8cdb625a21f710dfa11bbca1f48fb4057d2c0286975f8b415bf80942c99/matplotlib-3.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:25c2e5455efd8d99f41fb79871a31feb7d301569642e332ec58d72cfe9282bc3", size = 9281514, upload-time = "2026-06-12T02:28:40.028Z" }, - { url = "https://files.pythonhosted.org/packages/b7/2d/4e1240ea82ee197dfb3851e71f71c87eeeb975f1753b56a0588e4e80739a/matplotlib-3.11.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9695457a467ff86d23f35037a43deb6f1134dd6d3e2ac8ce1e2087cff09ffb9", size = 10843005, upload-time = "2026-06-12T02:28:42.39Z" }, - { url = "https://files.pythonhosted.org/packages/29/dc/6377ecfaa5fef79430f74a1a16638b4e2aa30d4692bae2c19f9d76fe3b01/matplotlib-3.11.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19c16c61dea63b3582918503e6b294193961261d9daa806d4ae2151f1ad05430", size = 11127459, upload-time = "2026-06-12T02:28:44.483Z" }, - { url = "https://files.pythonhosted.org/packages/6f/41/795c405aa7560443a3b01309424cde4a1113b85c90b8a63417444a749617/matplotlib-3.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2d72ea8b7924f3cb955e61518d21e43b3df1e6c8a793b480a0c1214f185d30ba", size = 10925160, upload-time = "2026-06-12T02:28:46.564Z" }, - { url = "https://files.pythonhosted.org/packages/1a/f7/3a9e6389a7cfaeff76c56e40c2dabcb13110e21e82f837228c834ebe748c/matplotlib-3.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:1c02da0a629dfa9debf52725ea06866b74c1fb70a895bae05e4493d34074f9f2", size = 9485186, upload-time = "2026-06-12T02:28:49.344Z" }, - { url = "https://files.pythonhosted.org/packages/8b/c0/396478ee7cf2091d182db8b4a8695f6a37f1ddb978989cf9dbb84cd5c123/matplotlib-3.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aa55d73b3117d4b07f959cd9eb6f69b375d8df3414139c479388e551aa5d999d", size = 9160349, upload-time = "2026-06-12T02:28:51.382Z" }, - { url = "https://files.pythonhosted.org/packages/c5/6f/1c3bd51bb2b34eaacdcf3c3d859dbb357f952fc8020c617dc118ad7c9e38/matplotlib-3.11.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a9d8c6e7cd2f0ddf11d8d92e520dd1d9d2abb0cf6ac8831e338666c81e905847", size = 9500921, upload-time = "2026-06-12T02:28:53.443Z" }, - { url = "https://files.pythonhosted.org/packages/e0/0d/4d861d0121840cb1a3fd4a10deb211efd6fccd481ed23e553f31f4f4da4a/matplotlib-3.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:be050fcf32f729eda99f7f75a80bf67612ce16ab9ac1c23a387dcaede95cb70e", size = 9332190, upload-time = "2026-06-12T02:28:55.623Z" }, - { url = "https://files.pythonhosted.org/packages/4b/cb/22f6bc35711a0b5639a784e74e653e77c86210bd4304449dd399a482f74e/matplotlib-3.11.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfabef0230d0697aa0d717385194dd41162e00207a68bf4abf94c2bf4c27dca0", size = 10854181, upload-time = "2026-06-12T02:28:57.856Z" }, - { url = "https://files.pythonhosted.org/packages/3f/7e/9a9eaca731a2939589da520f0ebe8fd8753d0f51fca98c7d20af6dbe261a/matplotlib-3.11.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1644db30e759199443493ac5e5caec24fdb775a8f6123021f85ba47c4133c3cb", size = 11137715, upload-time = "2026-06-12T02:29:00.555Z" }, - { url = "https://files.pythonhosted.org/packages/ef/f9/9b030b6088354acb0296871bb624b25befc1c42509d3c6cd17420c83a5b8/matplotlib-3.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:15b0d160079cb10699a0e98b5989c70677b2df7cacdc62af67c30f2facec46d9", size = 10939427, upload-time = "2026-06-12T02:29:02.527Z" }, - { url = "https://files.pythonhosted.org/packages/59/94/6b273eaee4ee250863567d100865da61a5c1527fa67f527b7ed22e0dd29c/matplotlib-3.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:446307e6b04b57b1f1239e228a1ec2af0d589a1008cebc3dfa3f5441d095cfb6", size = 9535809, upload-time = "2026-06-12T02:29:04.994Z" }, - { url = "https://files.pythonhosted.org/packages/60/95/1d36bddf2b7e2692c1540e78a6e5bc88bc1496b137e3e35a611f91b65ac3/matplotlib-3.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:652fb5696271d4c50f196d22a5ff4f8e4444c74f847423570d7dc0aa2bbd0159", size = 9209226, upload-time = "2026-06-12T02:29:07.033Z" }, - { url = "https://files.pythonhosted.org/packages/0f/c2/f5da6cd37ed6871f5c9b3c0507ddb69f14d6c36fac4541e4e0c60cb8cdfc/matplotlib-3.11.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:81ae77077a1e16d37a5b61096ccb07c8d90a99b518fa8256b8f21578932f2f62", size = 9434094, upload-time = "2026-06-12T02:29:09.135Z" }, - { url = "https://files.pythonhosted.org/packages/f8/07/56f66906e0f87a0c6d0d0acbd34dbc9432b1931d8f26ef618bd6f92932a9/matplotlib-3.11.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ddef37840695f5eef65f9f070fe2d2f510f584c2156203f9f622a5b0584efffd", size = 9262183, upload-time = "2026-06-12T02:29:11.283Z" }, - { url = "https://files.pythonhosted.org/packages/0c/d8/c4ecab06b7ea36a570c4f3bd2d48d1799fd5d9174470e45c2194199431e7/matplotlib-3.11.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cf662e5ac5707658cb931e19972c4bd99f7b4f8b7bf79d3c821d239fa6b71e64", size = 10015653, upload-time = "2026-06-12T02:29:13.251Z" }, + { name = "contourpy", version = "1.3.3", source = { registry = "https://pypi.org/simple" } }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/64/f9a391af28f518b11ad45a8a712353c94a0aefce09d3703200e5c54b610a/matplotlib-3.11.1.tar.gz", hash = "sha256:69647db5746941c793d6e445a4cd349323ffb87d9cc958c2ad84a659b4832d30", size = 32612045, upload-time = "2026-07-18T03:39:46.63Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/d0/791aa183dd88491555cf7d4be0b52b0bcf6c3c2a2c22c815a2e819bf53e2/matplotlib-3.11.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b7cf158e7add54a8d51ac9b5a84abd6d4e13ed4951b4f25f1c5139f41c2addb2", size = 9440302, upload-time = "2026-07-18T03:38:03.844Z" }, + { url = "https://files.pythonhosted.org/packages/35/74/82bbdf683a301f4478384c8aaba6903631a2ca18294b2d7655c9a542bffb/matplotlib-3.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d2ace7273b9a5061a3b420918a16fae1f2dc5dfee1abcc13aba71b5d94b1820c", size = 9268549, upload-time = "2026-07-18T03:38:06.144Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f0/9b4298911303f74e6d83e64a81d996c0616405ec95046fac7f17e4258b9e/matplotlib-3.11.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee55e9041211bf84302ab55ec3965df18dd90ae19f8b58332a7feaf208bfe83", size = 10024922, upload-time = "2026-07-18T03:38:08.236Z" }, + { url = "https://files.pythonhosted.org/packages/84/6f/0bc3c3d05b021db44c14bc379a7c0df7d57302aa15380c16fd4e63fd6a9b/matplotlib-3.11.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f4bdeea33a8d15a071dbfe6d119451b1d719c733ac666d65357082901a9099", size = 10832170, upload-time = "2026-07-18T03:38:10.276Z" }, + { url = "https://files.pythonhosted.org/packages/db/4d/e375f39acdb2af5a9342730618608e39790ec842e6f1b392863028781459/matplotlib-3.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b4c78ceb2f11bcac7389d305cda17aeb1f4586a857854ab5780bd3dd8dbfc407", size = 10916701, upload-time = "2026-07-18T03:38:12.512Z" }, + { url = "https://files.pythonhosted.org/packages/bc/be/fa26ed085b41298f64a8f9b7592c671bbf1acc8b0df124c1c5de96b859f8/matplotlib-3.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:7f33a781e12b1e53b278deb2f5373c2e55ec4f10727be3440c0cfb5cda9f944f", size = 9315331, upload-time = "2026-07-18T03:38:14.949Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f3/eb5bdf3b6e191b200db298b08bbc1638b7f3c82cdc8680f9d88bf72559ae/matplotlib-3.11.1-cp311-cp311-win_arm64.whl", hash = "sha256:67e4c3cd578c65ebd81bdc09a1b6592ceafee6dfafe116dc85dfcb647b5bbb18", size = 9003475, upload-time = "2026-07-18T03:38:17.205Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6c/7ef7ebcb2bd9739b2b66b18b076e077f44bb46fdbe28ca0506edb3c62c79/matplotlib-3.11.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e15ef41507f3d525f46154ac9e3ae785dacde9f20e593a25de8986267892ef74", size = 9453849, upload-time = "2026-07-18T03:38:19.593Z" }, + { url = "https://files.pythonhosted.org/packages/eb/f8/6d0c312c8d9738e7d9677f09fe5c986b3239e651a7b73a2deb38b65e4a71/matplotlib-3.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:21a67b961a6d597bca54fae826cd20695ba4a6e4d05424a08da6e13e3176fd6b", size = 9283113, upload-time = "2026-07-18T03:38:21.95Z" }, + { url = "https://files.pythonhosted.org/packages/c9/cf/b4ad2cc81b6672ea29ea04e64e350a9f9b493b0908ccd884c67eeff8f7b2/matplotlib-3.11.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ba8f811b8ddfac493734d6af0b2dff96919d0c28ca0d641858dab4262777c6ea", size = 10035615, upload-time = "2026-07-18T03:38:24.315Z" }, + { url = "https://files.pythonhosted.org/packages/88/90/4e10e033d9b66589d8ed98b84c95cdbb57033d57c1f41339d7393dbd2f2e/matplotlib-3.11.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c52f7ad20ef476806ed212380b1d54d20310c8b86bdc2c9a68b51f0024a44472", size = 10842559, upload-time = "2026-07-18T03:38:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/88/eb/799612d0f8cd3e816a10fec59329fca52cd2353264df80378dfc541ae855/matplotlib-3.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8b14eb22961fe865efb0e4ff167e333e428908b00115a8d800ccb65ee108e481", size = 10927532, upload-time = "2026-07-18T03:38:28.532Z" }, + { url = "https://files.pythonhosted.org/packages/88/89/56649bbaa2fd12e20f3be03dbcc135b0c8676d88bac17977599e3eb442a0/matplotlib-3.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:88a2a27dd9691ae448dfae4b26f59036be90c3c28757edd3553a29559d00859f", size = 9333886, upload-time = "2026-07-18T03:38:30.477Z" }, + { url = "https://files.pythonhosted.org/packages/c1/11/4d124efbbad677b7b7552f6f85a3bd432d4232f95400cea98fcd2ae36ef3/matplotlib-3.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:480194afceca4df2f137c2721227d3cba67121fbf4397b69cee7f83714b0a58a", size = 9007545, upload-time = "2026-07-18T03:38:32.833Z" }, + { url = "https://files.pythonhosted.org/packages/04/6c/4798363b7fb5644e309fe1fac30216e9146c9f70859d80d588c18caf5317/matplotlib-3.11.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6771b0cd7838c6a857a7209814158c0ad09bfef878db3033dd82d70ad101f191", size = 9454341, upload-time = "2026-07-18T03:38:35.001Z" }, + { url = "https://files.pythonhosted.org/packages/59/98/6acadbe7f98df19d274bc107ac58bb439fa75df82c33dc110d71a4a8501f/matplotlib-3.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2abdee5ffa2fe11b2d19f7a5c63b785fb7c28cc46c7bc1814156341d9d1a33e1", size = 9283627, upload-time = "2026-07-18T03:38:37.061Z" }, + { url = "https://files.pythonhosted.org/packages/24/ea/65cec46fe241390ccea1b1754207ee28eb71c5ab866bd5f22fe47e538fa4/matplotlib-3.11.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0a19dcf73406d3746d25a5ed42d713604c9a3e024d129b102852b0d941cb9f3", size = 10035860, upload-time = "2026-07-18T03:38:39.663Z" }, + { url = "https://files.pythonhosted.org/packages/c7/10/63fdccccbabe002fb0960876baabc5e3f24d9c1bb4cfb25651457f74b3a0/matplotlib-3.11.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7389b77ed2ab0552f46d9a90b81b7b8e6dfcdc42adc36c37a0865799843e0e3e", size = 10843594, upload-time = "2026-07-18T03:38:42.144Z" }, + { url = "https://files.pythonhosted.org/packages/98/51/a1155945bff7b91381875022ac1522c5dfdac0d006be8e7df389b3134eae/matplotlib-3.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c90be0b73568da4f662afac580956a76e308437e641b4a45aa08925eeb67d95f", size = 10927962, upload-time = "2026-07-18T03:38:44.302Z" }, + { url = "https://files.pythonhosted.org/packages/0d/3a/3d5e1f42dc761bf53401a62a83ff93389b37de9d2c093b2a3aa49ac34f1b/matplotlib-3.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:68408341f2312836fbbdf6b3c78047f65b2d8752f5fd221c3e72d348f5b34f8b", size = 9334074, upload-time = "2026-07-18T03:38:46.616Z" }, + { url = "https://files.pythonhosted.org/packages/e2/db/3f5ea5a5b64060ef5e1ff60a19170423e41ce21b8497a6fe15a36e0b43e3/matplotlib-3.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:0c1f44890d435c1b4ef52f701ad5828cb450ea97bcc83918fda6be74965d6cd2", size = 9007662, upload-time = "2026-07-18T03:38:49.112Z" }, + { url = "https://files.pythonhosted.org/packages/98/6e/c7ae5e0531425b69c0826b00ebbc264c85cab853f1cd6e096c9983c2cdc1/matplotlib-3.11.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:5e510088c27a89d53580a752f959146893563e63c330e161d159b0fee652af6f", size = 9503790, upload-time = "2026-07-18T03:38:51.527Z" }, + { url = "https://files.pythonhosted.org/packages/92/79/15be162e0a2ed546939674e2e97d0e33ec2447d86d4d4e611fa295bb178c/matplotlib-3.11.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:1524e2bdd48a93557aa47ddcfe9c225dfdd57d5a01a5c49128c20f0632980ee1", size = 9336148, upload-time = "2026-07-18T03:38:53.564Z" }, + { url = "https://files.pythonhosted.org/packages/6a/7f/36ffe144fc4aacfe0e3ed2318f72b6755d1e73b041d619b4d393e60f5a66/matplotlib-3.11.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:11664c551345553db92e61cae6cf1376f138f8c47cafdf13b64b18f3e3e9e464", size = 10049244, upload-time = "2026-07-18T03:38:55.911Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5f/55812d68c0a840d3a463638f48c00ab1fe338518ec49a640cb6473b444af/matplotlib-3.11.1-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e1f8922ba31959cf6a9dfb51be64b7f7bc582801a3957dc0c2f3afcd3537adf", size = 10860798, upload-time = "2026-07-18T03:38:58.282Z" }, + { url = "https://files.pythonhosted.org/packages/7a/64/cca444b4eb5e6c768c44fc5e1f0b5211f20ca2b282778051996e996a2bdf/matplotlib-3.11.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83235693abde86e5e0129998f80ee39fc7f58e6d56a88fafb28a9278833e9d5f", size = 10943282, upload-time = "2026-07-18T03:39:00.465Z" }, + { url = "https://files.pythonhosted.org/packages/e5/0f/a49c329d394f2e9ef38506982107e8b04ecf94dd41a9d8423ff82cc737c7/matplotlib-3.11.1-cp313-cp313t-win_amd64.whl", hash = "sha256:9a076f4fc5cdc43fdf510f5981418d25c2db4973418d9f22d8bb3dc8045ada78", size = 9383532, upload-time = "2026-07-18T03:39:02.468Z" }, + { url = "https://files.pythonhosted.org/packages/e4/50/103e86afb806d8f64d04ede14e4cfc09dbfc25f512421ff85fdd6ebd59cf/matplotlib-3.11.1-cp313-cp313t-win_arm64.whl", hash = "sha256:216fbb93a74add02ddb4cb38ef5348f59ac00b3e84567eaf16598772d40e150a", size = 9059665, upload-time = "2026-07-18T03:39:04.607Z" }, + { url = "https://files.pythonhosted.org/packages/35/04/3079499fa8cb661ea66d13d6439d5a3ae6710a7afd5c7f72e08914f275f8/matplotlib-3.11.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:30c492d4ba9448595b6fd8708c6725963f8148e25c0d8842948da5b05f0ee8d3", size = 9456022, upload-time = "2026-07-18T03:39:07.041Z" }, + { url = "https://files.pythonhosted.org/packages/53/a2/69acfe84ec1f32930e801a5782a07fc5c79c8c6599a507b806d859d5da8e/matplotlib-3.11.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ac104be2768ffdd8655db9e71b768cbb45f2b9aa7b450cf1595e8f65d3822319", size = 9285475, upload-time = "2026-07-18T03:39:09.562Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b3/31b15a2ca56d4ddd6aaa1c884c2f51cf9a61cfaf5ca6f6fbd6343d38e6df/matplotlib-3.11.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be943cb68bc6660ead58c55b3aa6366cba2ef7feb06460fbcce32360376f19f", size = 10847102, upload-time = "2026-07-18T03:39:11.532Z" }, + { url = "https://files.pythonhosted.org/packages/64/0d/a17e966e620545c1548125af0b29ac812dd17b197a18a7462ac12fa859ee/matplotlib-3.11.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5af0dcda57d471440a7b5b623e70e0a61003518443d9098f211a96ecfbbc25be", size = 11131087, upload-time = "2026-07-18T03:39:13.764Z" }, + { url = "https://files.pythonhosted.org/packages/97/c5/5e100efdd67abb7de20befaa333612ef9bfc63417fb71398f904f25d083c/matplotlib-3.11.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3d3fd84082b1afbd9398466c81309e20045be20d48fe0fb18c43504d164cbbb2", size = 10929036, upload-time = "2026-07-18T03:39:16.888Z" }, + { url = "https://files.pythonhosted.org/packages/ce/04/d719a0a36930ecc8dfc801ff340f9dcfc4223f8ca5d39d06b4020032fff8/matplotlib-3.11.1-cp314-cp314-win_amd64.whl", hash = "sha256:9601a1e90be21e4884c53b4f3dc3ee0544654946f9975258d691f1c2e2f119c6", size = 9489571, upload-time = "2026-07-18T03:39:19.449Z" }, + { url = "https://files.pythonhosted.org/packages/48/65/facabdc2f1f6caba7e856db64dfedddca25f7608df07d96a1c8fd114fd3b/matplotlib-3.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:ae30c6109848ac0f9fa36c5d6270938487614c47ba31860bd5361266dabc5685", size = 9164486, upload-time = "2026-07-18T03:39:21.424Z" }, + { url = "https://files.pythonhosted.org/packages/88/dd/18da6cd01cf96354534f98c468a25380c68ce582a2c9dd0cae12b04af4f2/matplotlib-3.11.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:dadfe80797174e2984aae3be0b77594a3c72d2c0a40fbd4a0de48d2728caf3ae", size = 9504876, upload-time = "2026-07-18T03:39:23.633Z" }, + { url = "https://files.pythonhosted.org/packages/79/b0/f0b63555a18b79d038c81fd6126f35fc4dfce0eaff48d96103348c7cf935/matplotlib-3.11.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:89b193b255f4f6f7948dbcee3691f4f341ab05d9a8874a67b45ddb4182922eda", size = 9336120, upload-time = "2026-07-18T03:39:25.797Z" }, + { url = "https://files.pythonhosted.org/packages/c6/dd/f210ec7c4a6f198d5567237048a93d0811fb5a1f1691f13320e592f95b41/matplotlib-3.11.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191163532cdefcb1571ca38a6d7e6474baccde64495783e6ba47aa07ec4b9bbb", size = 10858033, upload-time = "2026-07-18T03:39:27.999Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d2/d6d5324507c5fbb316db48e258c09c2807f3de03d9af47017e120070926f/matplotlib-3.11.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9fdf1c818ab05d0e74002091ddaf414478a3a449ec9d51c8976d45be7e3a01e2", size = 11141827, upload-time = "2026-07-18T03:39:30.092Z" }, + { url = "https://files.pythonhosted.org/packages/0f/68/3c22e9320bdce2c4d2f1320643ef706db7a24cb7420eea28b97a2d67f5a8/matplotlib-3.11.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b937b9dba5f5f6c1e31c47abe2186c865c0914fd18f2ce0dfc39c9adcef5951d", size = 10943061, upload-time = "2026-07-18T03:39:32.356Z" }, + { url = "https://files.pythonhosted.org/packages/f6/4a/907ed190ee81a9df581e0ed5456134fc0f7cb55ffcfda2f9e54ca900761c/matplotlib-3.11.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f2912f647f3fbe1ccf085f91e213936f9101bead81a5e670565b1f1b3712f4fb", size = 9540074, upload-time = "2026-07-18T03:39:34.789Z" }, + { url = "https://files.pythonhosted.org/packages/23/d4/97c19b77e0a6e3b48581185bb65088f431cd20186076cc0f650a1757ea46/matplotlib-3.11.1-cp314-cp314t-win_arm64.whl", hash = "sha256:54d47b8ae8b579633a3902ca5b4ad6c1e132a5626d64447b2e22a66394e79987", size = 9213472, upload-time = "2026-07-18T03:39:37.141Z" }, + { url = "https://files.pythonhosted.org/packages/ee/38/ceb1d637c4db6d06141f3739e93af3321e7caaabe69b57ae48ffe3ee95b1/matplotlib-3.11.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:427258425f9a3fc4ed79a91f9e9b9aaf5a82cb6571e85dc14063cc6fbb993741", size = 9438045, upload-time = "2026-07-18T03:39:39.491Z" }, + { url = "https://files.pythonhosted.org/packages/89/25/72ad8b58602d3a6ef1dfc4b65ecd01634ab65a2bdf494c9fe0e966dbf081/matplotlib-3.11.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:1ac697e591c11b6ad04679a73c2d2f9980fe9d9f0311fb414a2e329706343dfb", size = 9266127, upload-time = "2026-07-18T03:39:41.597Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6d/69552382fcc8e93d1f2763ef2665980a900a48b7f3a4c57ed290726d1cbc/matplotlib-3.11.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e4b9ac2f1f607ecda2af90a5232beee2af7582fce1cc30c4b6a1b012dc21ee99", size = 10019439, upload-time = "2026-07-18T03:39:43.78Z" }, ] [[package]] @@ -1699,7 +1709,7 @@ dev = [ { name = "lit" }, { name = "nanobind" }, { name = "nox" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "pytest" }, { name = "pytest-console-scripts" }, { name = "pytest-cov" }, @@ -1730,7 +1740,7 @@ docs = [ { name = "sphinxext-opengraph" }, ] test = [ - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "pytest" }, { name = "pytest-console-scripts" }, { name = "pytest-cov" }, @@ -1746,7 +1756,7 @@ provides-extras = ["qiskit"] [package.metadata.requires-dev] build = [ { name = "nanobind", specifier = "~=2.13.0" }, - { name = "scikit-build-core", specifier = "~=0.12" }, + { name = "scikit-build-core", specifier = "~=1.0.3" }, { name = "setuptools-scm", specifier = ">=9.2.2" }, ] dev = [ @@ -1757,11 +1767,11 @@ dev = [ { name = "numpy", marker = "python_full_version >= '3.14'", specifier = ">=2.3.2" }, { name = "pytest", specifier = ">=9.0.1" }, { name = "pytest-console-scripts", specifier = ">=1.4.1" }, - { name = "pytest-cov", specifier = ">=7.0.0" }, + { name = "pytest-cov", specifier = ">=7.1.0" }, { name = "pytest-sugar", specifier = ">=1.1.1" }, { name = "pytest-xdist", specifier = ">=3.8.0" }, { name = "qiskit", extras = ["qasm3-import"], specifier = ">=1.1.0" }, - { name = "scikit-build-core", specifier = "~=0.12" }, + { name = "scikit-build-core", specifier = "~=1.0.3" }, { name = "setuptools-scm", specifier = ">=9.2.2" }, ] docs = [ @@ -1787,7 +1797,7 @@ test = [ { name = "numpy", marker = "python_full_version >= '3.14'", specifier = ">=2.3.2" }, { name = "pytest", specifier = ">=9.0.1" }, { name = "pytest-console-scripts", specifier = ">=1.4.1" }, - { name = "pytest-cov", specifier = ">=7.0.0" }, + { name = "pytest-cov", specifier = ">=7.1.0" }, { name = "pytest-sugar", specifier = ">=1.1.1" }, { name = "pytest-xdist", specifier = ">=3.8.0" }, { name = "qiskit", extras = ["qasm3-import"], specifier = ">=1.1.0" }, @@ -1826,12 +1836,12 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "jinja2", marker = "python_full_version < '3.11'" }, - { name = "markdown-it-py", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "mdit-py-plugins", marker = "python_full_version < '3.11'" }, - { name = "pyyaml", marker = "python_full_version < '3.11'" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" } }, + { name = "jinja2" }, + { name = "markdown-it-py", version = "3.0.0", source = { registry = "https://pypi.org/simple" } }, + { name = "mdit-py-plugins" }, + { name = "pyyaml" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/66/a5/9626ba4f73555b3735ad86247a8077d4603aa8628537687c839ab08bfe44/myst_parser-4.0.1.tar.gz", hash = "sha256:5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4", size = 93985, upload-time = "2025-02-12T10:53:03.833Z" } wheels = [ @@ -1857,12 +1867,12 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "jinja2", marker = "python_full_version >= '3.11'" }, - { name = "markdown-it-py", version = "4.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "mdit-py-plugins", marker = "python_full_version >= '3.11'" }, - { name = "pyyaml", marker = "python_full_version >= '3.11'" }, - { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" } }, + { name = "jinja2" }, + { name = "markdown-it-py", version = "4.2.0", source = { registry = "https://pypi.org/simple" } }, + { name = "mdit-py-plugins" }, + { name = "pyyaml" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/21/dc/603751677fff302f34396e206b610f556a59d7fe58b9a2145f54e96b48e8/myst_parser-5.1.0.tar.gz", hash = "sha256:ab69322dc6719dcc7f296479dbb70181b66df6ed315064f92dbc85c0e1bf2f02", size = 101182, upload-time = "2026-05-13T09:38:19.361Z" } @@ -1920,7 +1930,7 @@ wheels = [ [[package]] name = "nox" -version = "2026.4.10" +version = "2026.7.11" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "argcomplete" }, @@ -1932,9 +1942,9 @@ dependencies = [ { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e7/6b/e672c862a43cfca704d32359221fa3780226daa1e5db5dfc401bcc8be9c9/nox-2026.4.10.tar.gz", hash = "sha256:2d0af5374f3f37a295428c927d1b04a8182aa01762897d172446dda2f1ce9692", size = 4034839, upload-time = "2026-04-10T17:42:42.209Z" } +sdist = { url = "https://files.pythonhosted.org/packages/75/bf/aafe066019cb1bcd3e1c22957412d6eb560bd6553c1afd28502168a51418/nox-2026.7.11.tar.gz", hash = "sha256:dec9bd2c854540a2d5c0b841eaaf1d23a7c26cd90af36d9f1f1668b34524bfd9", size = 4042267, upload-time = "2026-07-12T00:32:19.859Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/95/4df134a100b5a9a12378d5301b934366686ef6fbdaffcd21211d5654970e/nox-2026.4.10-py3-none-any.whl", hash = "sha256:082c117627590d9b90aa21f86df89b310b07c5842539524203bcb3c719f116c1", size = 75536, upload-time = "2026-04-10T17:42:40.664Z" }, + { url = "https://files.pythonhosted.org/packages/a0/70/4b459b66bbd7c9be3c9b15f2cf1605bbe10f58c6395fd99a59a21f3c0777/nox-2026.7.11-py3-none-any.whl", hash = "sha256:f5e811693ee8374d269396204eb39990d2084da67ed968239f94301805c9a169", size = 77265, upload-time = "2026-07-12T00:32:18.07Z" }, ] [[package]] @@ -2088,7 +2098,7 @@ wheels = [ [[package]] name = "numpy" -version = "2.5.0" +version = "2.5.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", @@ -2101,51 +2111,51 @@ resolution-markers = [ "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] -sdist = { url = "https://files.pythonhosted.org/packages/e7/05/3d27272d30698dc0ecb7fdfaa41ad70303b444f81722bb99bce1d818638a/numpy-2.5.0.tar.gz", hash = "sha256:5a129578019311b6e56bdd714250f19b518f7dceeeb8d1af5490f4942d3f891c", size = 20652461, upload-time = "2026-06-21T20:57:51.95Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/0a/11486d02add7b1384dff7374d124b1cfbb0ee864dcc9f6a2c0380638cf84/numpy-2.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:489780423903667933b4ed6197b6ec3b75ea5dd17d1d8f0f38d798feb6921561", size = 16789987, upload-time = "2026-06-21T20:56:16.657Z" }, - { url = "https://files.pythonhosted.org/packages/55/b2/285f48640a181947b4587a3766d21ec1eaa7fea833d4b49957e09da467a2/numpy-2.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ece55976ced6bca95a03ae2839e2e5ccffe8eb6a3e7022415645eb154a81e4e6", size = 11760322, upload-time = "2026-06-21T20:56:19.813Z" }, - { url = "https://files.pythonhosted.org/packages/dd/67/b032db1eb03ca30d16eda3b0c22aaa615338b9263c2fd559d0f29451aca4/numpy-2.5.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:c83b664b0e6eee9594fa920cf0639d8af796606d3fad6cc70180c87e4b97c7be", size = 5319605, upload-time = "2026-06-21T20:56:22.173Z" }, - { url = "https://files.pythonhosted.org/packages/b9/83/03fc7300c7c6b6c84c487b1dc80d322817b95fbd1f4dd57a85e23b7198de/numpy-2.5.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bf80333980bf37f523341ddd72c783f39d6829ec7736b9eb99086388a2d52cc2", size = 6653628, upload-time = "2026-06-21T20:56:23.914Z" }, - { url = "https://files.pythonhosted.org/packages/82/49/2ec21730bc63ccfda829323f7040a8ed4715b3852ce658689cf74ee96a8c/numpy-2.5.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1a4874217b36d5ac8fc876f52e39df56f8182c88463e9e2dceabf7ca8b7efb8", size = 15153691, upload-time = "2026-06-21T20:56:25.631Z" }, - { url = "https://files.pythonhosted.org/packages/bb/6b/f4a3d0637692c49da8ef99d72d52526f92e0a8d6ac4f0ca9f31441b9d9ea/numpy-2.5.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaa760137137e8d3c920d27927748215b56014f92667dc9b6c27dfc61249255a", size = 16660066, upload-time = "2026-06-21T20:56:28.009Z" }, - { url = "https://files.pythonhosted.org/packages/3a/2f/c354ec86d1f3f5c19649463b0d39652e160736e5b0a4cd18dff0576715c4/numpy-2.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7174ce8265fc7f7417d171c9ea8fe905220748893ea67a2a7abe726ec331c4b0", size = 16514638, upload-time = "2026-06-21T20:56:30.26Z" }, - { url = "https://files.pythonhosted.org/packages/06/34/43efdcb319988648580f93c11f1ae82cf7e2faa74925e98e454ae3aa95f8/numpy-2.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b8c3daaf99de52415d20b42f8e8155c78642cb04207d02f9d317a0dcf1b3fb54", size = 18419647, upload-time = "2026-06-21T20:56:32.41Z" }, - { url = "https://files.pythonhosted.org/packages/71/e2/f5d1676b1d7fb682eb5e9a1641e7ebd2414b3216c370661d1029778908b4/numpy-2.5.0-cp312-cp312-win32.whl", hash = "sha256:6206db0af545d73d068add6d992279145f158428d1da6cc49adc4b630c5d6ee5", size = 6056688, upload-time = "2026-06-21T20:56:34.657Z" }, - { url = "https://files.pythonhosted.org/packages/8f/7c/48f115d1c58a34032facebcd51fdf2d02df2c51d4a46a81dd1197bb2ea6b/numpy-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:6f2d6873e2940c860a309d21e25b1e69af6aaffdd80aa056b04c16380db1c4f2", size = 12419237, upload-time = "2026-06-21T20:56:36.24Z" }, - { url = "https://files.pythonhosted.org/packages/86/26/2e0882f4044d1b1a1b63e875151fb2393389032022a8b7f5657a7996d3b2/numpy-2.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:a55e1eb2bca2cfd17a16b213c99dfc8502d47b0d494224d2122277d0400935ca", size = 10339912, upload-time = "2026-06-21T20:56:38.733Z" }, - { url = "https://files.pythonhosted.org/packages/8a/33/07675aaad7f26ea013d5e884d9a0d784b79c6bd7566c333f5a52fa3c610b/numpy-2.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:520e6b8be0a4b65840ac8090d4f51cef4bed66e2b0894d5a520f099adc24a9b2", size = 16784890, upload-time = "2026-06-21T20:56:40.799Z" }, - { url = "https://files.pythonhosted.org/packages/85/4b/953118a730ee3b35e28645e0eb4cf9beec5bdbb954e1ac2f5fcefba6bbc3/numpy-2.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:146b81cdd3967fdb6beca8ba25f00c58741d8f3cbd797f55af0fbe0bfec3469c", size = 11754584, upload-time = "2026-06-21T20:56:43.094Z" }, - { url = "https://files.pythonhosted.org/packages/44/9b/56dd530c367c74ae17411027cea4135ca57e1e0583bf5594cee18bd83217/numpy-2.5.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:126b88d95e8ff9b00c9e717aa540469f21d6180162f84c0caec51b16215d49cd", size = 5313904, upload-time = "2026-06-21T20:56:45.503Z" }, - { url = "https://files.pythonhosted.org/packages/ce/b0/bcd672edad27ecca7da1f7bb0ce72cd1706a4f2d79ae94990afc97c13e1c/numpy-2.5.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d4313cef1594c5ce46c31b6e54e918338f63f16ee9322304e8c9114d6d81c8bd", size = 6648504, upload-time = "2026-06-21T20:56:47.567Z" }, - { url = "https://files.pythonhosted.org/packages/80/9e/15cdfcbd30a1544a46c9e487a00df331c4672450216538705a9e51fa6710/numpy-2.5.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:750fb097caf26fa878746d9d119f6f9da12dedcbff1eea966c3e3447647c4a9e", size = 15150086, upload-time = "2026-06-21T20:56:49.352Z" }, - { url = "https://files.pythonhosted.org/packages/32/4e/8d7656ccaab3e81e97258b8a9bc5f0c8502513a92fb4ceb0a2cbfebc17bf/numpy-2.5.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3893adc2dc7c0412ba76777db55a049215d99c9aa3113003be8f49f4f1290ab9", size = 16647250, upload-time = "2026-06-21T20:56:51.542Z" }, - { url = "https://files.pythonhosted.org/packages/3c/81/97060281b602ed07f21b12f4ec409eac1f75a2f91fbc829ed8b2becf3ad4/numpy-2.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:835e454dd99b238cdc5a3f63bce2371296f5ebc53ca1e0f8e6ddbb6d92a29aab", size = 16512864, upload-time = "2026-06-21T20:56:55.401Z" }, - { url = "https://files.pythonhosted.org/packages/33/ab/4496208146911f8d8ddb54f68a972aafa6c8d44babcb2ea03b0e5cc87c9d/numpy-2.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f9836778081a0a3c02a6a21493f3e9f5b311f8d2541934f31f05583dc999ea4", size = 18408407, upload-time = "2026-06-21T20:56:57.75Z" }, - { url = "https://files.pythonhosted.org/packages/d4/9f/a4df67c181e4ee8b467aa3332dc2db10fd5c515136831302f3ca48bc0a01/numpy-2.5.0-cp313-cp313-win32.whl", hash = "sha256:0b525be4744b60bb0557ac872d53ef07d085b5f39622bc579c98d3809d05b988", size = 6054431, upload-time = "2026-06-21T20:57:00.016Z" }, - { url = "https://files.pythonhosted.org/packages/30/53/491e1c47c55b62ccc6a63c1c5b8635c73fc2258dddeb9bda27cae4a0ae96/numpy-2.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:44353e2878930039db472b99dc353d749826e4010bd4d2a7f835e94a97a5c748", size = 12414420, upload-time = "2026-06-21T20:57:01.815Z" }, - { url = "https://files.pythonhosted.org/packages/eb/4a/25c2906f541e9d9f4c5769764db732e6627be91a13f4724fa10634d77db4/numpy-2.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:48f54b00711f83a5f796b70c518e8c2b3c5848dda03a54911f23eb68519b9b60", size = 10339533, upload-time = "2026-06-21T20:57:03.961Z" }, - { url = "https://files.pythonhosted.org/packages/86/ad/abc44aaceaf7b17ee1edde2bbb4458da591bc79574cffff50c4bb35f00d1/numpy-2.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f27582c55ba4c750b7c58c8faf021d2cd9324a662b466229db8a417b41368af9", size = 16783807, upload-time = "2026-06-21T20:57:06.253Z" }, - { url = "https://files.pythonhosted.org/packages/5d/39/b72e168daf9c00fb20c9fc996d00437ccecdef3102387775d29d7a62576d/numpy-2.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:28e7137057d551e4a83c4ae414e3451f50568409db7569aacc7f9811ee06a446", size = 11765215, upload-time = "2026-06-21T20:57:08.547Z" }, - { url = "https://files.pythonhosted.org/packages/f7/a0/8400a9c0e3625182347593f5e1f57da9a617a534794805c8df5518154ddc/numpy-2.5.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:e1da54b53e75cd9fcfc23efcc7edab2c6aecf97b6037566d8a0fe804af8ec57c", size = 5324493, upload-time = "2026-06-21T20:57:11.012Z" }, - { url = "https://files.pythonhosted.org/packages/f6/8c/0d104deaa0401c93395a629ec902891618a2eff76d19229139cb5a887bfc/numpy-2.5.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:694d8f74e156f7fd01179f1aa8faa2f648ab6ae0f70b6c3fe57a03249aea2303", size = 6645211, upload-time = "2026-06-21T20:57:12.919Z" }, - { url = "https://files.pythonhosted.org/packages/6a/d9/4a4a628c812750363786afc3d33492709a5cd64b215469c16b0f6c7bb811/numpy-2.5.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a7569a7b53c77716f036bb28cb1c91f166a26ec7d9502cd1e4bdfe502fdec22", size = 15166004, upload-time = "2026-06-21T20:57:14.717Z" }, - { url = "https://files.pythonhosted.org/packages/a0/5e/2a902317d7fc4aa93236e80c932662dadfc459b323d758329e01775125e1/numpy-2.5.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39a0433bd4086ebd462960cf375e19195bb07b53dc1d87dd5fcf47ad78576f03", size = 16650797, upload-time = "2026-06-21T20:57:16.906Z" }, - { url = "https://files.pythonhosted.org/packages/e9/a0/a0090e6329f4ca5992c07847bb579c5259a19953dc57255bb08793142ffb/numpy-2.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:929f0c79ac38bcbd7154fe631dc907abfeddbcc5027a896bd1f7767323271e7a", size = 16524647, upload-time = "2026-06-21T20:57:19.165Z" }, - { url = "https://files.pythonhosted.org/packages/5e/7d/6caf27734c42b65837e7461ed0dbbd6b6fc835060c9714ec59d673bb383a/numpy-2.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cc4f247a47bbf070bfd70be53ccdcf47b800af563535e7bbe172322197c30e21", size = 18411841, upload-time = "2026-06-21T20:57:21.638Z" }, - { url = "https://files.pythonhosted.org/packages/13/dc/26edadbd812536769a82c2e9e002234e33feb5da43061d47a044f6d309b7/numpy-2.5.0-cp314-cp314-win32.whl", hash = "sha256:5dc71423499fab3f46f7a7201155ade1669ea101f2f429d332df9e72f8161731", size = 6106361, upload-time = "2026-06-21T20:57:23.844Z" }, - { url = "https://files.pythonhosted.org/packages/f2/9e/4dd1459282229a72d92dece2ae9138e5cac94a72263a7ceb48f37434c925/numpy-2.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:ebb81d9d5443e0309d6c54894c3fbed74ad7da0714352a67b6d773cd189eae73", size = 12551749, upload-time = "2026-06-21T20:57:25.945Z" }, - { url = "https://files.pythonhosted.org/packages/05/a7/6bc6384c080b86c7f6c85c5bc5b540b24f4f679cd144791d99574e90d462/numpy-2.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:3b94d0d0deceebfad3e67ae5c0e5eb87371e8f7a0581cd04a779928c2450cf1e", size = 10617072, upload-time = "2026-06-21T20:57:28.175Z" }, - { url = "https://files.pythonhosted.org/packages/86/6b/4a2b71d66ada5608ae02b63f150dfad520f6940721cb7f029ad270befc0e/numpy-2.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:22f3d43e362d650bc39db1f17851302874a148ca95ba6981c1dfb5fa6862f35b", size = 11881067, upload-time = "2026-06-21T20:57:30.104Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b2/d365eb40a20efb49d67e9feb90494ed8511282ee1f5fa16006675c65397d/numpy-2.5.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:243563efb4cd7528a264567e9fd206c87826457322521d06206a00bfa316c927", size = 5440290, upload-time = "2026-06-21T20:57:32.193Z" }, - { url = "https://files.pythonhosted.org/packages/fa/5e/e9c03188de5f9b767e46a8fe988bcfd3efad066a4a3fda8b9cb11a93f895/numpy-2.5.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:84881d825ca75249b189bbee875fcfe3238aa5c479e6100893cda566e8e86826", size = 6748371, upload-time = "2026-06-21T20:57:33.933Z" }, - { url = "https://files.pythonhosted.org/packages/fd/1d/68c186a38a5027bae2c4ddd5ea681fdaf8b4d30fb7301def6d8ad270390f/numpy-2.5.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cda12aa4779d42b8771180aba759c96f527d43446d8f380ab59e2b35e8489efd", size = 15214643, upload-time = "2026-06-21T20:57:35.677Z" }, - { url = "https://files.pythonhosted.org/packages/8c/67/73f67b7c7e20635baae9c4c3ead4ae7326a005900297a6110971abd62eb5/numpy-2.5.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c0121101093d2bd74981b10f8837d78e794a8ff57834eb27179f49e1ba11ac6", size = 16690128, upload-time = "2026-06-21T20:57:38.159Z" }, - { url = "https://files.pythonhosted.org/packages/eb/05/d4c1fb0c46d02a27d6b2b8b319a78c90937acec8631c1641874670b31e6f/numpy-2.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d371c92cfa09da00022f501ab67fafaea813d752eb30ac44336d45b1e5b0268a", size = 16577902, upload-time = "2026-06-21T20:57:40.447Z" }, - { url = "https://files.pythonhosted.org/packages/9e/1d/771c797d50fa26e4888989cccf1d50ee51f530d4e455ad2692dcb64fa711/numpy-2.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9990713e9c38154c6861e7547f1e3fc7a87e75ff09bab24ef1cc81d81c2835e9", size = 18452814, upload-time = "2026-06-21T20:57:42.875Z" }, - { url = "https://files.pythonhosted.org/packages/e8/46/52fc0d2a68d7643f0f149eeea5a5d8ea2a3507056ac8afa83c9212606e8b/numpy-2.5.0-cp314-cp314t-win32.whl", hash = "sha256:edadfbd4794b1086c0d822f81863e8a68fc129d132fd0bb9e31e955d7fbbbdb7", size = 6253168, upload-time = "2026-06-21T20:57:45.101Z" }, - { url = "https://files.pythonhosted.org/packages/2a/be/6c8d1118b5f13b2881dc095d5b345de19c6638b8959c17409b6eff84c8aa/numpy-2.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f7e5fa4382967ae6548bd2f174219afb908e294b0d5f625af01166edd5f7d9aa", size = 12736286, upload-time = "2026-06-21T20:57:46.935Z" }, - { url = "https://files.pythonhosted.org/packages/fd/6a/d3a169aaf8536cf228d56a09e04bcb713a2fe4410d4e2105b9419b5a9c89/numpy-2.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:016623417bb330d719d579daf2d6b9a01ddc52e41a9ed61a47f39fde46dcd865", size = 10686451, upload-time = "2026-06-21T20:57:49.313Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/22/fd/89965aa4ac08c74998539fcbf24fa3540f3e15237fbeb6bcf9c908f4aade/numpy-2.5.1.tar.gz", hash = "sha256:a48a113e6afea91f5608793bafa7ef2ad481fefbda87ec5069f483de61cb9fa3", size = 20755553, upload-time = "2026-07-04T17:08:00.933Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/7b/14687aa674250e5e546f616f486b0d56d3631cd5b2415739141ce40bdcea/numpy-2.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c889b56fe48b1018f764b0eec8df59ab654e9148aa91faa12596043500de277", size = 16801574, upload-time = "2026-07-04T17:06:12.423Z" }, + { url = "https://files.pythonhosted.org/packages/e1/19/cc5bb2a3f2913d27d6dbb2c78d25921fabaedc6741d4a5a615a11f3c5bf3/numpy-2.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab451b59c5643c570974c43aef780703ef1d3b4965d2be07afd530615a9358d1", size = 11772250, upload-time = "2026-07-04T17:06:15.726Z" }, + { url = "https://files.pythonhosted.org/packages/42/77/fdf34a71dd30f54979b18603bee915e0aaf825b07afe79acd60b04b691e2/numpy-2.5.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:78798bd5b9ad744056af8efa90e3b9ddaa53272a0848a483084a1cc0a13b2dc0", size = 5331516, upload-time = "2026-07-04T17:06:17.913Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e2/eb7efa015b4cce41e2517bf182a7fce0d7d5b9d9ed76a29bfa0f4fe4505c/numpy-2.5.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2ae0ca40bcb22d6ba59c1dfd5446f49940b0f2d821fde133f10dda11f816b84e", size = 6664863, upload-time = "2026-07-04T17:06:20.02Z" }, + { url = "https://files.pythonhosted.org/packages/a9/4b/a2b32dd94ee9ffbeecb28152240042a3949db33b1c834d44090b80e1b3b8/numpy-2.5.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61ac47e772e6b8ea489e1d2f441a34c5c3ac17327e7ce294cbdf535795ad4e75", size = 15167977, upload-time = "2026-07-04T17:06:21.621Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a9/6e73d68500f80773f65f0654ea932019d6694329a0eb0ed0533de38df376/numpy-2.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:59fda5e192b570217ec2580c96f00e9a7e12ef6866a900eb089b62c1a32545ca", size = 16672469, upload-time = "2026-07-04T17:06:24.064Z" }, + { url = "https://files.pythonhosted.org/packages/24/7d/ad3e59015135f5261c95fd4cafeff159c955febd83a99a1d9250c4233815/numpy-2.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f7119ebff1a9829e9f431a4f9d28e703023bb6b9fe7c8f724467dbfc27c94ab3", size = 16527531, upload-time = "2026-07-04T17:06:26.69Z" }, + { url = "https://files.pythonhosted.org/packages/83/d0/a39b2fbcde9cb17a1dac678f254b33a6336298af9df338824c685425d5e8/numpy-2.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e824c2acf8862052246be5a44c15da1777940c60d010dd2aab897824d9c430f9", size = 18431940, upload-time = "2026-07-04T17:06:29.521Z" }, + { url = "https://files.pythonhosted.org/packages/04/12/cff070947791c1ed425ff76413189adbdc2fbe215eba7ce7fa454a03c7f8/numpy-2.5.1-cp312-cp312-win32.whl", hash = "sha256:08d60c810432eb83360958dea0999ac4cfb94531ea8efcbf0b7f277c2068aeb2", size = 6066764, upload-time = "2026-07-04T17:06:32.571Z" }, + { url = "https://files.pythonhosted.org/packages/65/66/53f31807a48a750f9d748da273bc3fcedd12b27ff1f3e373bfec55ef2dc0/numpy-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7d60026c0bdb1380e83bfa7a0419c4577ee4b9a08880afcb6dadeb74c649fa2", size = 12430966, upload-time = "2026-07-04T17:06:34.926Z" }, + { url = "https://files.pythonhosted.org/packages/2b/2a/d1a88066b1c14186f5d3c0d18c94f17b064511982bab0578d49ee9d43c29/numpy-2.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:17a25e09640602e10bc8de0e6fa2b3fd68eedd84ba6d7842dc8f32f9ab87bd0b", size = 10350488, upload-time = "2026-07-04T17:06:37.785Z" }, + { url = "https://files.pythonhosted.org/packages/eb/07/ec2a3f0c91761581d4b7104a740791800025983f9a4dc4e73f91a99aeac4/numpy-2.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0bfebd8695f9863592fe744be833a258120b14a9f39da255e8aa8fade2c0ddd1", size = 16796419, upload-time = "2026-07-04T17:06:40.37Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ab/ddb499fc4f8780354395face5b65c7fd107bcd6e1d667a5f07d046956f6f/numpy-2.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:30b44a6b53a7ae63c54c089a8726e5563ed302716c5b7ccc85afade40b0e7ff6", size = 11765832, upload-time = "2026-07-04T17:06:42.768Z" }, + { url = "https://files.pythonhosted.org/packages/88/b3/3c28c558a09fc72100c646dac6d2fce8e834c471b0edca01a29996706117/numpy-2.5.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6165343f81b56ef8f514f396989e529b61d9dc709b99421b07e9f3e698e2287d", size = 5325143, upload-time = "2026-07-04T17:06:45.466Z" }, + { url = "https://files.pythonhosted.org/packages/5e/0e/ce19b985bb15c596f4f05954e76cccc77c845083b3b8f938a6c68e523128/numpy-2.5.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4939237038ada79308dda3204ac6462df056b5672b2e25db1149cf873668b3e1", size = 6659749, upload-time = "2026-07-04T17:06:47.288Z" }, + { url = "https://files.pythonhosted.org/packages/2e/20/1ee6614d64332a1bba6411f38e68cb79eec1b2459e20a623777c5c5492a2/numpy-2.5.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c6759f538fb912fc46de0a6b1758ccf7b57bc7c7ebebc23974fdac3de8db0cd", size = 15164716, upload-time = "2026-07-04T17:06:49.494Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a7/2bcd3fdbb87804755c35b729bf8709d62025c5f4cfd7d5b2415997097515/numpy-2.5.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9726558e8db4a5bf7929a70ae50f63abda4daf0efe810e3bfbab95976f75fc1a", size = 16661440, upload-time = "2026-07-04T17:06:52.061Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d7/a41e3310c886fe457d36e670bbf24fae411aca8a7b6ad92a32afd924077c/numpy-2.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3935f3b419b244a02732676fa5317a9193cc596a4c0646db07e5b421229ac9f7", size = 16526305, upload-time = "2026-07-04T17:06:54.605Z" }, + { url = "https://files.pythonhosted.org/packages/53/75/4333a9a707c1edd3a4e1a0c58eca52c0f31e55089fa80db02b5565b24df7/numpy-2.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc932a65ded7ce9013d120845a2514dcccb1a67bfc8deb8d37633762951904a6", size = 18423008, upload-time = "2026-07-04T17:06:57.54Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/e314a32b1c11a2ffe818ddad3a57b50b4b6e1b6c487192eb50cdef0415d0/numpy-2.5.1-cp313-cp313-win32.whl", hash = "sha256:4b4ff1608417eb7a59da7b967bbb798cacfe071d2caf526a24281cd562072ed9", size = 6063885, upload-time = "2026-07-04T17:07:00.14Z" }, + { url = "https://files.pythonhosted.org/packages/10/70/800b3fca480af32df9e8ea9f3d4a0c8feb4b32d7f195d174eabbda4829ad/numpy-2.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:6c3fe51bc6a16453d452997053454f309e8e0ed7b42d6b361ce4ac8c32913d74", size = 12425674, upload-time = "2026-07-04T17:07:02.387Z" }, + { url = "https://files.pythonhosted.org/packages/8b/0b/196350c122f50f6ca56846f2d71efd5e0d24b7b2e07355e019b2e2c7a11e/numpy-2.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:f7feb014281029e628ba2d5a007407443b06e418b6fe451d1e2adcbc8eba0107", size = 10350256, upload-time = "2026-07-04T17:07:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/db/f4/731b6085a83faf6ca843394cbd5e217280c214399f7e8b21b9f552af0ae2/numpy-2.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7c786fe9a5bbe360022e584c5a34cf6b54265c71bd7ec8ac3d8fec38968071f8", size = 16795063, upload-time = "2026-07-04T17:07:07.374Z" }, + { url = "https://files.pythonhosted.org/packages/bf/64/0e215f2048dd11a55bb989ed41b3585ef57452404e638d703a211a3e4157/numpy-2.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32985c896d897419ef8da6917872d80b78ad0ea26d85b23245c7366ffde76d75", size = 11776652, upload-time = "2026-07-04T17:07:09.907Z" }, + { url = "https://files.pythonhosted.org/packages/b5/59/2b844c7a6e9deff69b404a66221e1542937734f65d5e6e39411876053862/numpy-2.5.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:efd736408cc97c79b9e6917338dfc8f06013b2274f992e96b1d9a81a71e2a2c2", size = 5335944, upload-time = "2026-07-04T17:07:12.227Z" }, + { url = "https://files.pythonhosted.org/packages/86/51/9bf7cb2cabcebc9e017e4ec7e6322b378317a542c08b4cb68479c1efc716/numpy-2.5.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ab84dc6b074fa881cae55bea94cc4f68e285181ba7f32497bf7dee6b1496165b", size = 6656266, upload-time = "2026-07-04T17:07:14.368Z" }, + { url = "https://files.pythonhosted.org/packages/83/3e/fb7615b211b82a32f44d5180a6d421b61f84d4fadd578b48ba4ac34e189f/numpy-2.5.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caf3e317d33d60c37986b452613f4ab51246d0691350c03d0cb4a898627f4a95", size = 15179720, upload-time = "2026-07-04T17:07:16.272Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/0f992cb24560673496c5d68de61913b57166ce530ffda07c1f280e0cc464/numpy-2.5.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54ad769f17bc2d833b620851989f62054fb9ab93c969d9e1dc3c8e3d56beea21", size = 16664835, upload-time = "2026-07-04T17:07:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2f/97d6475ee91afe2587797d09446f9d3e475ad4cb681662d824809327b75a/numpy-2.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c12afb53450fa976d4c681c50a7423729a4c51c0465ed9f32b8a9cabbc472373", size = 16539135, upload-time = "2026-07-04T17:07:22.015Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/4db81e4ba0be7e2776b1de68c82aa862c7f8ec27e1b4927d4ae075e20678/numpy-2.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e8c11c405efc5ff6816d5983c96cdfa215bab3428961243af3ff59b228490438", size = 18426684, upload-time = "2026-07-04T17:07:24.941Z" }, + { url = "https://files.pythonhosted.org/packages/1f/64/c0ba2d90724d450279a7df8f32057241070250a26a7e2b5337d77347f481/numpy-2.5.1-cp314-cp314-win32.whl", hash = "sha256:f2479a47f8d5932d1718168a681ad6e536a9df484c83cfcf9de365e164537ace", size = 6116103, upload-time = "2026-07-04T17:07:27.622Z" }, + { url = "https://files.pythonhosted.org/packages/c1/1a/837f9ed7405adcd7a40538792eb169eddd8fa5630c16a1ef49dae71a30f4/numpy-2.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:24d0eb82c0541d3415a33425db64ae439dffccd7b4dbcb30e7c35120205c506a", size = 12562177, upload-time = "2026-07-04T17:07:29.887Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/49707938b6dd0a78a9178dd93227dc89e4c11af47f5c798d70366e8d0483/numpy-2.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:5a4c988b38d261deeeaad9954e3deb091ad905c94e8bb6708654ef1d97f286b0", size = 10627739, upload-time = "2026-07-04T17:07:32.568Z" }, + { url = "https://files.pythonhosted.org/packages/a6/c7/bb4b882cfe7f299cbc8b66e42e7dd78cf9d14e40f9469fc5e3db7e15b3bd/numpy-2.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a33276be12fa045805f477f22482088b66bb758ffbe89a9d21457de863a32e22", size = 11894709, upload-time = "2026-07-04T17:07:34.941Z" }, + { url = "https://files.pythonhosted.org/packages/40/3f/5af7f4a7f6224aef48017aa82bb6174c7a659d724be0c75017b7e64a55b4/numpy-2.5.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:f089d7b00756190aacf1f5d34bdf38c3c430ac82b4f868f8cede73380460fce7", size = 5453810, upload-time = "2026-07-04T17:07:37.495Z" }, + { url = "https://files.pythonhosted.org/packages/20/c9/3474309bc94d634d3f9c3eddf03250ecb8c22cd948ef16fef69a77cc5d7b/numpy-2.5.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:09e9bfd8d2cf479c7d174804fb3811c53a8e9f20a37444008606b57d6b7a826d", size = 6761189, upload-time = "2026-07-04T17:07:39.563Z" }, + { url = "https://files.pythonhosted.org/packages/90/8a/558ae39fdd55d7e7f7fef9a84a6e964ac6b23edbd2a07e52bb084500507d/numpy-2.5.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e68d8dd1e7eba712948f2053a29ec86917bc70ba1358df869d9f06649ef9cf09", size = 15225039, upload-time = "2026-07-04T17:07:41.682Z" }, + { url = "https://files.pythonhosted.org/packages/63/27/ca7392b2d030277bdf0273e7d23255b3ee57d57a7c170a6f4fb3981e1e5d/numpy-2.5.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99d5095fa265a0c4152e7bb12759e14381ef5496152f1ce58f44bdf55c44beb4", size = 16701306, upload-time = "2026-07-04T17:07:44.611Z" }, + { url = "https://files.pythonhosted.org/packages/02/42/03d53ae7996c44d4374a8262e9dc41671fd56cbb98f7d47ef85cf5da4c6b/numpy-2.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ab87a91b3cc3382b8956095bd8f95e00cf679bb81554339be1a2ba404a1473c1", size = 16589955, upload-time = "2026-07-04T17:07:47.694Z" }, + { url = "https://files.pythonhosted.org/packages/7b/15/6c1784ae469640e65db111e9a34b3d0f14d91e8a38b9ce34810ced370dbb/numpy-2.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:224ca51130ef7da85bea2191625181cb4f337f9cb64b471f10c1a12aa8b60077", size = 18464252, upload-time = "2026-07-04T17:07:50.684Z" }, + { url = "https://files.pythonhosted.org/packages/94/a8/f98e50356cf167df656c526c2dfeec2d7dde182f2a3da4b458a5938e2776/numpy-2.5.1-cp314-cp314t-win32.whl", hash = "sha256:6eab239876581b2b3c5a242281b6007bbdbcd1c7085d7709bb57c5929b11e6bf", size = 6263298, upload-time = "2026-07-04T17:07:53.445Z" }, + { url = "https://files.pythonhosted.org/packages/72/ac/96ae880cdecad0b3275d9359fcec72667b49a4863c9f12942e43679dda02/numpy-2.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:83ce9c80d5b521b0d77ddcbe5447c218d247929b6cc056ca5351342accfff0af", size = 12748623, upload-time = "2026-07-04T17:07:55.384Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5a/4d2b1601df3602dba7a14f3348ba9bfe94a18adb428e693df6154c293831/numpy-2.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:5a6db61f9aaa57e369905c67d852045d3c4f7126405b29d09b19dec118e9c9cb", size = 10697674, upload-time = "2026-07-04T17:07:58.506Z" }, ] [[package]] @@ -2191,10 +2201,10 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "python-dateutil", marker = "python_full_version < '3.11'" }, - { name = "pytz", marker = "python_full_version < '3.11'" }, - { name = "tzdata", marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, ] sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } wheels = [ @@ -2249,7 +2259,7 @@ wheels = [ [[package]] name = "pandas" -version = "3.0.3" +version = "3.0.5" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", @@ -2266,60 +2276,54 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, - { name = "tzdata", marker = "(python_full_version >= '3.11' and sys_platform == 'emscripten') or (python_full_version >= '3.11' and sys_platform == 'win32')" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f8/87/4341c6252d1c47b08768c3d25ac487362bf403f0313ddae4a2a26c9b1b4c/pandas-3.0.3.tar.gz", hash = "sha256:696a4a00a2a2a35d4e5deb3fc946641b96c944f02230e4f76137fe35d806c4fc", size = 4651414, upload-time = "2026-05-11T18:54:29.21Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/16/b5c76b838fd9bf6ce84d3a53346b8874ec05c5f0040d75ef2c320100cd2a/pandas-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:455f6f8139d4282188f526868dbc3c828470e88a3d9d59a891bd46a455f21b98", size = 10338495, upload-time = "2026-05-11T18:52:11.558Z" }, - { url = "https://files.pythonhosted.org/packages/5a/b0/a4ffc4ae74d2d822200dcc46898987d8eb6032d1e2b219cae39da6f5cbcc/pandas-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4e15135e2ee5df1063313e2425ceef8ac0f4ae775893815b0923651b806a5639", size = 9938250, upload-time = "2026-05-11T18:52:17.005Z" }, - { url = "https://files.pythonhosted.org/packages/2e/b2/3323601a52caee42c019e370090ca4544b241437240ca04f786cce82b0cf/pandas-3.0.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05f1f1752b8533ea03f7f39a9c15b1a058d067bb48f4748948e7a8691e0510f2", size = 10770558, upload-time = "2026-05-11T18:52:19.865Z" }, - { url = "https://files.pythonhosted.org/packages/32/f1/bbecd2f867b97abebe0f9b53d750f862251b40337e061b36676ded3d920f/pandas-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a1e45c80cceb3b4a21bc5939d52e8cbd8d9b7305309219d59e9754d9ce09e27", size = 11274611, upload-time = "2026-05-11T18:52:22.622Z" }, - { url = "https://files.pythonhosted.org/packages/7f/4f/eafabf2d5fae5adf143b4d18d3706c5efdc368a7c4eb1ee8a3eddabbd0f6/pandas-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:14da8316da4d0c5a77618425996bfb1248ca87fc2c1486e6fde4652bd18b5824", size = 11784670, upload-time = "2026-05-11T18:52:25.4Z" }, - { url = "https://files.pythonhosted.org/packages/49/44/1eb20389301b57b19cc099a1c2f662501f72f08a65f912d05822613c1532/pandas-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a55066a0505dae0ba2b50a46637db34b46f9094c65c5d4800794ef6335010938", size = 12353708, upload-time = "2026-05-11T18:52:28.139Z" }, - { url = "https://files.pythonhosted.org/packages/eb/62/c321f13b5ba1819fc8dca456c7fce578da2dcfecff1abbf0eaddf8406c0f/pandas-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6674ab18ad8c57802867264b00e15e7bb904700cdd9046e3b2fa1fce237439ea", size = 9907609, upload-time = "2026-05-11T18:52:30.982Z" }, - { url = "https://files.pythonhosted.org/packages/53/85/1b7f563ebc6357c27233a02a96b589bcce1fa9c6eb89fb4f0e56421d277e/pandas-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:5cc09a68b3120e0f54870dede8287a7bb1fa463907e4fcec1ea77cab6179bf7a", size = 9165596, upload-time = "2026-05-11T18:52:33.334Z" }, - { url = "https://files.pythonhosted.org/packages/24/f1/392f8c5bfc16f66a0d2d41561c01627c228fe7ed2a0d056ef11315042570/pandas-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fed2ff7fd9779120e388e285fc029bd5cf9490cdd2e4166a9ee22c0e49a9ab09", size = 10357846, upload-time = "2026-05-11T18:52:36.143Z" }, - { url = "https://files.pythonhosted.org/packages/cf/3d/b16412745651e855f357e5e66930248688378853a6e2698a214e331fba1f/pandas-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b168fc218fd80a6cbdbdbc1a97ddc7889ed057d7eb45f50d866ceab5f39904c4", size = 9899550, upload-time = "2026-05-11T18:52:38.976Z" }, - { url = "https://files.pythonhosted.org/packages/31/a8/fa2535168fffcedf67f4f6de28d2dd903a747ca7c8ea6989451aaeb3a92f/pandas-3.0.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0383c72c75cdcca61a9e116e611143902dbfd08bff356829c2f6d1cf40a9ca8c", size = 10412965, upload-time = "2026-05-11T18:52:41.915Z" }, - { url = "https://files.pythonhosted.org/packages/65/b6/09b01cdbc15224e2850365192d17b7bdebb8bdbd8780ed221fcdf0d9a515/pandas-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6dc0b3fd2169c9157deed50b4d519553a3655c8c6a96027136d654592be973a9", size = 10894600, upload-time = "2026-05-11T18:52:45.02Z" }, - { url = "https://files.pythonhosted.org/packages/c9/a4/2eb28f2fccb4ced4a2c79ab2a5dee9ade1ebf44922ebad6fea158c9f95d4/pandas-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7e65d5407dc0b394f509699650e4a2ec01c0514f21850f453fa60f3be79a5dbf", size = 11422824, upload-time = "2026-05-11T18:52:48.058Z" }, - { url = "https://files.pythonhosted.org/packages/f8/45/830bb57f533a4604b355e07edcb8ea18cf88b5f94e5fca92f27052d7c597/pandas-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8894dc474d648fe7b6ff0ca9b0bd73950d19952bc1a6534540762c5d79d305c", size = 11950889, upload-time = "2026-05-11T18:52:50.905Z" }, - { url = "https://files.pythonhosted.org/packages/b9/c5/fc1b368f303087d20e8c9bf3d6ceb186263cfac0ade735cd938538bea839/pandas-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:c7be265b62cef88e253a941e4698604973736dcfe242fdb5198f0f7bc473cdcc", size = 9755463, upload-time = "2026-05-11T18:52:53.386Z" }, - { url = "https://files.pythonhosted.org/packages/86/bd/fda8f9705b1b09c6ebe14bfc0fa0e4ec8584d54ea673628f157ff55131af/pandas-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:557409bc4178e70ee8d9ddb494798e51ebf6ea59330f6be22c51bab2a7db6c49", size = 9066158, upload-time = "2026-05-11T18:52:56.038Z" }, - { url = "https://files.pythonhosted.org/packages/c5/90/62d8302883c44308c477e222c3daf7c813a34c8e96985882fbd53d964352/pandas-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:67b3b64c11910cfa29f4e94a14d3bff9ee693b6fc76055e7cad549cee0aec5fa", size = 10331071, upload-time = "2026-05-11T18:52:58.838Z" }, - { url = "https://files.pythonhosted.org/packages/7f/ae/6a6493c783a101f165e4356953ba3c74d6f77f0042fa7d753da9dfbb640c/pandas-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:39436b377d56d2a2e52d0395bdbee171f01068e99af5250509aceeb929f765c7", size = 9875690, upload-time = "2026-05-11T18:53:01.431Z" }, - { url = "https://files.pythonhosted.org/packages/62/7c/5df8e9f56c69a2769fbe9382a5ef8f2658c007e376434e1e2cbb57ad895f/pandas-3.0.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4be06d68f9ddcfc645b87534911da79a8fbffc7573c80e0edcf42a5020624d8", size = 10381634, upload-time = "2026-05-11T18:53:04.393Z" }, - { url = "https://files.pythonhosted.org/packages/99/68/1237369725aa617bb358263d535803e3053fdbc593513ec5ed9c9896b5b6/pandas-3.0.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a4eeb6830daf35a71cc09649bd823e2b542dac246cdee9614c6e4bd65028cd6a", size = 10891243, upload-time = "2026-05-11T18:53:07.643Z" }, - { url = "https://files.pythonhosted.org/packages/25/93/77d108e8af7222b4a503ebde0e30215b1c2e4f8e53a526431890f22d5586/pandas-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1928e07221f82db493cd4af1e23c1bfca524a19a4699887975bff68f49a72bfb", size = 11388659, upload-time = "2026-05-11T18:53:10.634Z" }, - { url = "https://files.pythonhosted.org/packages/d0/bd/eff5b4399f332ac386c853f6cd2bd3fa2ca0061b9f36ecd9c4d7c4265649/pandas-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51b1fe551acb77dac643c6fda86084d8d446c10fe64b06a9cc29c4cc8540e7f2", size = 11942880, upload-time = "2026-05-11T18:53:13.536Z" }, - { url = "https://files.pythonhosted.org/packages/2c/20/559ace4200982c3887d0b86bfd0d856a2143ef8ddab63cc07934951a964c/pandas-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:a82d532a3351d435432cd913edbccaf8b8e01d4dd0e5ced5a8d2e8ecd94c7e44", size = 9757091, upload-time = "2026-05-11T18:53:16.306Z" }, - { url = "https://files.pythonhosted.org/packages/3a/66/69055a09fe200f29f922a3eeec4804611900b95f52d932ece3393c3c0c19/pandas-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:275c14e0fce14a2ec20eee474aecd305478ea3c1e6f6a9d8fe219a165542717e", size = 9057282, upload-time = "2026-05-11T18:53:18.768Z" }, - { url = "https://files.pythonhosted.org/packages/57/0e/efe801b0e6811e8e650cd21b7f2608e30f08a7067e2bf6e8752b0d56ee3c/pandas-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:46997386d528eb40376ecd6b033cf4a8a1e5282580f68f43de875b78cba2199d", size = 10767016, upload-time = "2026-05-11T18:53:21.227Z" }, - { url = "https://files.pythonhosted.org/packages/ea/dc/eb55135a1d5f0f0519f28da1f609a206d2cad1f9c35c32d51e38dd7261ae/pandas-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:261e308dfb22448384b7580cf719d2f998fe2966c92893c3e77d14008af1f066", size = 10420210, upload-time = "2026-05-11T18:53:23.982Z" }, - { url = "https://files.pythonhosted.org/packages/c6/3e/b1d5d955ce33ffecb407465a60bc32769d74fcf68224b7ae67ae11d4dea4/pandas-3.0.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd1a5d1def6a46002e964510bdc67c368aa0951df5d1d9f8365336f5a1f490cd", size = 10336126, upload-time = "2026-05-11T18:53:26.731Z" }, - { url = "https://files.pythonhosted.org/packages/f5/76/a01261711ab60a22d71b862f0de20e4c504bf80457270ad8cb42110f6abc/pandas-3.0.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d72828c20c6d6e83e1e22a6a3b47b326b71664112fa9705dcbccfd7a39b62085", size = 10728051, upload-time = "2026-05-11T18:53:29.125Z" }, - { url = "https://files.pythonhosted.org/packages/e9/21/ea191195e587b18cf682e97f433f81b2d0fbe341380e80a3e0d6e4403c8e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d26cbe1fcfc12e8fd900e2454163e466b2d3af84f7c75481df7683ffc073d870", size = 11350796, upload-time = "2026-05-11T18:53:32.056Z" }, - { url = "https://files.pythonhosted.org/packages/64/69/f0eaaf54939f0e8c6768fd06be9af2cef9b36048b96dfb9e1b2c685a807e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e91cec1879ada0624fc3dc9953c5cbd60208e59c0db28f540c5d6d47502422f", size = 11799741, upload-time = "2026-05-11T18:53:34.985Z" }, - { url = "https://files.pythonhosted.org/packages/45/a4/865e0e510cae5fc2194de4db28be638952de942571ba9125934fd9c01d47/pandas-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:08d789b41f87e0905880e293cedf6197ce71fe67cc081358b1e148a491b9bd13", size = 10499958, upload-time = "2026-05-11T18:53:37.857Z" }, - { url = "https://files.pythonhosted.org/packages/86/54/effdcc3c0ff7a08037889200e148ebe94c16c4f653be078c7b3675955df1/pandas-3.0.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3650109c0f22879df8bd6179ab9ee3d7f1d1d4e7e0094a3f0032d9f51e2e64ac", size = 10336065, upload-time = "2026-05-11T18:53:41.099Z" }, - { url = "https://files.pythonhosted.org/packages/68/10/bf2d6738d72748b961a3751ab89522d58c54efc36a8e1a12161216cd45cf/pandas-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bab900348131a7db1f69a7309ef141fd5680f1487094193bcbbb61791573bf8f", size = 9926101, upload-time = "2026-05-11T18:53:43.515Z" }, - { url = "https://files.pythonhosted.org/packages/ae/e9/e35cf11c8a136e757b956f5f0efdcaa50aecde85ea055f1898dfc68262f3/pandas-3.0.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba7e08b9ac1d54569cd1e256e3668975ed624d6826f7b68df0342b012007bddb", size = 10457553, upload-time = "2026-05-11T18:53:46.394Z" }, - { url = "https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d71c63ae4ebdbf70209742096f1fc46a83a0613c99d4b23766cced9ff8cd62a", size = 10914065, upload-time = "2026-05-11T18:53:49.134Z" }, - { url = "https://files.pythonhosted.org/packages/c4/c2/1ef644445fcd72e3627bceec77e3560636f87ddce4ed841afe76b83b5bf9/pandas-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e3a2ec42c98ffa2565a67e08e218d06d72576d758d90facb7c00805194d8f360", size = 11459188, upload-time = "2026-05-11T18:53:52.527Z" }, - { url = "https://files.pythonhosted.org/packages/7e/49/4d8d4f42cbc9c4adc7a1870f269c02cbd6cd40d059622c06fb298addcbad/pandas-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:335f62418ed562cfc3c49e9e196375c28b729dcef8543abf4f9438e381bf3c76", size = 11982966, upload-time = "2026-05-11T18:53:55.043Z" }, - { url = "https://files.pythonhosted.org/packages/38/55/792619469bab9882d8bbd5865d45a72f6478762d04a9af4bf0d08c503e95/pandas-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:3c20a521bbb85902f79f7270c80a59e1b5452d96d170c034f207181870f97ac5", size = 9876755, upload-time = "2026-05-11T18:53:58.067Z" }, - { url = "https://files.pythonhosted.org/packages/2a/af/33c469653b0ba03b50c3a98192d4c07f0c75c66b263ceb097fce0ee97d31/pandas-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:a2d2dff8a04f3917b55ab3910c32990f8ddf7eceba114947838cefa976a68977", size = 9198658, upload-time = "2026-05-11T18:54:00.733Z" }, - { url = "https://files.pythonhosted.org/packages/a2/fa/b8c257bd76b8bd060c3a9151c1fca05e9b9c5e3af5d0f549c0356f6d143d/pandas-3.0.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0d589105b3c14645af1738ff279b2995102d8f7a03b0a66dc8d95550eb513e04", size = 10787242, upload-time = "2026-05-11T18:54:03.564Z" }, - { url = "https://files.pythonhosted.org/packages/54/eb/f19206ffb0bf1919002969aa448b4702c6594845156a6f8050674855aac3/pandas-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:13fc1e853d9e04743d11ba75a985ccbc2a317fe07d8af61e445a6fd24dacd6a6", size = 10436369, upload-time = "2026-05-11T18:54:06.311Z" }, - { url = "https://files.pythonhosted.org/packages/fd/24/c7c39fb4fe22b71a0c2d78bf0c585c600092d85f94f086d2b3b2f6ca27e2/pandas-3.0.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:819959dab7bbd0049c15623fbac4e29a191b9528160a61fb1032242d8ced2d9c", size = 10358306, upload-time = "2026-05-11T18:54:09.085Z" }, - { url = "https://files.pythonhosted.org/packages/16/ec/dd2a9eb7fa1204df88c0864164e35b228ac581062ac612ba0a67fd812e4c/pandas-3.0.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:60ae316d3fd75d1858d450d0db0103ea2be3e7d4a95ec2f064f7e2ae63f7b028", size = 10758394, upload-time = "2026-05-11T18:54:11.956Z" }, - { url = "https://files.pythonhosted.org/packages/95/6e/00c61ea8e85b4f6d8d35e11852a1a4998fc7fafc91c6a602d1cc9c972d64/pandas-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd3a518890b400d32f9023722dc9a9a5c969f00b415419a3c06c043f09bb5d7d", size = 11375717, upload-time = "2026-05-11T18:54:14.539Z" }, - { url = "https://files.pythonhosted.org/packages/31/89/8fc1c268969fac43688d65fd92e67df24bd128d53cb4d2eee534cd307399/pandas-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9c39be2d709d01fa972a0cabc522389fceca4f3969332ba25a7d6c5802cf976a", size = 11828897, upload-time = "2026-05-11T18:54:17.146Z" }, - { url = "https://files.pythonhosted.org/packages/56/3b/e7d20dea247a3e6dc0bd8a6953854afbedc03951def4e7371e05e7263e25/pandas-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4db8c527972a821cf5286b40ccc57642a39bc62e62022b42f99f8a67fca8c3a1", size = 10900855, upload-time = "2026-05-11T18:54:19.72Z" }, - { url = "https://files.pythonhosted.org/packages/0f/54/68a0978d1ef8502b8492099beaa6e7a0c1b32e3b5d4f677f5810cb08711c/pandas-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b2c95f8bfc1ee412bf482605d7bfd30c12d1d26bd59fdd91efeef1d4718decb1", size = 9466464, upload-time = "2026-05-11T18:54:22.754Z" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "python-dateutil" }, + { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/4f/5f3422a2afec5ffc46308b79e53291365a93748b498ac2e58bead0197916/pandas-3.0.5.tar.gz", hash = "sha256:dca3734d6ab7c906e6730f0788b0a1dbb9f2467731f9711f77995c8e9d62d712", size = 4658219, upload-time = "2026-07-22T22:19:28.819Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/ef/f1fd7431d635bf20015489bf0bd69c17fff1018de773540f651455a3916b/pandas-3.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2946e77e4a53cd248cbde631a12f0e51c8324ce354c3eba4d20147c1ad6f4282", size = 10397178, upload-time = "2026-07-22T22:17:48.274Z" }, + { url = "https://files.pythonhosted.org/packages/31/b4/0eafac990a431561187694126de01f9b12559549b4d86360c0c4bd870fde/pandas-3.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71ecc8fb7ed1a7aa4392316b5309a6347e8e7f832f38fd897846b3a1457a9298", size = 9990736, upload-time = "2026-07-22T22:17:52.388Z" }, + { url = "https://files.pythonhosted.org/packages/de/21/359880af3ea9b7cb23bea5b51e8e70ef3866c03be09da9a2787e18e330a8/pandas-3.0.5-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b173f5951ff6b8b0ec7675e20dff3c97b7e7a57dfcce387c2d7c5afe87cb7899", size = 10814438, upload-time = "2026-07-22T22:17:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/d1/50/d6cc4d7e508bbccf5d6027314a8312bc7ac73d0ec7f195f53838daafab40/pandas-3.0.5-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c0cf1dd9b55a22d105fc46c1b489af3bd42264fcba7c66297bf47a9a1d9c78a", size = 11323634, upload-time = "2026-07-22T22:17:56.858Z" }, + { url = "https://files.pythonhosted.org/packages/70/2b/d5f0a8c90dd0ae04e64ba53b871afb796ec026b615086d382ddc2ade729b/pandas-3.0.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0fac0010c75e4efb6b99e249c183a8993ce0dc95c240f9b120a5e67c727b7928", size = 11850860, upload-time = "2026-07-22T22:17:59.1Z" }, + { url = "https://files.pythonhosted.org/packages/5c/30/183aec2e19adf778a98d29b5729a0a68f4cc4ebf9b9c3b70d0297355bcb1/pandas-3.0.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:08d24fe11a17dc33bd6e937dc9c665f9cba08fbdc9f657f405713515febe300d", size = 12411100, upload-time = "2026-07-22T22:18:01.485Z" }, + { url = "https://files.pythonhosted.org/packages/fa/9a/31f4983f191af51ab2a8f2d0c7b33dff3a84da26533f982fff02c2f9e28b/pandas-3.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:b1261758dfb6cf12c3cff8300e21cefad30e7ec709abb4c24ac7318e6a52462a", size = 9968804, upload-time = "2026-07-22T22:18:03.903Z" }, + { url = "https://files.pythonhosted.org/packages/49/97/7886c89a39045c69ad82cbceaf3343810480c8ef49a216319ce8183860a6/pandas-3.0.5-cp311-cp311-win_arm64.whl", hash = "sha256:679f4e85b30ddb1515458ab1e788d3e260eae369b1f78da7a3aa4cac8ebf4a2a", size = 9205447, upload-time = "2026-07-22T22:18:06.134Z" }, + { url = "https://files.pythonhosted.org/packages/1c/54/1dc810ea558d1320b597aa140a514f2fdf1d2ea09c38cf556f13ea712ec9/pandas-3.0.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fa290c16964d4963fbfbc358928239cf3bd755b20e988ce944877def2f44471d", size = 10411717, upload-time = "2026-07-22T22:18:08.307Z" }, + { url = "https://files.pythonhosted.org/packages/68/56/fbe81c09195924d8b7b8d4461a20458fe80a6a5ed6b24f0314da684277e1/pandas-3.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2e26bb46934b8a2ca0c3de1d3d606fc5f6746584791b2db264d58cf370e08dc", size = 9957095, upload-time = "2026-07-22T22:18:10.6Z" }, + { url = "https://files.pythonhosted.org/packages/e0/51/fac252f4a913ed5eabf3c11b880a9e8d5a6c10f0b2129d0462212d238b4d/pandas-3.0.5-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73fa87b08a7ef706f8aafda39ddaccf2a99047bea62d8c88a0361bcafb2237bc", size = 10485458, upload-time = "2026-07-22T22:18:12.834Z" }, + { url = "https://files.pythonhosted.org/packages/12/98/e976540c1addf70442be7842a18cf70884a964abbf69442504f4d2939989/pandas-3.0.5-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d373ce03ffd84010ed9839fa73672a9c8256990532e158440c0085db7d914b34", size = 10998091, upload-time = "2026-07-22T22:18:15.209Z" }, + { url = "https://files.pythonhosted.org/packages/a4/8c/1f29b5be8d3fc47dd7567eb167fabba2085879b31e0287ce7cba6d3d2ff4/pandas-3.0.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2a29c53d85ea98c5e792c59ef82ee9fbe6ca902c0d0adb6b23f45ef894cd7bf6", size = 11499501, upload-time = "2026-07-22T22:18:17.689Z" }, + { url = "https://files.pythonhosted.org/packages/9d/e2/bd9c98ad2df7b38bde002adde4cdf353519da51881634323b126c55997f9/pandas-3.0.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a5ad3b02ed6bc7d7ae9b70804b2c6aa31827489d150f8e623ce82491b82085d7", size = 12060559, upload-time = "2026-07-22T22:18:20.147Z" }, + { url = "https://files.pythonhosted.org/packages/f3/9a/ffbd852d58bd74a617fe2f8ee6a58a96982271ce41cf981eab22190b4a4b/pandas-3.0.5-cp312-cp312-pyemscripten_2024_0_wasm32.whl", hash = "sha256:b2acb4650527eec6822c3dadb2b771277b65e7dae7a267d4bccf65fd1bb3fbce", size = 7197652, upload-time = "2026-07-22T22:18:22.502Z" }, + { url = "https://files.pythonhosted.org/packages/70/b5/d2d3e9ae73362ba4229651b0ee1455cf78073a1ce585f6ff693782ce263e/pandas-3.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:80a611068e8a3ac23f7398c6c14eb46dc974e5cc9997f653e2dcfd1da74edd41", size = 9831691, upload-time = "2026-07-22T22:18:24.534Z" }, + { url = "https://files.pythonhosted.org/packages/52/51/dea1e89d6a6796b9c43f85a09b484ee03edb8a4c4842e73e200a8c11301c/pandas-3.0.5-cp312-cp312-win_arm64.whl", hash = "sha256:25ff585b972a18ef1fe9ffa3ac6544d9950508aa76832e5147640b6022821e49", size = 9105796, upload-time = "2026-07-22T22:18:27.064Z" }, + { url = "https://files.pythonhosted.org/packages/bf/09/7b95c4a0025227d6f118c4039b423412ac6a982db02864166185d812fbc7/pandas-3.0.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c1c05a767fe8e5b4fe9e1c29806829c582052eaedb9120a3da83ba3f69e24a5b", size = 10385742, upload-time = "2026-07-22T22:18:29.346Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0c/dc78fd8c4da477b4b5e8ad37295af352190d21ef63a9ee1bc071753074cc/pandas-3.0.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b86765f268b56f7e665b93bce9d5df69dee7f99e595cf8fb839483ab315942a3", size = 9932067, upload-time = "2026-07-22T22:18:31.833Z" }, + { url = "https://files.pythonhosted.org/packages/3e/71/3592c055cf44df9808550f9368ceda80ff2b224d355ef73fe251dcda1802/pandas-3.0.5-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c597ecf5616b5c420372c1d4d4c00dbbfba7398bea857dcc984347e1ea48417b", size = 10466756, upload-time = "2026-07-22T22:18:34.195Z" }, + { url = "https://files.pythonhosted.org/packages/e3/70/4363150359f95b4cb4bcbb34ca23572bb5495749a621a8f3d5a1ddfd293c/pandas-3.0.5-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b11c36e218331d0387cbe3a0a5f75162357a1d92d57b2b08a336ff94b19b2be", size = 10938525, upload-time = "2026-07-22T22:18:36.81Z" }, + { url = "https://files.pythonhosted.org/packages/f7/d0/317e7a0c67c0e69fa905a0161409397a7dc2d46ff611f6ca4803352c042b/pandas-3.0.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cf52e1f61d229496da17dc7ab54acdee627357e7008fd4fecba3d0ba2937fa58", size = 11489303, upload-time = "2026-07-22T22:18:39.287Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8d/36dade89b49e4f9d5cbdbe863772581f98c0c6d78fc39ad4c557f6f2e17e/pandas-3.0.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:db172144bb56422bd157812f3b021eacc255451470b31e2c633c349490a1cfee", size = 11989004, upload-time = "2026-07-22T22:18:42.208Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ba/18c4ec8a746e177da05a9e7a7963781d8ea195780724f854601b6ebd6b78/pandas-3.0.5-cp313-cp313-win_amd64.whl", hash = "sha256:0d298e951f23016ce4699951d044ae6418dbc91bf68cefca0f77666fcbb4e5c6", size = 9826896, upload-time = "2026-07-22T22:18:44.539Z" }, + { url = "https://files.pythonhosted.org/packages/de/ec/28a57266b753799a87b8bc79e7887ac6fd981b8c6d2978a0b7e7b6bd708c/pandas-3.0.5-cp313-cp313-win_arm64.whl", hash = "sha256:66266d3442a5e8b3c90274c2b8b230bee42dd1c286bc822cc2f9f2c7e12b883e", size = 9094790, upload-time = "2026-07-22T22:18:47.468Z" }, + { url = "https://files.pythonhosted.org/packages/51/2f/cf6aae281264f4463f0875bcbb15fd2bb6d291cc535187dad1732475e4a9/pandas-3.0.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2f264fc46911cc8131a7322a16199bbf8e353d27c10bb211f5bd0c814324dc36", size = 10390034, upload-time = "2026-07-22T22:18:49.818Z" }, + { url = "https://files.pythonhosted.org/packages/06/ec/5189518c7a7659c4bdcc6b1eb32c46c6f3c86b0661ffd84143d1112c7732/pandas-3.0.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:53730687fcd161883b24e10411c06d6a4c0f2275d2faf3bb2bc25deb4ba8007c", size = 9980065, upload-time = "2026-07-22T22:18:52.249Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f1/598503ce8d7e3c35601e0747ba288c7864baae66380725bc12f13f884dfe/pandas-3.0.5-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:960d3ebcf249f75206899fcd2c6de53f736b7265759ced0d3e559df0b8b709b0", size = 10545532, upload-time = "2026-07-22T22:18:54.813Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/ceae2adf7034e07e9910299fe412e1819c4f0dd520700a888bcb03625448/pandas-3.0.5-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e94c2c5ca43bd3ca32bf64d32308887b65e5f9bfd8023ea52755107a999f93b", size = 10963120, upload-time = "2026-07-22T22:18:57.42Z" }, + { url = "https://files.pythonhosted.org/packages/66/25/86e0f4451874eb79e688deeebe3c451fec4557f8952005818d800ee8ac7e/pandas-3.0.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e819dd5f62966b481a8cb649d3299ebd886a1ea91ed5a99bf7ce77c98d18ab94", size = 11563178, upload-time = "2026-07-22T22:18:59.729Z" }, + { url = "https://files.pythonhosted.org/packages/f3/45/8643daa3b4147e433adfcccefdd0380d3aad79d86b15d8999730fe1944d5/pandas-3.0.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3c5ed2e7c06e91d340dfd091d7934f9bc82e4a36b95f647f090b9d1c9ac649da", size = 12028708, upload-time = "2026-07-22T22:19:02.164Z" }, + { url = "https://files.pythonhosted.org/packages/96/58/ad979ae617615576e8aafd569c9d4b62f1191d896e38f51d66ba06f3b89a/pandas-3.0.5-cp314-cp314-win_amd64.whl", hash = "sha256:cd8f7c6dc98527058ee6264219343f5392240a6f1bfa654fc5d79023020d0c92", size = 9951806, upload-time = "2026-07-22T22:19:04.596Z" }, + { url = "https://files.pythonhosted.org/packages/69/32/7ac03886b304049a9d2625ee88f59af760d8a93bd30ed9239bce7b9869a8/pandas-3.0.5-cp314-cp314-win_arm64.whl", hash = "sha256:5183427f5a8156d480f30333777bc978be93650a49a7c01db26adffe95b31e85", size = 9238297, upload-time = "2026-07-22T22:19:06.836Z" }, + { url = "https://files.pythonhosted.org/packages/be/ed/1d1f2ee5547d5167face2376d11c8b2a4c7bfff5a416ee7a9046891fab1e/pandas-3.0.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:303da736987d481074ca720ada325f8bd80c64ebc2d45ed79b29df3aaa4a26ca", size = 10849690, upload-time = "2026-07-22T22:19:09.391Z" }, + { url = "https://files.pythonhosted.org/packages/57/55/17e17152e98fbb0c4b1e562bc65387a2f20a80db0f4a86bf8d3a0e4248d4/pandas-3.0.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3b2801bbb049d0136f6c213eae02b5fca969384fc2064dd728d8620552aa49da", size = 10509945, upload-time = "2026-07-22T22:19:11.773Z" }, + { url = "https://files.pythonhosted.org/packages/88/90/817d44dbf83facf9556f33576d9af0a241981e7bb5c00606c0bcb5df8dda/pandas-3.0.5-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cce3a9d11d2b1f82c69a27ec1f4948a170e2c403c4bbfa8cca62e3fdebe2ef3a", size = 10392197, upload-time = "2026-07-22T22:19:14.024Z" }, + { url = "https://files.pythonhosted.org/packages/f1/da/889f00c0a6f5aa1545add70abbf01502dff87ab577adb855bd631c54d2f2/pandas-3.0.5-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef01af4d8dc6cd2c8d6c7736f149574ef93fe043811eeb5e445f2647154b5040", size = 10862726, upload-time = "2026-07-22T22:19:16.351Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/f1e934fb3c98fce859c6147c6785816c7b5b9ab7821115c5d8c4de9842b9/pandas-3.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e2759e890db96dfcffdbd9b86c3c2cb6afaf58def482820317e06163ec1066cd", size = 11414864, upload-time = "2026-07-22T22:19:18.981Z" }, + { url = "https://files.pythonhosted.org/packages/fe/be/d448af7d657d82e1888dd8551f79c6d6fb161080b5b9752d84d910ec2319/pandas-3.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b58b1b39d46a5862e3fb18f50d1a201398619d16a0f9f73f57eea5583cf0e63c", size = 11925105, upload-time = "2026-07-22T22:19:21.515Z" }, + { url = "https://files.pythonhosted.org/packages/29/c1/ccb4238212c8c4f496c584f3044d94e0c030ed8e1d68999db46c91c2242f/pandas-3.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:1c10461f6eeb35d8f05b6184c65c8b9991663b66c46b1d559b682cb34ae7c6ea", size = 10387612, upload-time = "2026-07-22T22:19:24.257Z" }, + { url = "https://files.pythonhosted.org/packages/d2/cf/6a51b2c38980e04c279fd2fa908a1b0982064e860444acfca4ec2e2c8359/pandas-3.0.5-cp314-cp314t-win_arm64.whl", hash = "sha256:3c5015fd1730fbf883647e88068176c839c102cea883ba1769a6f4593bfc1f8c", size = 9509776, upload-time = "2026-07-22T22:19:26.694Z" }, ] [[package]] @@ -2448,11 +2452,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.10.0" +version = "4.11.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224, upload-time = "2026-05-28T03:32:53.587Z" } +sdist = { url = "https://files.pythonhosted.org/packages/78/9b/560e4be8e26f6fd133a03630a8df0c663b9e8d61b4ade152b72005aec83b/platformdirs-4.11.0.tar.gz", hash = "sha256:0555d18370482847566ffabcaa53ad7c6c1c29f195989ae1ed634a05f76ea1e0", size = 31953, upload-time = "2026-07-21T13:09:36.565Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" }, + { url = "https://files.pythonhosted.org/packages/7d/68/d8d58938dfb1370b266a1a729e6d77a985be23689a0496498ee17b2cbf90/platformdirs-4.11.0-py3-none-any.whl", hash = "sha256:360ccded2b7fce0af0ff80cc8f5942a1c5d99b0e856033acb030bfc634709e74", size = 23247, upload-time = "2026-07-21T13:09:35.422Z" }, ] [[package]] @@ -2466,14 +2470,14 @@ wheels = [ [[package]] name = "prompt-toolkit" -version = "3.0.52" +version = "3.0.53" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wcwidth" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/ea/39b988c938f75cb75d7045b5c69f8bfed47ee2152c8837fb403de29d6fb8/prompt_toolkit-3.0.53.tar.gz", hash = "sha256:9ec8a0ad96d5c56148b3f914aa79c1564c3fde5d2e6b876e7bc327e353cf8fa6", size = 435492, upload-time = "2026-07-26T20:56:14.758Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, + { url = "https://files.pythonhosted.org/packages/54/6f/84908cad2d6aa5144abcf7b42709fe4fdb459bc640ec7ac5786e7693dabc/prompt_toolkit-3.0.53-py3-none-any.whl", hash = "sha256:01c0891d7f9237d5e339f7d3e42cdae80b7534abb1c7c0e3352efba6231492f2", size = 392288, upload-time = "2026-07-26T20:56:12.512Z" }, ] [[package]] @@ -2581,9 +2585,12 @@ wheels = [ [[package]] name = "pylatexenc" -version = "2.10" +version = "2.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5d/ab/34ec41718af73c00119d0351b7a2531d2ebddb51833a36448fc7b862be60/pylatexenc-2.10.tar.gz", hash = "sha256:3dd8fd84eb46dc30bee1e23eaab8d8fb5a7f507347b23e5f38ad9675c84f40d3", size = 162597, upload-time = "2021-04-06T07:56:07.854Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/45/ddb0fb04acf95fe9cf9c369814dbdd08651bd2c9ee455f142651e06f4488/pylatexenc-2.11.tar.gz", hash = "sha256:305a072a99ce736246049c9da05841b9d718c0f7ea8888f5f596cf15cb621053", size = 165743, upload-time = "2026-07-25T17:26:31.534Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/06/3d67bd912ef337aa4856b466121d03f304aa1bb4d804f9298b6227cd227e/pylatexenc-2.11-py2.py3-none-any.whl", hash = "sha256:e78e7391d6c104f1ed150e21cfaa58016cdb50aa54406a2eecb793649ffdfdd0", size = 137533, upload-time = "2026-07-25T17:26:30.141Z" }, +] [[package]] name = "pyparsing" @@ -2678,24 +2685,24 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.4.2" +version = "1.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0b/1a/cbbaf13b730abb0a16b964d984e19f2fe520c21a4dc664051359a3f5a9e7/python_discovery-1.4.2.tar.gz", hash = "sha256:8f3746c4b4968d22afbb97d36e1a0e5b66e6c0f297290f2e95f05b9b8bf18690", size = 70277, upload-time = "2026-06-11T16:10:42.383Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/51/276f964496a5714ab9f320896195639086881c2b39c03b5ad13de84acbb8/python_discovery-1.5.0.tar.gz", hash = "sha256:3e014c6327154d3dda27939a9a0dc9c5c000439f1906d3f303b48f984bd2ecef", size = 72483, upload-time = "2026-07-21T13:14:14.641Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/82/a70006589557f267f15bd384c0642ad49f0d97b690c3a05b166b9dcbad3b/python_discovery-1.4.2-py3-none-any.whl", hash = "sha256:475803f53b7b2ed6e490e27373f9d8340f7d2eebf9acdaf645d7d714c97bb500", size = 33886, upload-time = "2026-06-11T16:10:41.192Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7b/14882602ddee241d7984a742fcb423cb4a30fb0d6efc546ac3129fba475a/python_discovery-1.5.0-py3-none-any.whl", hash = "sha256:70c4fc61b4e7404e44f01d6fc44a715c4d685ca6cea83d295922f05891877c98", size = 34205, upload-time = "2026-07-21T13:14:13.398Z" }, ] [[package]] name = "pytz" -version = "2026.2" +version = "2026.3.post1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ff/46/dd499ec9038423421951e4fad73051febaa13d2df82b4064f87af8b8c0c3/pytz-2026.2.tar.gz", hash = "sha256:0e60b47b29f21574376f218fe21abc009894a2321ea16c6754f3cad6eb7cdd6a", size = 320861, upload-time = "2026-05-04T01:35:29.667Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/48/fb042503b6ca6cd271261dc559fd6432f7d8c713153e9ec5c591af4dfc1c/pytz-2026.3.post1.tar.gz", hash = "sha256:2211d3fcf9a797d3405cac96ac7f61d80e6a644f72a3309607282fe8a2010c5d", size = 319745, upload-time = "2026-07-25T15:12:07.385Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/dd/96da98f892250475bdf2328112d7468abdd4acc7b902b6af23f4ed958ea0/pytz-2026.2-py2.py3-none-any.whl", hash = "sha256:04156e608bee23d3792fd45c94ae47fae1036688e75032eea2e3bf0323d1f126", size = 510141, upload-time = "2026-05-04T01:35:27.408Z" }, + { url = "https://files.pythonhosted.org/packages/0f/7b/39c34ca613b0b198cb866466651b26b045e2009864c5183c979a3b83f383/pytz-2026.3.post1-py2.py3-none-any.whl", hash = "sha256:dd95840dd199baea12d9cc096a1d452caa6596a1c1e4b5f3dbd1541855d5e815", size = 508283, upload-time = "2026-07-25T15:12:05.782Z" }, ] [[package]] @@ -2837,29 +2844,30 @@ wheels = [ [[package]] name = "qiskit" -version = "2.4.2" +version = "2.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "dill" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "rustworkx" }, { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "stevedore" }, + { name = "stevedore", version = "5.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "stevedore", version = "5.9.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/86/32/36d51b8f79b86e82fa41d402b4175a204140bbc1c75bdadca980fdaf70e9/qiskit-2.4.2.tar.gz", hash = "sha256:b9844e28336fdbed0d53f8cf82a681d0365574dd68d04ef877d1619971462ad6", size = 4089826, upload-time = "2026-06-12T18:59:55.118Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/b8/9900e3c8e2c1f7d97e15e9a7b5fcf7274211887ace8947cd0e0075583da5/qiskit-2.5.1.tar.gz", hash = "sha256:923e11f02c6720da4f9435cbfc78b6d2c4a106198175a16d32ae730eaa007fa6", size = 4253196, upload-time = "2026-07-23T18:39:23.67Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/1c/8a2818a21f9375a30695c30648801465dc9cb01fc6cf1d92ac69fe85b14c/qiskit-2.4.2-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:9ff8ed0f047af28a7e6946c5fc5801b09f700c1720232c22343ec0c8a17f6397", size = 9345349, upload-time = "2026-06-14T10:02:34.855Z" }, - { url = "https://files.pythonhosted.org/packages/34/e1/07f65537832c3ac96ab491c2b556f20f301ca43ef6d060e75afe0b6727ba/qiskit-2.4.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:0bdc0e4d302f25208409fb618c5fd963e775924f0a3d8886be42b9bc34c87a71", size = 8328417, upload-time = "2026-06-12T18:59:45.911Z" }, - { url = "https://files.pythonhosted.org/packages/7c/78/d7a12575cc89e43431712b6e7920b7e45f5ba5e66897ef67b109fe5e09b0/qiskit-2.4.2-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:85f8a1e0cb72e2fd5e8c615634a91a3a83f64d98ea8c1c6ec7eeb73367379f96", size = 8620233, upload-time = "2026-06-12T18:59:48.057Z" }, - { url = "https://files.pythonhosted.org/packages/95/0c/ea8cbaf4ee1de9f7b060c7343eb077a12a7beb868919d1060c3fb612a8f8/qiskit-2.4.2-cp310-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:76c4d7d9abf91e7689e834800a8f6ea2eb0b2cb5d2aa5f5822faaf78d0334584", size = 9675835, upload-time = "2026-06-14T10:02:37.54Z" }, - { url = "https://files.pythonhosted.org/packages/43/3b/0b48969a222a6add7b1a34fa5525dab14de18cd644d905bc4f26283880ff/qiskit-2.4.2-cp310-abi3-manylinux_2_28_s390x.whl", hash = "sha256:b3b9f9a3c02b4ffcd1d34ddd1479d0089626b7469c82f977f48fac1846c2855f", size = 9175120, upload-time = "2026-06-14T10:02:39.767Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e9/c934221566f4cd0588d3c8440fba460cea99f34485a4b1cf086446dc6def/qiskit-2.4.2-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:11a569903b300a8c3478a3abbda3644e7cec133210f3e106aa48dfc5b5340136", size = 9327641, upload-time = "2026-06-12T18:59:50.162Z" }, - { url = "https://files.pythonhosted.org/packages/f9/44/df0346b7d54d6213fbb5b15dd593ff09f5a36b9fc5db57ced5926632a9cc/qiskit-2.4.2-cp310-abi3-win_amd64.whl", hash = "sha256:0810c78b1d635f859f743daaeef3b98e5b36555da82d076e56ff677da108fba7", size = 9105646, upload-time = "2026-06-12T18:59:52.486Z" }, + { url = "https://files.pythonhosted.org/packages/a9/01/70fc67393981089786e7f3ccdb1450bed9f76a6e4d07d0d50aab19cada41/qiskit-2.5.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:090e4c2f6f97a5e9d2fbeb910ed1cb33476fd9ded16b0fe42bb4f05726c61b0d", size = 9335588, upload-time = "2026-07-23T19:21:37.295Z" }, + { url = "https://files.pythonhosted.org/packages/94/ff/24ad78571f4368e3575860b185709f41eae8f171aa1a68dd7dc33aea1560/qiskit-2.5.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:b70e7b1b1b1e988ac758a102eb2c06dd643afb7977ef7b005f2eba1190a71747", size = 8736917, upload-time = "2026-07-23T18:39:12.035Z" }, + { url = "https://files.pythonhosted.org/packages/9c/61/ecd80b064476e2b7a0c73b640ec56dfed255bc18b97c123578b5698c03aa/qiskit-2.5.1-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:083cf6743d88bfbe711fae150c8a84b9f621e82a1983202c1be10dbada5f24c0", size = 9022618, upload-time = "2026-07-23T18:39:15.015Z" }, + { url = "https://files.pythonhosted.org/packages/0a/30/b238d7ce67dd23d8483ec8a11062500a27c2bf0991af6212b9cfee662801/qiskit-2.5.1-cp310-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:162aa151a5892913bbdc9910a0ebcb291f268331d26020ab2f0e0dbc674c6b55", size = 9940998, upload-time = "2026-07-23T19:21:39.911Z" }, + { url = "https://files.pythonhosted.org/packages/48/e0/50a5e3518c1da8fbd4dd2f4a915e919613b389a44d97b1bc71bde5835432/qiskit-2.5.1-cp310-abi3-manylinux_2_28_s390x.whl", hash = "sha256:7a10203bf4e058e104188f3e0ed333c8974e3c2298bfcc2d3f87da2c5530e034", size = 9446636, upload-time = "2026-07-23T19:21:42.009Z" }, + { url = "https://files.pythonhosted.org/packages/a7/d7/bca6581752d00bc9615e54ad246baff0946c62db232b5eb4b93a170e8327/qiskit-2.5.1-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c1a0cee82eb8e008fad6c79d568fbb52f05db4952f6d61c0542dc50f9c06c53c", size = 9711786, upload-time = "2026-07-23T18:39:17.94Z" }, + { url = "https://files.pythonhosted.org/packages/98/17/88644857596749eeca472634a8b1e646a743541ea7d1160ba55d5fdb66d7/qiskit-2.5.1-cp310-abi3-win_amd64.whl", hash = "sha256:08b861f35cf0c1731853e58ed1b2b7741f9f4a8522cd39d1da3b64b9cb5e3a06", size = 9440092, upload-time = "2026-07-23T18:39:21.05Z" }, ] [package.optional-dependencies] @@ -2868,7 +2876,7 @@ qasm3-import = [ ] visualization = [ { name = "matplotlib", version = "3.10.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "matplotlib", version = "3.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "matplotlib", version = "3.11.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "pillow" }, { name = "pydot" }, { name = "pylatexenc" }, @@ -3197,7 +3205,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ff/1a/545aa3a3e251e9da00e4acf0e5472b7fbbb15360f56d9d5342c1f93b8b93/rustworkx-0.18.0.tar.gz", hash = "sha256:5ca9cf8dbee50f8def012119ebb64771ae86916993417415aba9e845831cb436", size = 894567, upload-time = "2026-06-18T04:38:56.141Z" } wheels = [ @@ -3221,7 +3229,7 @@ wheels = [ [[package]] name = "scikit-build-core" -version = "0.12.2" +version = "1.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, @@ -3230,9 +3238,9 @@ dependencies = [ { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/28/cd/9ebb50029b6d8a3ee9e38cdce514ebd70190ec1edf28ab0a1f66d0b84670/scikit_build_core-0.12.2.tar.gz", hash = "sha256:562e0bbc9de1a354c87825ccf732080268d6582a0200f648e8c4a2dcb1e3736d", size = 303553, upload-time = "2026-03-05T18:25:57.666Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/7c/0f69b0c7150ce4bcee78c199fe3b7e03a6e01578451bd5d5d1a58beb32f9/scikit_build_core-1.0.3.tar.gz", hash = "sha256:a4d7a05978ee37975c37743510c8991e2debce7ef83afb0a07c0c576fd4f16e8", size = 477539, upload-time = "2026-07-12T05:08:30.298Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/49/b2f0fbe3165d55c02e7f9eec6a10685d518af0ef6e919ff2f589c2d15c85/scikit_build_core-0.12.2-py3-none-any.whl", hash = "sha256:6ea4730da400f9a998ec3287bd3ebc1d751fe45ad0a93451bead8618adbc02b1", size = 192625, upload-time = "2026-03-05T18:25:56.207Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b0/7ad8fa9aebb0835b2e20afebf1483cbe175d1612fe01e85042ce9ced8755/scikit_build_core-1.0.3-py3-none-any.whl", hash = "sha256:ae95427b7d3c14a6cf8bbfd4d901f6138ab64c99e20cbe8ea7d75cd26093f085", size = 278294, upload-time = "2026-07-12T05:08:28.725Z" }, ] [[package]] @@ -3243,7 +3251,7 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } wheels = [ @@ -3304,7 +3312,7 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } wheels = [ @@ -3386,7 +3394,7 @@ resolution-markers = [ "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/a7/25/c2700dfaf6442b4effaa91af24ebce5dc9d31bb4a69706313aae70d72cd0/scipy-1.18.0.tar.gz", hash = "sha256:67b2ad2ad54c72ca6d04975a9b2df8c3638c34ddd5b28738e94fc2b57929d378", size = 30774447, upload-time = "2026-06-19T15:01:43.456Z" } wheels = [ @@ -3438,12 +3446,12 @@ version = "0.13.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "matplotlib", version = "3.10.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "matplotlib", version = "3.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "matplotlib", version = "3.11.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "pandas", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pandas", version = "3.0.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/86/59/a451d7420a77ab0b98f7affa3a1d78a313d2f7281a57afb1a34bae8ab412/seaborn-0.13.2.tar.gz", hash = "sha256:93e60a40988f4d65e9f4885df477e2fdaff6b73a9ded434c1ab356dd57eefff7", size = 1457696, upload-time = "2024-01-25T13:21:52.551Z" } wheels = [ @@ -3452,16 +3460,16 @@ wheels = [ [[package]] name = "setuptools" -version = "82.0.1" +version = "83.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/26/f5d29e25ffdb535afef2d35cdb55b325298f96debd670da4c325e08d70f4/setuptools-83.0.0.tar.gz", hash = "sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef", size = 1154254, upload-time = "2026-07-04T15:31:22.699Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, + { url = "https://files.pythonhosted.org/packages/5d/40/e1e72872c6354b306daef1703549e8e83b4d43cfea356311bf722a043752/setuptools-83.0.0-py3-none-any.whl", hash = "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3", size = 1008090, upload-time = "2026-07-04T15:31:20.885Z" }, ] [[package]] name = "setuptools-scm" -version = "10.2.0" +version = "10.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, @@ -3470,9 +3478,9 @@ dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.11'" }, { name = "vcs-versioning" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/61/fb/6b22ba201305ec33fa68c1419e984abe1aaee1b236a5e1186cc805738e95/setuptools_scm-10.2.0.tar.gz", hash = "sha256:ec8ea1738b92e42146a46e29a0e9de9ad462744c63cf9778677b95dfd605adde", size = 67319, upload-time = "2026-06-25T05:27:10.243Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5d/b1/d0b97ffd2856a7d19c63024a89fb84813cb9d2ed7fa8fdbedf9e2f13a9ab/setuptools_scm-10.2.1.tar.gz", hash = "sha256:4fa7dd82cf8c800df59c9a288c90299b1657ff1ecfc3f5cc00287c5dbf5e27a9", size = 154237, upload-time = "2026-07-21T08:08:04.553Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/b4/21180f6fbbf7ed2043bc353608c7b9cc6f7b3d342ad8c287a097e5ca8477/setuptools_scm-10.2.0-py3-none-any.whl", hash = "sha256:6cc5ac7da8e54d74e43503be2fa023eb7a1ca820d5c318e6ae20efc48110d6c7", size = 27741, upload-time = "2026-06-25T05:27:09.037Z" }, + { url = "https://files.pythonhosted.org/packages/c3/b4/02ac1d9833b882c87b3a4e82703e70eac4ab33d0d15fb123e60bb01f3bc1/setuptools_scm-10.2.1-py3-none-any.whl", hash = "sha256:b7c82f4102d389ee57dc66ccdb4f9b4bca3c40ba83b43f1f63d68ccd72db2580", size = 29078, upload-time = "2026-07-21T08:08:03.293Z" }, ] [[package]] @@ -3495,11 +3503,11 @@ wheels = [ [[package]] name = "soupsieve" -version = "2.8.4" +version = "2.9.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/2c/0a5f6f8ee0d5589e48c7640213ed5175d52cf540a06725b628cc1a45d6ce/soupsieve-2.8.4.tar.gz", hash = "sha256:e121fd02e975c695e4e9e8774a5ee35d74714b59307868dcc5319ad2d9e3328e", size = 121110, upload-time = "2026-05-24T13:55:57.154Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/38/e12680bbe6b4f8f3d17adcaf38d26850aa756c85cf4a80e79fc12a018fe8/soupsieve-2.9.1.tar.gz", hash = "sha256:c33e6605bbc71dd628b00c632d58ae607c22bade247e52553928f83bbb75b4ba", size = 122261, upload-time = "2026-07-21T16:57:17.452Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/f5/0c41cb68dcae6b7de4fac4188a3a9589e21fb31df21ea3a2e888db95e6c9/soupsieve-2.8.4-py3-none-any.whl", hash = "sha256:e7e6b0769c8f51ed59acab6e994b00621096cfb1c640a7509295987388fbaf65", size = 37304, upload-time = "2026-05-24T13:55:55.406Z" }, + { url = "https://files.pythonhosted.org/packages/0f/2c/437fe806897c2d6cfdc3ee43a18da8bf8e568530a4ae9bac781541ca9896/soupsieve-2.9.1-py3-none-any.whl", hash = "sha256:4f4477399246b7a0c720a88ca2454b11cd6bb9ae4c9d170140786e916776c14c", size = 37404, upload-time = "2026-07-21T16:57:16.421Z" }, ] [[package]] @@ -3510,23 +3518,23 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "alabaster", marker = "python_full_version < '3.11'" }, - { name = "babel", marker = "python_full_version < '3.11'" }, - { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, - { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "imagesize", marker = "python_full_version < '3.11'" }, - { name = "jinja2", marker = "python_full_version < '3.11'" }, - { name = "packaging", marker = "python_full_version < '3.11'" }, - { name = "pygments", marker = "python_full_version < '3.11'" }, - { name = "requests", marker = "python_full_version < '3.11'" }, - { name = "snowballstemmer", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.11'" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" } }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, + { name = "tomli" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611, upload-time = "2024-10-13T20:27:13.93Z" } wheels = [ @@ -3543,23 +3551,23 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "alabaster", marker = "python_full_version == '3.11.*'" }, - { name = "babel", marker = "python_full_version == '3.11.*'" }, - { name = "colorama", marker = "python_full_version == '3.11.*' and sys_platform == 'win32'" }, - { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "imagesize", marker = "python_full_version == '3.11.*'" }, - { name = "jinja2", marker = "python_full_version == '3.11.*'" }, - { name = "packaging", marker = "python_full_version == '3.11.*'" }, - { name = "pygments", marker = "python_full_version == '3.11.*'" }, - { name = "requests", marker = "python_full_version == '3.11.*'" }, - { name = "roman-numerals", marker = "python_full_version == '3.11.*'" }, - { name = "snowballstemmer", marker = "python_full_version == '3.11.*'" }, - { name = "sphinxcontrib-applehelp", marker = "python_full_version == '3.11.*'" }, - { name = "sphinxcontrib-devhelp", marker = "python_full_version == '3.11.*'" }, - { name = "sphinxcontrib-htmlhelp", marker = "python_full_version == '3.11.*'" }, - { name = "sphinxcontrib-jsmath", marker = "python_full_version == '3.11.*'" }, - { name = "sphinxcontrib-qthelp", marker = "python_full_version == '3.11.*'" }, - { name = "sphinxcontrib-serializinghtml", marker = "python_full_version == '3.11.*'" }, + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" } }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "roman-numerals" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/50/a8c6ccc36d5eacdfd7913ddccd15a9cee03ecafc5ee2bc40e1f168d85022/sphinx-9.0.4.tar.gz", hash = "sha256:594ef59d042972abbc581d8baa577404abe4e6c3b04ef61bd7fc2acbd51f3fa3", size = 8710502, upload-time = "2025-12-04T07:45:27.343Z" } wheels = [ @@ -3582,23 +3590,23 @@ resolution-markers = [ "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "alabaster", marker = "python_full_version >= '3.12'" }, - { name = "babel", marker = "python_full_version >= '3.12'" }, - { name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, - { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "imagesize", marker = "python_full_version >= '3.12'" }, - { name = "jinja2", marker = "python_full_version >= '3.12'" }, - { name = "packaging", marker = "python_full_version >= '3.12'" }, - { name = "pygments", marker = "python_full_version >= '3.12'" }, - { name = "requests", marker = "python_full_version >= '3.12'" }, - { name = "roman-numerals", marker = "python_full_version >= '3.12'" }, - { name = "snowballstemmer", marker = "python_full_version >= '3.12'" }, - { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.12'" }, - { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.12'" }, - { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.12'" }, - { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.12'" }, - { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.12'" }, - { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.12'" }, + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" } }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "roman-numerals" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } wheels = [ @@ -3658,7 +3666,7 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/2b/69/b34e0cb5336f09c6866d53b4a19d76c227cdec1bbc7ac4de63ca7d58c9c7/sphinx_design-0.6.1.tar.gz", hash = "sha256:b44eea3719386d04d765c1a8257caca2b3e6f8421d7b3a5e742c0fd45f84e632", size = 2193689, upload-time = "2024-08-02T13:48:44.277Z" } wheels = [ @@ -3684,7 +3692,7 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/13/7b/804f311da4663a4aecc6cf7abd83443f3d4ded970826d0c958edc77d4527/sphinx_design-0.7.0.tar.gz", hash = "sha256:d2a3f5b19c24b916adb52f97c5f00efab4009ca337812001109084a740ec9b7a", size = 2203582, upload-time = "2026-01-19T13:12:53.297Z" } @@ -3865,11 +3873,37 @@ wheels = [ name = "stevedore" version = "5.8.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] sdist = { url = "https://files.pythonhosted.org/packages/e9/88/35e4d27d9177d7df76d060e0a18f69c6c5794c96960c94042e20a12c8ba2/stevedore-5.8.0.tar.gz", hash = "sha256:b49867b32ca3016e94100e68dbf26e72aa7b8708d0a3f73c08aeb220370ac715", size = 514710, upload-time = "2026-05-18T09:15:27.731Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f5/ac/19f9941c74add59d17694930ec8105d5eddeee4ce56dd8632b765ca16d6c/stevedore-5.8.0-py3-none-any.whl", hash = "sha256:88eede9e66ca80e34085b9174e2327da2c61ac91f24f70e41c3ad76e4bb4872b", size = 54553, upload-time = "2026-05-18T09:15:25.82Z" }, ] +[[package]] +name = "stevedore" +version = "5.9.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/dd/04d56c2a5232358df41f3d0f0e31833d378b6c8ed7803a6b1b7867b0eba6/stevedore-5.9.0.tar.gz", hash = "sha256:abbd0af7a38a8bbb1d6adea2e35b17609cf004eaac323e88a8d8963640dd2b3c", size = 514850, upload-time = "2026-07-02T11:38:08.509Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/8d/008761f6e1000600e5303db30d05724bdcf3d2d186cbb59fac79b52e39ed/stevedore-5.9.0-py3-none-any.whl", hash = "sha256:e520945d4c257700eddc1eb1d79df04b2ea578eef185e0e3fa5b442fc848d3f7", size = 54463, upload-time = "2026-07-02T11:38:07.43Z" }, +] + [[package]] name = "sympy" version = "1.14.0" @@ -3982,20 +4016,20 @@ wheels = [ [[package]] name = "typing-extensions" -version = "4.15.0" +version = "4.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, ] [[package]] name = "tzdata" -version = "2026.2" +version = "2026.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" } +sdist = { url = "https://files.pythonhosted.org/packages/92/ff/5a28bdfd8c3ebec42564ac7d0e54ca3db65044a9314a97f9564fa7a1e926/tzdata-2026.3.tar.gz", hash = "sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415", size = 198674, upload-time = "2026-07-10T08:50:37.887Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" }, + { url = "https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931", size = 348168, upload-time = "2026-07-10T08:50:36.46Z" }, ] [[package]] @@ -4009,21 +4043,21 @@ wheels = [ [[package]] name = "vcs-versioning" -version = "2.2.2" +version = "2.2.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/95/c95bb74950763a163defcf4cedf6c5edfca1d623fd5031b76516ece85076/vcs_versioning-2.2.2.tar.gz", hash = "sha256:4ac4ded78720cdb4d0291ae58ace87e1e9201912e1023f3029c6cce5c9152cfb", size = 143135, upload-time = "2026-06-29T13:26:06.901Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e9/bb/5270e8ffd3123051ac4fbbaabf61afb8f27819ac8ced0f0cc4b24137d30e/vcs_versioning-2.2.3.tar.gz", hash = "sha256:c21e284aa98c8269996791a19d6173baf953cf9996ef42ec3ebcbdc151e18b9b", size = 143653, upload-time = "2026-07-28T21:07:36.045Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/9e/1f06f4ddc3a74ccfe0877490caaf4a5233e65401160afc8cb13207815f5e/vcs_versioning-2.2.2-py3-none-any.whl", hash = "sha256:fe7fb216f8780a5516e7864a9333aacb1b70015b087a9be3918da76ef2760808", size = 108014, upload-time = "2026-06-29T13:26:05.367Z" }, + { url = "https://files.pythonhosted.org/packages/95/2e/83be70d0379bbe17034e5d5960fae2a23cc136d466786607d2615ac2612b/vcs_versioning-2.2.3-py3-none-any.whl", hash = "sha256:4f7873af76f5cc24e701608354f376f5eb70b317d663787ace57a7edd7a27506", size = 108180, upload-time = "2026-07-28T21:07:33.842Z" }, ] [[package]] name = "virtualenv" -version = "21.5.1" +version = "21.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -4032,9 +4066,9 @@ dependencies = [ { name = "python-discovery" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f1/a5/81f987504738e6defeed61ec1c47e2aefab3c35d8eeb87e1b3f38cf28254/virtualenv-21.5.1.tar.gz", hash = "sha256:dca3bf98275a59c652b69d68e73433e597d977c2da9198882479d1a7188009c8", size = 4578798, upload-time = "2026-06-16T16:23:58.603Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/25/e367a7229b0914772ca8d81b41fde012d9feda68523b52644a571bb21ce8/virtualenv-21.7.0.tar.gz", hash = "sha256:7f9519b9432ff11b6e1a3e94061664efc2ff99ea21780e3cf4f6bd0a5da8b37c", size = 5527510, upload-time = "2026-07-21T13:12:14.109Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/02/3623e6169bed617ed1e2d372f7c69f92ec28d54c4dfc997055c8578ec148/virtualenv-21.5.1-py3-none-any.whl", hash = "sha256:55aa670b67bbfb991b03fda39bd3276d92c419d702376e98c5df1c9989a26783", size = 4558820, upload-time = "2026-06-16T16:23:56.963Z" }, + { url = "https://files.pythonhosted.org/packages/a5/7a/ae29312b1e88a22e81f5d21fc11526d2a114089776c2550d2b205b6c2a47/virtualenv-21.7.0-py3-none-any.whl", hash = "sha256:a8370c1c5530fbabf955e40b8fbbc68a431648b10f9433faa587db30a06e51dd", size = 5507078, upload-time = "2026-07-21T13:12:12.136Z" }, ] [[package]] diff --git a/vendor/tomlplusplus/README.md b/vendor/tomlplusplus/README.md new file mode 100644 index 0000000000..6e5384047b --- /dev/null +++ b/vendor/tomlplusplus/README.md @@ -0,0 +1,10 @@ +# toml++ single-header distribution + +`toml.hpp` is the upstream single-header distribution of +[toml++](https://github.com/marzer/tomlplusplus), pinned to commit +`a43ad3787293f4a46b1d70c0924b5a25d10e79fc` from 24 May 2026. + +The unmodified file has SHA-256 digest +`e8a56bd3ae26d71c7414615f4d85bce5072077b2e1ca455289211170a541e026`. Its preamble +contains the MIT license, as prescribed for upstream's single-header +distribution. diff --git a/vendor/tomlplusplus/toml.hpp b/vendor/tomlplusplus/toml.hpp new file mode 100644 index 0000000000..caf87c4c21 --- /dev/null +++ b/vendor/tomlplusplus/toml.hpp @@ -0,0 +1,17899 @@ +//---------------------------------------------------------------------------------------------------------------------- +// +// toml++ v3.4.0 +// https://github.com/marzer/tomlplusplus +// SPDX-License-Identifier: MIT +// +//---------------------------------------------------------------------------------------------------------------------- +// +// - THIS FILE WAS ASSEMBLED FROM MULTIPLE HEADER FILES BY A SCRIPT - PLEASE DON'T EDIT IT DIRECTLY - +// +// If you wish to submit a contribution to toml++, hooray and thanks! Before you crack on, please be aware that this +// file was assembled from a number of smaller files by a python script, and code contributions should not be made +// against it directly. You should instead make your changes in the relevant source file(s). The file names of the files +// that contributed to this header can be found at the beginnings and ends of the corresponding sections of this file. +// +//---------------------------------------------------------------------------------------------------------------------- +// +// TOML Language Specifications: +// latest: https://github.com/toml-lang/toml/blob/master/README.md +// v1.0.0: https://toml.io/en/v1.0.0 +// v0.5.0: https://toml.io/en/v0.5.0 +// changelog: https://github.com/toml-lang/toml/blob/master/CHANGELOG.md +// +//---------------------------------------------------------------------------------------------------------------------- +// +// MIT License +// +// Copyright (c) Mark Gillard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +// Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//---------------------------------------------------------------------------------------------------------------------- +#ifndef TOMLPLUSPLUS_HPP +#define TOMLPLUSPLUS_HPP + +#define INCLUDE_TOMLPLUSPLUS_H // old guard name used pre-v3 +#define TOMLPLUSPLUS_H // guard name used in the legacy toml.h + +//******** impl/preprocessor.hpp ************************************************************************************* + +#ifndef __cplusplus +#error toml++ is a C++ library. +#endif + +#ifndef TOML_CPP +#ifdef _MSVC_LANG +#if _MSVC_LANG > __cplusplus +#define TOML_CPP _MSVC_LANG +#endif +#endif +#ifndef TOML_CPP +#define TOML_CPP __cplusplus +#endif +#if TOML_CPP >= 202900L +#undef TOML_CPP +#define TOML_CPP 29 +#elif TOML_CPP >= 202600L +#undef TOML_CPP +#define TOML_CPP 26 +#elif TOML_CPP >= 202302L +#undef TOML_CPP +#define TOML_CPP 23 +#elif TOML_CPP >= 202002L +#undef TOML_CPP +#define TOML_CPP 20 +#elif TOML_CPP >= 201703L +#undef TOML_CPP +#define TOML_CPP 17 +#elif TOML_CPP >= 201402L +#undef TOML_CPP +#define TOML_CPP 14 +#elif TOML_CPP >= 201103L +#undef TOML_CPP +#define TOML_CPP 11 +#else +#undef TOML_CPP +#define TOML_CPP 0 +#endif +#endif + +#if !TOML_CPP +#error toml++ requires C++17 or higher. For a pre-C++11 TOML library see https://github.com/ToruNiina/Boost.toml +#elif TOML_CPP < 17 +#error toml++ requires C++17 or higher. For a C++11 TOML library see https://github.com/ToruNiina/toml11 +#endif + +#ifndef TOML_MAKE_VERSION +#define TOML_MAKE_VERSION(major, minor, patch) (((major)*10000) + ((minor)*100) + ((patch))) +#endif + +#ifndef TOML_INTELLISENSE +#ifdef __INTELLISENSE__ +#define TOML_INTELLISENSE 1 +#else +#define TOML_INTELLISENSE 0 +#endif +#endif + +#ifndef TOML_DOXYGEN +#if defined(DOXYGEN) || defined(__DOXYGEN) || defined(__DOXYGEN__) || defined(__doxygen__) || defined(__POXY__) \ + || defined(__poxy__) +#define TOML_DOXYGEN 1 +#else +#define TOML_DOXYGEN 0 +#endif +#endif + +#ifndef TOML_CLANG +#ifdef __clang__ +#define TOML_CLANG __clang_major__ +#else +#define TOML_CLANG 0 +#endif + +// special handling for apple clang; see: +// - https://github.com/marzer/tomlplusplus/issues/189 +// - https://en.wikipedia.org/wiki/Xcode +// - +// https://stackoverflow.com/questions/19387043/how-can-i-reliably-detect-the-version-of-clang-at-preprocessing-time +#if TOML_CLANG && defined(__apple_build_version__) +#undef TOML_CLANG +#define TOML_CLANG_VERSION TOML_MAKE_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__) +#if TOML_CLANG_VERSION >= TOML_MAKE_VERSION(15, 0, 0) +#define TOML_CLANG 16 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(14, 3, 0) +#define TOML_CLANG 15 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(14, 0, 0) +#define TOML_CLANG 14 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(13, 1, 6) +#define TOML_CLANG 13 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(13, 0, 0) +#define TOML_CLANG 12 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(12, 0, 5) +#define TOML_CLANG 11 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(12, 0, 0) +#define TOML_CLANG 10 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(11, 0, 3) +#define TOML_CLANG 9 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(11, 0, 0) +#define TOML_CLANG 8 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(10, 0, 1) +#define TOML_CLANG 7 +#else +#define TOML_CLANG 6 // not strictly correct but doesn't matter below this +#endif +#undef TOML_CLANG_VERSION +#endif +#endif + +#ifndef TOML_ICC +#ifdef __INTEL_COMPILER +#define TOML_ICC __INTEL_COMPILER +#ifdef __ICL +#define TOML_ICC_CL TOML_ICC +#else +#define TOML_ICC_CL 0 +#endif +#else +#define TOML_ICC 0 +#define TOML_ICC_CL 0 +#endif +#endif + +#ifndef TOML_MSVC_LIKE +#ifdef _MSC_VER +#define TOML_MSVC_LIKE _MSC_VER +#else +#define TOML_MSVC_LIKE 0 +#endif +#endif + +#ifndef TOML_MSVC +#if TOML_MSVC_LIKE && !TOML_CLANG && !TOML_ICC +#define TOML_MSVC TOML_MSVC_LIKE +#else +#define TOML_MSVC 0 +#endif +#endif + +#ifndef TOML_GCC_LIKE +#ifdef __GNUC__ +#define TOML_GCC_LIKE __GNUC__ +#else +#define TOML_GCC_LIKE 0 +#endif +#endif + +#ifndef TOML_GCC +#if TOML_GCC_LIKE && !TOML_CLANG && !TOML_ICC +#define TOML_GCC TOML_GCC_LIKE +#else +#define TOML_GCC 0 +#endif +#endif + +#ifndef TOML_CUDA +#if defined(__CUDACC__) || defined(__CUDA_ARCH__) || defined(__CUDA_LIBDEVICE__) +#define TOML_CUDA 1 +#else +#define TOML_CUDA 0 +#endif +#endif + +#ifndef TOML_NVCC +#ifdef __NVCOMPILER_MAJOR__ +#define TOML_NVCC __NVCOMPILER_MAJOR__ +#else +#define TOML_NVCC 0 +#endif +#endif + +#ifndef TOML_ARCH_ITANIUM +#if defined(__ia64__) || defined(__ia64) || defined(_IA64) || defined(__IA64__) || defined(_M_IA64) +#define TOML_ARCH_ITANIUM 1 +#define TOML_ARCH_BITNESS 64 +#else +#define TOML_ARCH_ITANIUM 0 +#endif +#endif + +#ifndef TOML_ARCH_AMD64 +#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_AMD64) +#define TOML_ARCH_AMD64 1 +#define TOML_ARCH_BITNESS 64 +#else +#define TOML_ARCH_AMD64 0 +#endif +#endif + +#ifndef TOML_ARCH_X86 +#if defined(__i386__) || defined(_M_IX86) +#define TOML_ARCH_X86 1 +#define TOML_ARCH_BITNESS 32 +#else +#define TOML_ARCH_X86 0 +#endif +#endif + +#ifndef TOML_ARCH_ARM +#if defined(__aarch64__) || defined(__ARM_ARCH_ISA_A64) || defined(_M_ARM64) || defined(__ARM_64BIT_STATE) \ + || defined(_M_ARM64EC) +#define TOML_ARCH_ARM32 0 +#define TOML_ARCH_ARM64 1 +#define TOML_ARCH_ARM 1 +#define TOML_ARCH_BITNESS 64 +#elif defined(__arm__) || defined(_M_ARM) || defined(__ARM_32BIT_STATE) +#define TOML_ARCH_ARM32 1 +#define TOML_ARCH_ARM64 0 +#define TOML_ARCH_ARM 1 +#define TOML_ARCH_BITNESS 32 +#else +#define TOML_ARCH_ARM32 0 +#define TOML_ARCH_ARM64 0 +#define TOML_ARCH_ARM 0 +#endif +#endif + +#ifndef TOML_ARCH_BITNESS +#define TOML_ARCH_BITNESS 0 +#endif + +#ifndef TOML_ARCH_X64 +#if TOML_ARCH_BITNESS == 64 +#define TOML_ARCH_X64 1 +#else +#define TOML_ARCH_X64 0 +#endif +#endif + +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || defined(__CYGWIN__) +#define TOML_WINDOWS 1 +#else +#define TOML_WINDOWS 0 +#endif + +#ifdef __unix__ +#define TOML_UNIX 1 +#else +#define TOML_UNIX 0 +#endif + +#ifdef __linux__ +#define TOML_LINUX 1 +#else +#define TOML_LINUX 0 +#endif + +// TOML_HAS_INCLUDE +#ifndef TOML_HAS_INCLUDE +#ifdef __has_include +#define TOML_HAS_INCLUDE(header) __has_include(header) +#else +#define TOML_HAS_INCLUDE(header) 0 +#endif +#endif + +// TOML_HAS_BUILTIN +#ifndef TOML_HAS_BUILTIN +#ifdef __has_builtin +#define TOML_HAS_BUILTIN(name) __has_builtin(name) +#else +#define TOML_HAS_BUILTIN(name) 0 +#endif +#endif + +// TOML_HAS_FEATURE +#ifndef TOML_HAS_FEATURE +#ifdef __has_feature +#define TOML_HAS_FEATURE(name) __has_feature(name) +#else +#define TOML_HAS_FEATURE(name) 0 +#endif +#endif + +// TOML_HAS_ATTR +#ifndef TOML_HAS_ATTR +#ifdef __has_attribute +#define TOML_HAS_ATTR(attr) __has_attribute(attr) +#else +#define TOML_HAS_ATTR(attr) 0 +#endif +#endif + +// TOML_HAS_CPP_ATTR +#ifndef TOML_HAS_CPP_ATTR +#ifdef __has_cpp_attribute +#define TOML_HAS_CPP_ATTR(attr) __has_cpp_attribute(attr) +#else +#define TOML_HAS_CPP_ATTR(attr) 0 +#endif +#endif + +// TOML_ATTR (gnu attributes) +#ifndef TOML_ATTR +#if TOML_CLANG || TOML_GCC_LIKE +#define TOML_ATTR(...) __attribute__((__VA_ARGS__)) +#else +#define TOML_ATTR(...) +#endif +#endif + +// TOML_DECLSPEC (msvc attributes) +#ifndef TOML_DECLSPEC +#if TOML_MSVC_LIKE +#define TOML_DECLSPEC(...) __declspec(__VA_ARGS__) +#else +#define TOML_DECLSPEC(...) +#endif +#endif + +// TOML_COMPILER_HAS_EXCEPTIONS +#ifndef TOML_COMPILER_HAS_EXCEPTIONS +#if defined(__EXCEPTIONS) || defined(_CPPUNWIND) || defined(__cpp_exceptions) +#define TOML_COMPILER_HAS_EXCEPTIONS 1 +#else +#define TOML_COMPILER_HAS_EXCEPTIONS 0 +#endif +#endif + +// TOML_COMPILER_HAS_RTTI +#ifndef TOML_COMPILER_HAS_RTTI +#if defined(_CPPRTTI) || defined(__GXX_RTTI) || TOML_HAS_FEATURE(cxx_rtti) +#define TOML_COMPILER_HAS_RTTI 1 +#else +#define TOML_COMPILER_HAS_RTTI 0 +#endif +#endif + +// TOML_CONCAT +#define TOML_CONCAT_1(x, y) x##y +#define TOML_CONCAT(x, y) TOML_CONCAT_1(x, y) + +// TOML_MAKE_STRING +#define TOML_MAKE_STRING_1(s) #s +#define TOML_MAKE_STRING(s) TOML_MAKE_STRING_1(s) + +// TOML_PRAGMA_XXXX (compiler-specific pragmas) +#if TOML_CLANG +#define TOML_PRAGMA_CLANG(decl) _Pragma(TOML_MAKE_STRING(clang decl)) +#else +#define TOML_PRAGMA_CLANG(decl) +#endif +#if TOML_CLANG >= 8 +#define TOML_PRAGMA_CLANG_GE_8(decl) TOML_PRAGMA_CLANG(decl) +#else +#define TOML_PRAGMA_CLANG_GE_8(decl) +#endif +#if TOML_CLANG >= 9 +#define TOML_PRAGMA_CLANG_GE_9(decl) TOML_PRAGMA_CLANG(decl) +#else +#define TOML_PRAGMA_CLANG_GE_9(decl) +#endif +#if TOML_CLANG >= 10 +#define TOML_PRAGMA_CLANG_GE_10(decl) TOML_PRAGMA_CLANG(decl) +#else +#define TOML_PRAGMA_CLANG_GE_10(decl) +#endif +#if TOML_CLANG >= 11 +#define TOML_PRAGMA_CLANG_GE_11(decl) TOML_PRAGMA_CLANG(decl) +#else +#define TOML_PRAGMA_CLANG_GE_11(decl) +#endif +#if TOML_GCC +#define TOML_PRAGMA_GCC(decl) _Pragma(TOML_MAKE_STRING(GCC decl)) +#else +#define TOML_PRAGMA_GCC(decl) +#endif +#if TOML_MSVC +#define TOML_PRAGMA_MSVC(...) __pragma(__VA_ARGS__) +#else +#define TOML_PRAGMA_MSVC(...) +#endif +#if TOML_ICC +#define TOML_PRAGMA_ICC(...) __pragma(__VA_ARGS__) +#else +#define TOML_PRAGMA_ICC(...) +#endif + +// TOML_ALWAYS_INLINE +#ifndef TOML_ALWAYS_INLINE +#ifdef _MSC_VER +#define TOML_ALWAYS_INLINE __forceinline +#elif TOML_GCC || TOML_CLANG || TOML_HAS_ATTR(__always_inline__) +#define TOML_ALWAYS_INLINE \ + TOML_ATTR(__always_inline__) \ + inline +#else +#define TOML_ALWAYS_INLINE inline +#endif +#endif + +// TOML_NEVER_INLINE +#ifndef TOML_NEVER_INLINE +#ifdef _MSC_VER +#define TOML_NEVER_INLINE TOML_DECLSPEC(noinline) +#elif TOML_CUDA // https://gitlab.gnome.org/GNOME/glib/-/issues/2555 +#define TOML_NEVER_INLINE TOML_ATTR(noinline) +#else +#if TOML_GCC || TOML_CLANG || TOML_HAS_ATTR(__noinline__) +#define TOML_NEVER_INLINE TOML_ATTR(__noinline__) +#endif +#endif +#ifndef TOML_NEVER_INLINE +#define TOML_NEVER_INLINE +#endif +#endif + +// MSVC attributes +#ifndef TOML_ABSTRACT_INTERFACE +#define TOML_ABSTRACT_INTERFACE TOML_DECLSPEC(novtable) +#endif +#ifndef TOML_EMPTY_BASES +#define TOML_EMPTY_BASES TOML_DECLSPEC(empty_bases) +#endif + +// TOML_TRIVIAL_ABI +#ifndef TOML_TRIVIAL_ABI +#if TOML_CLANG || TOML_HAS_ATTR(__trivial_abi__) +#define TOML_TRIVIAL_ABI TOML_ATTR(__trivial_abi__) +#else +#define TOML_TRIVIAL_ABI +#endif +#endif + +// TOML_NODISCARD +#ifndef TOML_NODISCARD +#if TOML_CPP >= 17 && TOML_HAS_CPP_ATTR(nodiscard) >= 201603 +#define TOML_NODISCARD [[nodiscard]] +#elif TOML_CLANG || TOML_GCC || TOML_HAS_ATTR(__warn_unused_result__) +#define TOML_NODISCARD TOML_ATTR(__warn_unused_result__) +#else +#define TOML_NODISCARD +#endif +#endif + +// TOML_NODISCARD_CTOR +#ifndef TOML_NODISCARD_CTOR +#if TOML_CPP >= 17 && TOML_HAS_CPP_ATTR(nodiscard) >= 201907 +#define TOML_NODISCARD_CTOR [[nodiscard]] +#else +#define TOML_NODISCARD_CTOR +#endif +#endif + +// pure + const +#ifndef TOML_PURE +#ifdef NDEBUG +#define TOML_PURE \ + TOML_DECLSPEC(noalias) \ + TOML_ATTR(pure) +#else +#define TOML_PURE +#endif +#endif +#ifndef TOML_CONST +#ifdef NDEBUG +#define TOML_CONST \ + TOML_DECLSPEC(noalias) \ + TOML_ATTR(const) +#else +#define TOML_CONST +#endif +#endif +#ifndef TOML_INLINE_GETTER +#define TOML_INLINE_GETTER \ + TOML_NODISCARD \ + TOML_ALWAYS_INLINE +#endif +#ifndef TOML_PURE_GETTER +#define TOML_PURE_GETTER \ + TOML_NODISCARD \ + TOML_PURE +#endif +#ifndef TOML_PURE_INLINE_GETTER +#define TOML_PURE_INLINE_GETTER \ + TOML_NODISCARD \ + TOML_ALWAYS_INLINE \ + TOML_PURE +#endif +#ifndef TOML_CONST_GETTER +#define TOML_CONST_GETTER \ + TOML_NODISCARD \ + TOML_CONST +#endif +#ifndef TOML_CONST_INLINE_GETTER +#define TOML_CONST_INLINE_GETTER \ + TOML_NODISCARD \ + TOML_ALWAYS_INLINE \ + TOML_CONST +#endif + +// TOML_ASSUME +#ifndef TOML_ASSUME +#ifdef _MSC_VER +#define TOML_ASSUME(expr) __assume(expr) +#elif TOML_ICC || TOML_CLANG || TOML_HAS_BUILTIN(__builtin_assume) +#define TOML_ASSUME(expr) __builtin_assume(expr) +#elif TOML_HAS_CPP_ATTR(assume) >= 202207 +#define TOML_ASSUME(expr) [[assume(expr)]] +#elif TOML_HAS_ATTR(__assume__) +#define TOML_ASSUME(expr) __attribute__((__assume__(expr))) +#else +#define TOML_ASSUME(expr) static_cast(0) +#endif +#endif + +// TOML_UNREACHABLE +#ifndef TOML_UNREACHABLE +#ifdef _MSC_VER +#define TOML_UNREACHABLE __assume(0) +#elif TOML_ICC || TOML_CLANG || TOML_GCC || TOML_HAS_BUILTIN(__builtin_unreachable) +#define TOML_UNREACHABLE __builtin_unreachable() +#else +#define TOML_UNREACHABLE static_cast(0) +#endif +#endif + +// TOML_LIKELY +#if TOML_CPP >= 20 && TOML_HAS_CPP_ATTR(likely) >= 201803 +#define TOML_LIKELY(...) (__VA_ARGS__) [[likely]] +#define TOML_LIKELY_CASE [[likely]] +#elif TOML_GCC || TOML_CLANG || TOML_HAS_BUILTIN(__builtin_expect) +#define TOML_LIKELY(...) (__builtin_expect(!!(__VA_ARGS__), 1)) +#else +#define TOML_LIKELY(...) (__VA_ARGS__) +#endif +#ifndef TOML_LIKELY_CASE +#define TOML_LIKELY_CASE +#endif + +// TOML_UNLIKELY +#if TOML_CPP >= 20 && TOML_HAS_CPP_ATTR(unlikely) >= 201803 +#define TOML_UNLIKELY(...) (__VA_ARGS__) [[unlikely]] +#define TOML_UNLIKELY_CASE [[unlikely]] +#elif TOML_GCC || TOML_CLANG || TOML_HAS_BUILTIN(__builtin_expect) +#define TOML_UNLIKELY(...) (__builtin_expect(!!(__VA_ARGS__), 0)) +#else +#define TOML_UNLIKELY(...) (__VA_ARGS__) +#endif +#ifndef TOML_UNLIKELY_CASE +#define TOML_UNLIKELY_CASE +#endif + +// TOML_FLAGS_ENUM +#if TOML_CLANG || TOML_HAS_ATTR(flag_enum) +#define TOML_FLAGS_ENUM __attribute__((flag_enum)) +#else +#define TOML_FLAGS_ENUM +#endif + +// TOML_OPEN_ENUM + TOML_CLOSED_ENUM +#if TOML_CLANG || TOML_HAS_ATTR(enum_extensibility) +#define TOML_OPEN_ENUM __attribute__((enum_extensibility(open))) +#define TOML_CLOSED_ENUM __attribute__((enum_extensibility(closed))) +#else +#define TOML_OPEN_ENUM +#define TOML_CLOSED_ENUM +#endif + +// TOML_OPEN_FLAGS_ENUM + TOML_CLOSED_FLAGS_ENUM +#define TOML_OPEN_FLAGS_ENUM TOML_OPEN_ENUM TOML_FLAGS_ENUM +#define TOML_CLOSED_FLAGS_ENUM TOML_CLOSED_ENUM TOML_FLAGS_ENUM + +// TOML_MAKE_FLAGS +#define TOML_MAKE_FLAGS_2(T, op, linkage) \ + TOML_CONST_INLINE_GETTER \ + linkage constexpr T operator op(T lhs, T rhs) noexcept \ + { \ + using under = std::underlying_type_t; \ + return static_cast(static_cast(lhs) op static_cast(rhs)); \ + } \ + \ + linkage constexpr T& operator TOML_CONCAT(op, =)(T & lhs, T rhs) noexcept \ + { \ + return lhs = (lhs op rhs); \ + } \ + \ + static_assert(true) +#define TOML_MAKE_FLAGS_1(T, linkage) \ + static_assert(std::is_enum_v); \ + \ + TOML_MAKE_FLAGS_2(T, &, linkage); \ + TOML_MAKE_FLAGS_2(T, |, linkage); \ + TOML_MAKE_FLAGS_2(T, ^, linkage); \ + \ + TOML_CONST_INLINE_GETTER \ + linkage constexpr T operator~(T val) noexcept \ + { \ + using under = std::underlying_type_t; \ + return static_cast(~static_cast(val)); \ + } \ + \ + TOML_CONST_INLINE_GETTER \ + linkage constexpr bool operator!(T val) noexcept \ + { \ + using under = std::underlying_type_t; \ + return !static_cast(val); \ + } \ + \ + static_assert(true) +#define TOML_MAKE_FLAGS(T) TOML_MAKE_FLAGS_1(T, ) + +#define TOML_UNUSED(...) static_cast(__VA_ARGS__) + +#define TOML_DELETE_DEFAULTS(T) \ + T(const T&) = delete; \ + T(T&&) = delete; \ + T& operator=(const T&) = delete; \ + T& operator=(T&&) = delete + +#define TOML_ASYMMETRICAL_EQUALITY_OPS(LHS, RHS, ...) \ + __VA_ARGS__ TOML_NODISCARD \ + friend bool operator==(RHS rhs, LHS lhs) noexcept \ + { \ + return lhs == rhs; \ + } \ + __VA_ARGS__ TOML_NODISCARD \ + friend bool operator!=(LHS lhs, RHS rhs) noexcept \ + { \ + return !(lhs == rhs); \ + } \ + __VA_ARGS__ TOML_NODISCARD \ + friend bool operator!=(RHS rhs, LHS lhs) noexcept \ + { \ + return !(lhs == rhs); \ + } \ + static_assert(true) + +#define TOML_EVAL_BOOL_1(T, F) T +#define TOML_EVAL_BOOL_0(T, F) F + +#if !defined(__POXY__) && !defined(POXY_IMPLEMENTATION_DETAIL) +#define POXY_IMPLEMENTATION_DETAIL(...) __VA_ARGS__ +#endif + +// COMPILER-SPECIFIC WARNING MANAGEMENT + +#if TOML_CLANG + +#define TOML_PUSH_WARNINGS \ + TOML_PRAGMA_CLANG(diagnostic push) \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wunknown-warning-option") \ + static_assert(true) + +#define TOML_DISABLE_SWITCH_WARNINGS \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wswitch") \ + static_assert(true) + +#define TOML_DISABLE_ARITHMETIC_WARNINGS \ + TOML_PRAGMA_CLANG_GE_10(diagnostic ignored "-Wimplicit-int-float-conversion") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wfloat-equal") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wdouble-promotion") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wchar-subscripts") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wshift-sign-overflow") \ + static_assert(true) + +#define TOML_DISABLE_SPAM_WARNINGS \ + TOML_PRAGMA_CLANG_GE_8(diagnostic ignored "-Wdefaulted-function-deleted") \ + TOML_PRAGMA_CLANG_GE_9(diagnostic ignored "-Wctad-maybe-unsupported") \ + TOML_PRAGMA_CLANG_GE_10(diagnostic ignored "-Wzero-as-null-pointer-constant") \ + TOML_PRAGMA_CLANG_GE_11(diagnostic ignored "-Wsuggest-destructor-override") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wweak-vtables") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wweak-template-vtables") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wdouble-promotion") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wchar-subscripts") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wmissing-field-initializers") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wpadded") \ + static_assert(true) + +#define TOML_POP_WARNINGS \ + TOML_PRAGMA_CLANG(diagnostic pop) \ + static_assert(true) + +#define TOML_DISABLE_WARNINGS \ + TOML_PRAGMA_CLANG(diagnostic push) \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Weverything") \ + static_assert(true, "") + +#define TOML_ENABLE_WARNINGS \ + TOML_PRAGMA_CLANG(diagnostic pop) \ + static_assert(true) + +#define TOML_SIMPLE_STATIC_ASSERT_MESSAGES 1 + +#elif TOML_MSVC + +#define TOML_PUSH_WARNINGS \ + __pragma(warning(push)) \ + static_assert(true) + +#if TOML_HAS_INCLUDE() +#pragma warning(push, 0) +#include +#pragma warning(pop) +#define TOML_DISABLE_CODE_ANALYSIS_WARNINGS \ + __pragma(warning(disable : ALL_CODE_ANALYSIS_WARNINGS)) \ + static_assert(true) +#else +#define TOML_DISABLE_CODE_ANALYSIS_WARNINGS static_assert(true) +#endif + +#define TOML_DISABLE_SWITCH_WARNINGS \ + __pragma(warning(disable : 4061)) \ + __pragma(warning(disable : 4062)) \ + __pragma(warning(disable : 4063)) \ + __pragma(warning(disable : 5262)) /* switch-case implicit fallthrough (false-positive) */ \ + __pragma(warning(disable : 26819)) /* cg: unannotated fallthrough */ \ + static_assert(true) + +#define TOML_DISABLE_SPAM_WARNINGS \ + __pragma(warning(disable : 4127)) /* conditional expr is constant */ \ + __pragma(warning(disable : 4324)) /* structure was padded due to alignment specifier */ \ + __pragma(warning(disable : 4348)) \ + __pragma(warning(disable : 4464)) /* relative include path contains '..' */ \ + __pragma(warning(disable : 4505)) /* unreferenced local function removed */ \ + __pragma(warning(disable : 4514)) /* unreferenced inline function has been removed */ \ + __pragma(warning(disable : 4582)) /* constructor is not implicitly called */ \ + __pragma(warning(disable : 4619)) /* there is no warning number 'XXXX' */ \ + __pragma(warning(disable : 4623)) /* default constructor was implicitly defined as deleted */ \ + __pragma(warning(disable : 4625)) /* copy constructor was implicitly defined as deleted */ \ + __pragma(warning(disable : 4626)) /* assignment operator was implicitly defined as deleted */ \ + __pragma(warning(disable : 4710)) /* function not inlined */ \ + __pragma(warning(disable : 4711)) /* function selected for automatic expansion */ \ + __pragma(warning(disable : 4820)) /* N bytes padding added */ \ + __pragma(warning(disable : 5026)) /* move constructor was implicitly defined as deleted */ \ + __pragma(warning(disable : 5027)) /* move assignment operator was implicitly defined as deleted */ \ + __pragma(warning(disable : 5039)) /* potentially throwing function passed to 'extern "C"' function */ \ + __pragma(warning(disable : 5045)) /* Compiler will insert Spectre mitigation */ \ + __pragma(warning(disable : 5264)) /* const variable is not used (false-positive) */ \ + __pragma(warning(disable : 26451)) \ + __pragma(warning(disable : 26490)) \ + __pragma(warning(disable : 26495)) \ + __pragma(warning(disable : 26812)) \ + __pragma(warning(disable : 26819)) \ + static_assert(true) + +#define TOML_DISABLE_ARITHMETIC_WARNINGS \ + __pragma(warning(disable : 4365)) /* argument signed/unsigned mismatch */ \ + __pragma(warning(disable : 4738)) /* storing 32-bit float result in memory */ \ + __pragma(warning(disable : 5219)) /* implicit conversion from integral to float */ \ + static_assert(true) + +#define TOML_POP_WARNINGS \ + __pragma(warning(pop)) \ + static_assert(true) + +#define TOML_DISABLE_WARNINGS \ + __pragma(warning(push, 0)) \ + __pragma(warning(disable : 4348)) \ + __pragma(warning(disable : 4668)) \ + __pragma(warning(disable : 5105)) \ + __pragma(warning(disable : 5264)) \ + TOML_DISABLE_CODE_ANALYSIS_WARNINGS; \ + TOML_DISABLE_SWITCH_WARNINGS; \ + TOML_DISABLE_SPAM_WARNINGS; \ + TOML_DISABLE_ARITHMETIC_WARNINGS; \ + static_assert(true) + +#define TOML_ENABLE_WARNINGS TOML_POP_WARNINGS + +#elif TOML_ICC + +#define TOML_PUSH_WARNINGS \ + __pragma(warning(push)) \ + static_assert(true) + +#define TOML_DISABLE_SPAM_WARNINGS \ + __pragma(warning(disable : 82)) /* storage class is not first */ \ + __pragma(warning(disable : 111)) /* statement unreachable (false-positive) */ \ + __pragma(warning(disable : 869)) /* unreferenced parameter */ \ + __pragma(warning(disable : 1011)) /* missing return (false-positive) */ \ + __pragma(warning(disable : 2261)) /* assume expr side-effects discarded */ \ + static_assert(true) + +#define TOML_POP_WARNINGS \ + __pragma(warning(pop)) \ + static_assert(true) + +#define TOML_DISABLE_WARNINGS \ + __pragma(warning(push, 0)) \ + TOML_DISABLE_SPAM_WARNINGS + +#define TOML_ENABLE_WARNINGS \ + __pragma(warning(pop)) \ + static_assert(true) + +#elif TOML_GCC + +#define TOML_PUSH_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic push) \ + static_assert(true) + +#define TOML_DISABLE_SWITCH_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wswitch") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wswitch-enum") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wswitch-default") \ + static_assert(true) + +#define TOML_DISABLE_ARITHMETIC_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wfloat-equal") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wsign-conversion") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wchar-subscripts") \ + static_assert(true) + +#define TOML_DISABLE_SUGGEST_ATTR_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wsuggest-attribute=const") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wsuggest-attribute=pure") \ + static_assert(true) + +#define TOML_DISABLE_SPAM_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wpadded") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wcast-align") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wcomment") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wtype-limits") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wuseless-cast") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wchar-subscripts") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wsubobject-linkage") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wmissing-field-initializers") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wmaybe-uninitialized") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wnoexcept") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wnull-dereference") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wduplicated-branches") \ + static_assert(true) + +#define TOML_POP_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic pop) \ + static_assert(true) + +#define TOML_DISABLE_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic push) \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wall") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wextra") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wpedantic") \ + TOML_DISABLE_SWITCH_WARNINGS; \ + TOML_DISABLE_ARITHMETIC_WARNINGS; \ + TOML_DISABLE_SUGGEST_ATTR_WARNINGS; \ + TOML_DISABLE_SPAM_WARNINGS; \ + static_assert(true) + +#define TOML_ENABLE_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic pop) \ + static_assert(true) + +#endif + +#ifndef TOML_PUSH_WARNINGS +#define TOML_PUSH_WARNINGS static_assert(true) +#endif +#ifndef TOML_DISABLE_CODE_ANALYSIS_WARNINGS +#define TOML_DISABLE_CODE_ANALYSIS_WARNINGS static_assert(true) +#endif +#ifndef TOML_DISABLE_SWITCH_WARNINGS +#define TOML_DISABLE_SWITCH_WARNINGS static_assert(true) +#endif +#ifndef TOML_DISABLE_SUGGEST_ATTR_WARNINGS +#define TOML_DISABLE_SUGGEST_ATTR_WARNINGS static_assert(true) +#endif +#ifndef TOML_DISABLE_SPAM_WARNINGS +#define TOML_DISABLE_SPAM_WARNINGS static_assert(true) +#endif +#ifndef TOML_DISABLE_ARITHMETIC_WARNINGS +#define TOML_DISABLE_ARITHMETIC_WARNINGS static_assert(true) +#endif +#ifndef TOML_POP_WARNINGS +#define TOML_POP_WARNINGS static_assert(true) +#endif +#ifndef TOML_DISABLE_WARNINGS +#define TOML_DISABLE_WARNINGS static_assert(true) +#endif +#ifndef TOML_ENABLE_WARNINGS +#define TOML_ENABLE_WARNINGS static_assert(true) +#endif +#ifndef TOML_SIMPLE_STATIC_ASSERT_MESSAGES +#define TOML_SIMPLE_STATIC_ASSERT_MESSAGES 0 +#endif + +#ifdef TOML_CONFIG_HEADER +#include TOML_CONFIG_HEADER +#endif + +// is the library being built as a shared lib/dll using meson and friends? +#ifndef TOML_SHARED_LIB +#define TOML_SHARED_LIB 0 +#endif + +// header-only mode +#if !defined(TOML_HEADER_ONLY) && defined(TOML_ALL_INLINE) // was TOML_ALL_INLINE pre-2.0 +#define TOML_HEADER_ONLY TOML_ALL_INLINE +#endif +#if !defined(TOML_HEADER_ONLY) || (defined(TOML_HEADER_ONLY) && TOML_HEADER_ONLY) || TOML_INTELLISENSE +#undef TOML_HEADER_ONLY +#define TOML_HEADER_ONLY 1 +#endif +#if TOML_DOXYGEN || TOML_SHARED_LIB +#undef TOML_HEADER_ONLY +#define TOML_HEADER_ONLY 0 +#endif + +// internal implementation switch +#if defined(TOML_IMPLEMENTATION) || TOML_HEADER_ONLY +#undef TOML_IMPLEMENTATION +#define TOML_IMPLEMENTATION 1 +#else +#define TOML_IMPLEMENTATION 0 +#endif + +// dll/shared lib function exports (legacy - TOML_API was the old name for this setting) +#if !defined(TOML_EXPORTED_MEMBER_FUNCTION) && !defined(TOML_EXPORTED_STATIC_FUNCTION) \ + && !defined(TOML_EXPORTED_FREE_FUNCTION) && !defined(TOML_EXPORTED_CLASS) && defined(TOML_API) +#define TOML_EXPORTED_MEMBER_FUNCTION TOML_API +#define TOML_EXPORTED_STATIC_FUNCTION TOML_API +#define TOML_EXPORTED_FREE_FUNCTION TOML_API +#endif + +// dll/shared lib exports +#if TOML_SHARED_LIB +#undef TOML_API +#undef TOML_EXPORTED_CLASS +#undef TOML_EXPORTED_MEMBER_FUNCTION +#undef TOML_EXPORTED_STATIC_FUNCTION +#undef TOML_EXPORTED_FREE_FUNCTION +#if TOML_WINDOWS +#if TOML_IMPLEMENTATION +#define TOML_EXPORTED_CLASS __declspec(dllexport) +#define TOML_EXPORTED_FREE_FUNCTION __declspec(dllexport) +#else +#define TOML_EXPORTED_CLASS __declspec(dllimport) +#define TOML_EXPORTED_FREE_FUNCTION __declspec(dllimport) +#endif +#ifndef TOML_CALLCONV +#define TOML_CALLCONV __cdecl +#endif +#elif defined(__GNUC__) && __GNUC__ >= 4 +#define TOML_EXPORTED_CLASS __attribute__((visibility("default"))) +#define TOML_EXPORTED_MEMBER_FUNCTION __attribute__((visibility("default"))) +#define TOML_EXPORTED_STATIC_FUNCTION __attribute__((visibility("default"))) +#define TOML_EXPORTED_FREE_FUNCTION __attribute__((visibility("default"))) +#endif +#endif +#ifndef TOML_EXPORTED_CLASS +#define TOML_EXPORTED_CLASS +#endif +#ifndef TOML_EXPORTED_MEMBER_FUNCTION +#define TOML_EXPORTED_MEMBER_FUNCTION +#endif +#ifndef TOML_EXPORTED_STATIC_FUNCTION +#define TOML_EXPORTED_STATIC_FUNCTION +#endif +#ifndef TOML_EXPORTED_FREE_FUNCTION +#define TOML_EXPORTED_FREE_FUNCTION +#endif + +// experimental language features +#if !defined(TOML_ENABLE_UNRELEASED_FEATURES) && defined(TOML_UNRELEASED_FEATURES) // was TOML_UNRELEASED_FEATURES + // pre-3.0 +#define TOML_ENABLE_UNRELEASED_FEATURES TOML_UNRELEASED_FEATURES +#endif +#if (defined(TOML_ENABLE_UNRELEASED_FEATURES) && TOML_ENABLE_UNRELEASED_FEATURES) || TOML_INTELLISENSE +#undef TOML_ENABLE_UNRELEASED_FEATURES +#define TOML_ENABLE_UNRELEASED_FEATURES 1 +#endif +#ifndef TOML_ENABLE_UNRELEASED_FEATURES +#define TOML_ENABLE_UNRELEASED_FEATURES 0 +#endif + +// parser +#if !defined(TOML_ENABLE_PARSER) && defined(TOML_PARSER) // was TOML_PARSER pre-3.0 +#define TOML_ENABLE_PARSER TOML_PARSER +#endif +#if !defined(TOML_ENABLE_PARSER) || (defined(TOML_ENABLE_PARSER) && TOML_ENABLE_PARSER) || TOML_INTELLISENSE +#undef TOML_ENABLE_PARSER +#define TOML_ENABLE_PARSER 1 +#endif + +// formatters +#if !defined(TOML_ENABLE_FORMATTERS) || (defined(TOML_ENABLE_FORMATTERS) && TOML_ENABLE_FORMATTERS) || TOML_INTELLISENSE +#undef TOML_ENABLE_FORMATTERS +#define TOML_ENABLE_FORMATTERS 1 +#endif + +// SIMD +#if !defined(TOML_ENABLE_SIMD) || (defined(TOML_ENABLE_SIMD) && TOML_ENABLE_SIMD) || TOML_INTELLISENSE +#undef TOML_ENABLE_SIMD +#define TOML_ENABLE_SIMD 1 +#endif + +// windows compat +#if !defined(TOML_ENABLE_WINDOWS_COMPAT) && defined(TOML_WINDOWS_COMPAT) // was TOML_WINDOWS_COMPAT pre-3.0 +#define TOML_ENABLE_WINDOWS_COMPAT TOML_WINDOWS_COMPAT +#endif +#if !defined(TOML_ENABLE_WINDOWS_COMPAT) || (defined(TOML_ENABLE_WINDOWS_COMPAT) && TOML_ENABLE_WINDOWS_COMPAT) \ + || TOML_INTELLISENSE +#undef TOML_ENABLE_WINDOWS_COMPAT +#define TOML_ENABLE_WINDOWS_COMPAT 1 +#endif + +#if !TOML_WINDOWS +#undef TOML_ENABLE_WINDOWS_COMPAT +#define TOML_ENABLE_WINDOWS_COMPAT 0 +#endif + +#ifndef TOML_INCLUDE_WINDOWS_H +#define TOML_INCLUDE_WINDOWS_H 0 +#endif + +// custom optional +#ifdef TOML_OPTIONAL_TYPE +#define TOML_HAS_CUSTOM_OPTIONAL_TYPE 1 +#else +#define TOML_HAS_CUSTOM_OPTIONAL_TYPE 0 +#endif + +// exceptions (library use) +#if TOML_COMPILER_HAS_EXCEPTIONS +#if !defined(TOML_EXCEPTIONS) || (defined(TOML_EXCEPTIONS) && TOML_EXCEPTIONS) +#undef TOML_EXCEPTIONS +#define TOML_EXCEPTIONS 1 +#endif +#else +#if defined(TOML_EXCEPTIONS) && TOML_EXCEPTIONS +#error TOML_EXCEPTIONS was explicitly enabled but exceptions are disabled/unsupported by the compiler. +#endif +#undef TOML_EXCEPTIONS +#define TOML_EXCEPTIONS 0 +#endif + +// calling convention for static/free/friend functions +#ifndef TOML_CALLCONV +#define TOML_CALLCONV +#endif + +#ifndef TOML_UNDEF_MACROS +#define TOML_UNDEF_MACROS 1 +#endif + +#ifndef TOML_MAX_NESTED_VALUES +#define TOML_MAX_NESTED_VALUES 128 +// this refers to the depth of nested values, e.g. inline tables and arrays. +// 128 is very generous; real TOML files rarely exceed single-digit nesting. +// keep this value low enough to avoid stack overflows in sanitizer-instrumented builds +// where each recursion cycle may consume ~3KB of stack. +#endif + +#ifndef TOML_MAX_DOTTED_KEYS_DEPTH +#define TOML_MAX_DOTTED_KEYS_DEPTH 1024 +#endif + +#ifdef TOML_CHAR_8_STRINGS +#if TOML_CHAR_8_STRINGS +#error TOML_CHAR_8_STRINGS was removed in toml++ 2.0.0; all value setters and getters now work with char8_t strings implicitly. +#endif +#endif + +#ifdef TOML_LARGE_FILES +#if !TOML_LARGE_FILES +#error Support for !TOML_LARGE_FILES (i.e. 'small files') was removed in toml++ 3.0.0. +#endif +#endif + +#ifndef TOML_LIFETIME_HOOKS +#define TOML_LIFETIME_HOOKS 0 +#endif + +#ifdef NDEBUG +#undef TOML_ASSERT +#define TOML_ASSERT(expr) static_assert(true) +#endif +#ifndef TOML_ASSERT +#ifndef assert +TOML_DISABLE_WARNINGS; +#include +TOML_ENABLE_WARNINGS; +#endif +#define TOML_ASSERT(expr) assert(expr) +#endif +#ifdef NDEBUG +#define TOML_ASSERT_ASSUME(expr) TOML_ASSUME(expr) +#else +#define TOML_ASSERT_ASSUME(expr) TOML_ASSERT(expr) +#endif + +#ifndef TOML_ENABLE_FLOAT16 +#define TOML_ENABLE_FLOAT16 0 +#endif + +#ifndef TOML_DISABLE_CONDITIONAL_NOEXCEPT_LAMBDA +#define TOML_DISABLE_CONDITIONAL_NOEXCEPT_LAMBDA 0 +#endif + +#ifndef TOML_DISABLE_NOEXCEPT_NOEXCEPT +#define TOML_DISABLE_NOEXCEPT_NOEXCEPT 0 + #ifdef _MSC_VER + #if _MSC_VER <= 1943 // Up to Visual Studio 2022 Version 17.13.6 + #undef TOML_DISABLE_NOEXCEPT_NOEXCEPT + #define TOML_DISABLE_NOEXCEPT_NOEXCEPT 1 + #endif + #endif +#endif + +#if !defined(TOML_FLOAT_CHARCONV) && (TOML_GCC || TOML_CLANG || (TOML_ICC && !TOML_ICC_CL)) +// not supported by any version of GCC or Clang as of 26/11/2020 +// not supported by any version of ICC on Linux as of 11/01/2021 +#define TOML_FLOAT_CHARCONV 0 +#endif +#if !defined(TOML_INT_CHARCONV) && (defined(__EMSCRIPTEN__) || defined(__APPLE__)) +// causes link errors on emscripten +// causes Mac OS SDK version errors on some versions of Apple Clang +#define TOML_INT_CHARCONV 0 +#endif +#ifndef TOML_INT_CHARCONV +#define TOML_INT_CHARCONV 1 +#endif +#ifndef TOML_FLOAT_CHARCONV +#define TOML_FLOAT_CHARCONV 1 +#endif +#if (TOML_INT_CHARCONV || TOML_FLOAT_CHARCONV) && !TOML_HAS_INCLUDE() +#undef TOML_INT_CHARCONV +#undef TOML_FLOAT_CHARCONV +#define TOML_INT_CHARCONV 0 +#define TOML_FLOAT_CHARCONV 0 +#endif + +#if defined(__cpp_concepts) && __cpp_concepts >= 201907 +#define TOML_REQUIRES(...) requires(__VA_ARGS__) +#else +#define TOML_REQUIRES(...) +#endif +#define TOML_ENABLE_IF(...) , typename std::enable_if<(__VA_ARGS__), int>::type = 0 +#define TOML_CONSTRAINED_TEMPLATE(condition, ...) \ + template <__VA_ARGS__ TOML_ENABLE_IF(condition)> \ + TOML_REQUIRES(condition) +#define TOML_HIDDEN_CONSTRAINT(condition, ...) TOML_CONSTRAINED_TEMPLATE(condition, __VA_ARGS__) + +#if defined(__SIZEOF_FLOAT128__) && defined(__FLT128_MANT_DIG__) && defined(__LDBL_MANT_DIG__) \ + && __FLT128_MANT_DIG__ > __LDBL_MANT_DIG__ +#define TOML_FLOAT128 __float128 +#endif + +#ifdef __SIZEOF_INT128__ +#define TOML_INT128 __int128_t +#define TOML_UINT128 __uint128_t +#endif + +// clang-format off + +//******** impl/version.hpp ****************************************************************************************** + +#define TOML_LIB_MAJOR 3 +#define TOML_LIB_MINOR 4 +#define TOML_LIB_PATCH 0 + +#define TOML_LANG_MAJOR 1 +#define TOML_LANG_MINOR 0 +#define TOML_LANG_PATCH 0 + +//******** impl/preprocessor.hpp ************************************************************************************* + +#define TOML_LIB_SINGLE_HEADER 1 + +#if TOML_ENABLE_UNRELEASED_FEATURES + #define TOML_LANG_EFFECTIVE_VERSION \ + TOML_MAKE_VERSION(TOML_LANG_MAJOR, TOML_LANG_MINOR, TOML_LANG_PATCH+1) +#else + #define TOML_LANG_EFFECTIVE_VERSION \ + TOML_MAKE_VERSION(TOML_LANG_MAJOR, TOML_LANG_MINOR, TOML_LANG_PATCH) +#endif + +#define TOML_LANG_HIGHER_THAN(major, minor, patch) \ + (TOML_LANG_EFFECTIVE_VERSION > TOML_MAKE_VERSION(major, minor, patch)) + +#define TOML_LANG_AT_LEAST(major, minor, patch) \ + (TOML_LANG_EFFECTIVE_VERSION >= TOML_MAKE_VERSION(major, minor, patch)) + +#define TOML_LANG_UNRELEASED \ + TOML_LANG_HIGHER_THAN(TOML_LANG_MAJOR, TOML_LANG_MINOR, TOML_LANG_PATCH) + +#ifndef TOML_ABI_NAMESPACES + #if TOML_DOXYGEN + #define TOML_ABI_NAMESPACES 0 + #else + #define TOML_ABI_NAMESPACES 1 + #endif +#endif +#if TOML_ABI_NAMESPACES + #define TOML_NAMESPACE_START namespace toml { inline namespace TOML_CONCAT(v, TOML_LIB_MAJOR) + #define TOML_NAMESPACE_END } static_assert(true) + #define TOML_NAMESPACE ::toml::TOML_CONCAT(v, TOML_LIB_MAJOR) + #define TOML_ABI_NAMESPACE_START(name) inline namespace name { static_assert(true) + #define TOML_ABI_NAMESPACE_BOOL(cond, T, F) TOML_ABI_NAMESPACE_START(TOML_CONCAT(TOML_EVAL_BOOL_, cond)(T, F)) + #define TOML_ABI_NAMESPACE_END } static_assert(true) +#else + #define TOML_NAMESPACE_START namespace toml + #define TOML_NAMESPACE_END static_assert(true) + #define TOML_NAMESPACE toml + #define TOML_ABI_NAMESPACE_START(...) static_assert(true) + #define TOML_ABI_NAMESPACE_BOOL(...) static_assert(true) + #define TOML_ABI_NAMESPACE_END static_assert(true) +#endif +#define TOML_IMPL_NAMESPACE_START TOML_NAMESPACE_START { namespace impl +#define TOML_IMPL_NAMESPACE_END } TOML_NAMESPACE_END +#if TOML_HEADER_ONLY + #define TOML_ANON_NAMESPACE_START static_assert(TOML_IMPLEMENTATION); TOML_IMPL_NAMESPACE_START + #define TOML_ANON_NAMESPACE_END TOML_IMPL_NAMESPACE_END + #define TOML_ANON_NAMESPACE TOML_NAMESPACE::impl + #define TOML_EXTERNAL_LINKAGE inline + #define TOML_INTERNAL_LINKAGE inline +#else + #define TOML_ANON_NAMESPACE_START static_assert(TOML_IMPLEMENTATION); \ + using namespace toml; \ + namespace + #define TOML_ANON_NAMESPACE_END static_assert(true) + #define TOML_ANON_NAMESPACE + #define TOML_EXTERNAL_LINKAGE + #define TOML_INTERNAL_LINKAGE static +#endif + +// clang-format on + +// clang-format off + +#if TOML_SIMPLE_STATIC_ASSERT_MESSAGES + + #define TOML_SA_NEWLINE " " + #define TOML_SA_LIST_SEP ", " + #define TOML_SA_LIST_BEG " (" + #define TOML_SA_LIST_END ")" + #define TOML_SA_LIST_NEW " " + #define TOML_SA_LIST_NXT ", " + +#else + + #define TOML_SA_NEWLINE "\n| " + #define TOML_SA_LIST_SEP TOML_SA_NEWLINE " - " + #define TOML_SA_LIST_BEG TOML_SA_LIST_SEP + #define TOML_SA_LIST_END + #define TOML_SA_LIST_NEW TOML_SA_NEWLINE TOML_SA_NEWLINE + #define TOML_SA_LIST_NXT TOML_SA_LIST_NEW + +#endif + +#define TOML_SA_NATIVE_VALUE_TYPE_LIST \ + TOML_SA_LIST_BEG "std::string" \ + TOML_SA_LIST_SEP "int64_t" \ + TOML_SA_LIST_SEP "double" \ + TOML_SA_LIST_SEP "bool" \ + TOML_SA_LIST_SEP "toml::date" \ + TOML_SA_LIST_SEP "toml::time" \ + TOML_SA_LIST_SEP "toml::date_time" \ + TOML_SA_LIST_END + +#define TOML_SA_NODE_TYPE_LIST \ + TOML_SA_LIST_BEG "toml::table" \ + TOML_SA_LIST_SEP "toml::array" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_END + +#define TOML_SA_UNWRAPPED_NODE_TYPE_LIST \ + TOML_SA_LIST_NEW "A native TOML value type" \ + TOML_SA_NATIVE_VALUE_TYPE_LIST \ + \ + TOML_SA_LIST_NXT "A TOML node type" \ + TOML_SA_NODE_TYPE_LIST + +// clang-format on + +TOML_PUSH_WARNINGS; +TOML_DISABLE_SPAM_WARNINGS; +TOML_DISABLE_SWITCH_WARNINGS; +TOML_DISABLE_SUGGEST_ATTR_WARNINGS; + +// misc warning false-positives +#if TOML_MSVC +#pragma warning(disable : 5031) // #pragma warning(pop): likely mismatch +#if TOML_SHARED_LIB +#pragma warning(disable : 4251) // dll exports for std lib types +#endif +#elif TOML_CLANG +TOML_PRAGMA_CLANG(diagnostic ignored "-Wheader-hygiene") +#if TOML_CLANG >= 12 +TOML_PRAGMA_CLANG(diagnostic ignored "-Wc++20-extensions") +#endif +#if TOML_CLANG == 13 +TOML_PRAGMA_CLANG(diagnostic ignored "-Wreserved-identifier") +#endif +#endif + +//******** impl/std_new.hpp ****************************************************************************************** + +TOML_DISABLE_WARNINGS; +#include +TOML_ENABLE_WARNINGS; + +#if (!defined(__apple_build_version__) && TOML_CLANG >= 8) || TOML_GCC >= 7 || TOML_ICC >= 1910 || TOML_MSVC >= 1914 +#define TOML_LAUNDER(x) __builtin_launder(x) +#elif defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606 +#define TOML_LAUNDER(x) std::launder(x) +#else +#define TOML_LAUNDER(x) x +#endif + +//******** impl/std_string.hpp *************************************************************************************** + +TOML_DISABLE_WARNINGS; +#include +#include +TOML_ENABLE_WARNINGS; + +#if TOML_DOXYGEN \ + || (defined(__cpp_char8_t) && __cpp_char8_t >= 201811 && defined(__cpp_lib_char8_t) \ + && __cpp_lib_char8_t >= 201907) +#define TOML_HAS_CHAR8 1 +#else +#define TOML_HAS_CHAR8 0 +#endif + +namespace toml // non-abi namespace; this is not an error +{ + using namespace std::string_literals; + using namespace std::string_view_literals; +} + +#if TOML_ENABLE_WINDOWS_COMPAT + +TOML_IMPL_NAMESPACE_START +{ + TOML_NODISCARD + TOML_EXPORTED_FREE_FUNCTION + std::string narrow(std::wstring_view); + + TOML_NODISCARD + TOML_EXPORTED_FREE_FUNCTION + std::wstring widen(std::string_view); + +#if TOML_HAS_CHAR8 + + TOML_NODISCARD + TOML_EXPORTED_FREE_FUNCTION + std::wstring widen(std::u8string_view); + +#endif +} +TOML_IMPL_NAMESPACE_END; + +#endif // TOML_ENABLE_WINDOWS_COMPAT + +//******** impl/std_optional.hpp ************************************************************************************* + +TOML_DISABLE_WARNINGS; +#if !TOML_HAS_CUSTOM_OPTIONAL_TYPE +#include +#endif +TOML_ENABLE_WARNINGS; + +TOML_NAMESPACE_START +{ +#if TOML_HAS_CUSTOM_OPTIONAL_TYPE + + template + using optional = TOML_OPTIONAL_TYPE; + +#else + + template + using optional = std::optional; + +#endif +} +TOML_NAMESPACE_END; + +//******** impl/forward_declarations.hpp ***************************************************************************** + +TOML_DISABLE_WARNINGS; +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +TOML_ENABLE_WARNINGS; +TOML_PUSH_WARNINGS; +#ifdef _MSC_VER +#ifndef __clang__ +#pragma inline_recursion(on) +#endif +#pragma push_macro("min") +#pragma push_macro("max") +#undef min +#undef max +#endif + +#ifndef TOML_DISABLE_ENVIRONMENT_CHECKS +#define TOML_ENV_MESSAGE \ + "If you're seeing this error it's because you're building toml++ for an environment that doesn't conform to " \ + "one of the 'ground truths' assumed by the library. Essentially this just means that I don't have the " \ + "resources to test on more platforms, but I wish I did! You can try disabling the checks by defining " \ + "TOML_DISABLE_ENVIRONMENT_CHECKS, but your mileage may vary. Please consider filing an issue at " \ + "https://github.com/marzer/tomlplusplus/issues to help me improve support for your target environment. " \ + "Thanks!" + +static_assert(CHAR_BIT == 8, TOML_ENV_MESSAGE); +#ifdef FLT_RADIX +static_assert(FLT_RADIX == 2, TOML_ENV_MESSAGE); +#endif +static_assert('A' == 65, TOML_ENV_MESSAGE); +static_assert(sizeof(double) == 8, TOML_ENV_MESSAGE); +static_assert(std::numeric_limits::is_iec559, TOML_ENV_MESSAGE); +static_assert(std::numeric_limits::digits == 53, TOML_ENV_MESSAGE); +static_assert(std::numeric_limits::digits10 == 15, TOML_ENV_MESSAGE); +static_assert(std::numeric_limits::radix == 2, TOML_ENV_MESSAGE); + +#undef TOML_ENV_MESSAGE +#endif // !TOML_DISABLE_ENVIRONMENT_CHECKS + +// undocumented forward declarations are hidden from doxygen because they fuck it up =/ + +namespace toml // non-abi namespace; this is not an error +{ + using ::std::size_t; + using ::std::intptr_t; + using ::std::uintptr_t; + using ::std::ptrdiff_t; + using ::std::nullptr_t; + using ::std::int8_t; + using ::std::int16_t; + using ::std::int32_t; + using ::std::int64_t; + using ::std::uint8_t; + using ::std::uint16_t; + using ::std::uint32_t; + using ::std::uint64_t; + using ::std::uint_least32_t; + using ::std::uint_least64_t; +} + +TOML_NAMESPACE_START +{ + struct date; + struct time; + struct time_offset; + + TOML_ABI_NAMESPACE_BOOL(TOML_HAS_CUSTOM_OPTIONAL_TYPE, custopt, stdopt); + struct date_time; + TOML_ABI_NAMESPACE_END; + + struct source_position; + struct source_region; + + class node; + template + class node_view; + + class key; + class array; + class table; + template + class value; + + class path; + + class toml_formatter; + class json_formatter; + class yaml_formatter; + + TOML_ABI_NAMESPACE_BOOL(TOML_EXCEPTIONS, ex, noex); +#if TOML_EXCEPTIONS + using parse_result = table; +#else + class parse_result; +#endif + TOML_ABI_NAMESPACE_END; // TOML_EXCEPTIONS +} +TOML_NAMESPACE_END; + +TOML_IMPL_NAMESPACE_START +{ + using node_ptr = std::unique_ptr; + + TOML_ABI_NAMESPACE_BOOL(TOML_EXCEPTIONS, impl_ex, impl_noex); + class parser; + TOML_ABI_NAMESPACE_END; // TOML_EXCEPTIONS + + // clang-format off + + inline constexpr std::string_view control_char_escapes[] = + { + "\\u0000"sv, + "\\u0001"sv, + "\\u0002"sv, + "\\u0003"sv, + "\\u0004"sv, + "\\u0005"sv, + "\\u0006"sv, + "\\u0007"sv, + "\\b"sv, + "\\t"sv, + "\\n"sv, + "\\u000B"sv, + "\\f"sv, + "\\r"sv, + "\\u000E"sv, + "\\u000F"sv, + "\\u0010"sv, + "\\u0011"sv, + "\\u0012"sv, + "\\u0013"sv, + "\\u0014"sv, + "\\u0015"sv, + "\\u0016"sv, + "\\u0017"sv, + "\\u0018"sv, + "\\u0019"sv, + "\\u001A"sv, + "\\u001B"sv, + "\\u001C"sv, + "\\u001D"sv, + "\\u001E"sv, + "\\u001F"sv, + }; + + inline constexpr std::string_view node_type_friendly_names[] = + { + "none"sv, + "table"sv, + "array"sv, + "string"sv, + "integer"sv, + "floating-point"sv, + "boolean"sv, + "date"sv, + "time"sv, + "date-time"sv + }; + + // clang-format on +} +TOML_IMPL_NAMESPACE_END; + +#if TOML_ABI_NAMESPACES +#if TOML_EXCEPTIONS +#define TOML_PARSER_TYPENAME TOML_NAMESPACE::impl::impl_ex::parser +#else +#define TOML_PARSER_TYPENAME TOML_NAMESPACE::impl::impl_noex::parser +#endif +#else +#define TOML_PARSER_TYPENAME TOML_NAMESPACE::impl::parser +#endif + +namespace toml +{ +} + +TOML_NAMESPACE_START // abi namespace +{ + inline namespace literals + { + } + + enum class TOML_CLOSED_ENUM node_type : uint8_t + { + none, + table, + array, + string, + integer, + floating_point, + boolean, + date, + time, + date_time + }; + + template + inline std::basic_ostream& operator<<(std::basic_ostream& lhs, node_type rhs) + { + const auto str = impl::node_type_friendly_names[static_cast>(rhs)]; + using str_char_t = decltype(str)::value_type; + if constexpr (std::is_same_v) + return lhs << str; + else + { + if constexpr (sizeof(Char) == sizeof(str_char_t)) + return lhs << std::basic_string_view{ reinterpret_cast(str.data()), str.length() }; + else + return lhs << str.data(); + } + } + + enum class TOML_OPEN_FLAGS_ENUM value_flags : uint16_t // being an "OPEN" flags enum is not an error + { + none, + format_as_binary = 1, + format_as_octal = 2, + format_as_hexadecimal = 3, + }; + TOML_MAKE_FLAGS(value_flags); + + inline constexpr value_flags preserve_source_value_flags = + POXY_IMPLEMENTATION_DETAIL(value_flags{ static_cast>(-1) }); + + enum class TOML_CLOSED_FLAGS_ENUM format_flags : uint64_t + { + none, + quote_dates_and_times = (1ull << 0), + quote_infinities_and_nans = (1ull << 1), + allow_literal_strings = (1ull << 2), + allow_multi_line_strings = (1ull << 3), + allow_real_tabs_in_strings = (1ull << 4), + allow_unicode_strings = (1ull << 5), + allow_binary_integers = (1ull << 6), + allow_octal_integers = (1ull << 7), + allow_hexadecimal_integers = (1ull << 8), + indent_sub_tables = (1ull << 9), + indent_array_elements = (1ull << 10), + indentation = indent_sub_tables | indent_array_elements, + relaxed_float_precision = (1ull << 11), + terse_key_value_pairs = (1ull << 12), + force_multiline_arrays = (1ull << 13), + }; + TOML_MAKE_FLAGS(format_flags); + + template + struct TOML_TRIVIAL_ABI inserter + { + static_assert(std::is_reference_v); + + T value; + }; + template + inserter(T&&) -> inserter; + template + inserter(T&) -> inserter; + + using default_formatter = toml_formatter; +} +TOML_NAMESPACE_END; + +TOML_IMPL_NAMESPACE_START +{ + template + using remove_cvref = std::remove_cv_t>; + + template + using common_signed_type = std::common_type_t...>; + + template + inline constexpr bool is_one_of = (false || ... || std::is_same_v); + + template + inline constexpr bool all_integral = (std::is_integral_v && ...); + + template + inline constexpr bool is_cvref = std::is_reference_v || std::is_const_v || std::is_volatile_v; + + template + inline constexpr bool is_wide_string = + is_one_of, const wchar_t*, wchar_t*, std::wstring_view, std::wstring>; + + template + inline constexpr bool value_retrieval_is_nothrow = !std::is_same_v, std::string> +#if TOML_HAS_CHAR8 + && !std::is_same_v, std::u8string> +#endif + + && !is_wide_string; + + template + struct copy_ref_; + template + using copy_ref = typename copy_ref_::type; + + template + struct copy_ref_ + { + using type = Dest; + }; + + template + struct copy_ref_ + { + using type = std::add_lvalue_reference_t; + }; + + template + struct copy_ref_ + { + using type = std::add_rvalue_reference_t; + }; + + template + struct copy_cv_; + template + using copy_cv = typename copy_cv_::type; + + template + struct copy_cv_ + { + using type = Dest; + }; + + template + struct copy_cv_ + { + using type = std::add_const_t; + }; + + template + struct copy_cv_ + { + using type = std::add_volatile_t; + }; + + template + struct copy_cv_ + { + using type = std::add_cv_t; + }; + + template + using copy_cvref = + copy_ref, std::remove_reference_t>, Dest>, Src>; + + template + inline constexpr bool always_false = false; + + template + inline constexpr bool first_is_same = false; + template + inline constexpr bool first_is_same = true; + + template > + struct underlying_type_ + { + using type = std::underlying_type_t; + }; + template + struct underlying_type_ + { + using type = T; + }; + template + using underlying_type = typename underlying_type_::type; + + // general value traits + // (as they relate to their equivalent native TOML type) + struct default_value_traits + { + using native_type = void; + static constexpr bool is_native = false; + static constexpr bool is_losslessly_convertible_to_native = false; + static constexpr bool can_represent_native = false; + static constexpr bool can_partially_represent_native = false; + static constexpr auto type = node_type::none; + }; + + template + struct value_traits; + + template > + struct value_traits_base_selector + { + static_assert(!is_cvref); + + using type = default_value_traits; + }; + template + struct value_traits_base_selector + { + static_assert(!is_cvref); + + using type = value_traits>; + }; + + template + struct value_traits : value_traits_base_selector::type + {}; + template + struct value_traits : value_traits + {}; + template + struct value_traits : value_traits + {}; + template + struct value_traits : value_traits + {}; + template + struct value_traits : value_traits + {}; + template + struct value_traits : value_traits + {}; + + // integer value_traits specializations - standard types + template + struct integer_limits + { + static constexpr T min = T{ (std::numeric_limits>::min)() }; + static constexpr T max = T{ (std::numeric_limits>::max)() }; + }; + template + struct integer_traits_base : integer_limits + { + using native_type = int64_t; + static constexpr bool is_native = std::is_same_v, native_type>; + static constexpr bool is_signed = static_cast>(-1) < underlying_type{}; + static constexpr auto type = node_type::integer; + static constexpr bool can_partially_represent_native = true; + }; + template + struct unsigned_integer_traits : integer_traits_base + { + static constexpr bool is_losslessly_convertible_to_native = + integer_limits>::max <= 9223372036854775807ULL; + static constexpr bool can_represent_native = false; + }; + template + struct signed_integer_traits : integer_traits_base + { + using native_type = int64_t; + static constexpr bool is_losslessly_convertible_to_native = + integer_limits>::min >= (-9223372036854775807LL - 1LL) + && integer_limits>::max <= 9223372036854775807LL; + static constexpr bool can_represent_native = + integer_limits>::min <= (-9223372036854775807LL - 1LL) + && integer_limits>::max >= 9223372036854775807LL; + }; + template ::is_signed> + struct integer_traits : signed_integer_traits + {}; + template + struct integer_traits : unsigned_integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + static_assert(value_traits::is_native); + static_assert(value_traits::is_signed); + static_assert(value_traits::is_losslessly_convertible_to_native); + static_assert(value_traits::can_represent_native); + static_assert(value_traits::can_partially_represent_native); + + // integer value_traits specializations - non-standard types +#ifdef TOML_INT128 + template <> + struct integer_limits + { + static constexpr TOML_INT128 max = + static_cast((TOML_UINT128{ 1u } << ((__SIZEOF_INT128__ * CHAR_BIT) - 1)) - 1); + static constexpr TOML_INT128 min = -max - TOML_INT128{ 1 }; + }; + template <> + struct integer_limits + { + static constexpr TOML_UINT128 min = TOML_UINT128{}; + static constexpr TOML_UINT128 max = (2u * static_cast(integer_limits::max)) + 1u; + }; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; +#endif +#ifdef TOML_SMALL_INT_TYPE + template <> + struct value_traits : signed_integer_traits + {}; +#endif + + // floating-point traits base + template + struct float_traits_base + { + static constexpr auto type = node_type::floating_point; + using native_type = double; + static constexpr bool is_native = std::is_same_v; + static constexpr bool is_signed = true; + + static constexpr int bits = static_cast(sizeof(T) * CHAR_BIT); + static constexpr int digits = MantissaDigits; + static constexpr int digits10 = DecimalDigits; + + static constexpr bool is_losslessly_convertible_to_native = bits <= 64 // + && digits <= 53 // DBL_MANT_DIG + && digits10 <= 15; // DBL_DIG + + static constexpr bool can_represent_native = digits >= 53 // DBL_MANT_DIG + && digits10 >= 15; // DBL_DIG + + static constexpr bool can_partially_represent_native = digits > 0 && digits10 > 0; + }; + template + struct float_traits : float_traits_base::digits, std::numeric_limits::digits10> + {}; +#if TOML_ENABLE_FLOAT16 + template <> + struct float_traits<_Float16> : float_traits_base<_Float16, __FLT16_MANT_DIG__, __FLT16_DIG__> + {}; +#endif +#ifdef TOML_FLOAT128 + template <> + struct float_traits : float_traits_base + {}; +#endif + + // floating-point traits + template <> + struct value_traits : float_traits + {}; + template <> + struct value_traits : float_traits + {}; + template <> + struct value_traits : float_traits + {}; +#if TOML_ENABLE_FLOAT16 + template <> + struct value_traits<_Float16> : float_traits<_Float16> + {}; +#endif +#ifdef TOML_FLOAT128 + template <> + struct value_traits : float_traits + {}; +#endif +#ifdef TOML_SMALL_FLOAT_TYPE + template <> + struct value_traits : float_traits + {}; +#endif + static_assert(value_traits::is_native); + static_assert(value_traits::is_losslessly_convertible_to_native); + static_assert(value_traits::can_represent_native); + static_assert(value_traits::can_partially_represent_native); + + // string value_traits specializations - char-based strings + template + struct string_traits + { + using native_type = std::string; + static constexpr bool is_native = std::is_same_v; + static constexpr bool is_losslessly_convertible_to_native = true; + static constexpr bool can_represent_native = + !std::is_array_v && (!std::is_pointer_v || std::is_const_v>); + static constexpr bool can_partially_represent_native = can_represent_native; + static constexpr auto type = node_type::string; + }; + template <> + struct value_traits : string_traits + {}; + template <> + struct value_traits : string_traits + {}; + template <> + struct value_traits : string_traits + {}; + template + struct value_traits : string_traits + {}; + template <> + struct value_traits : string_traits + {}; + template + struct value_traits : string_traits + {}; + + // string value_traits specializations - char8_t-based strings +#if TOML_HAS_CHAR8 + template <> + struct value_traits : string_traits + {}; + template <> + struct value_traits : string_traits + {}; + template <> + struct value_traits : string_traits + {}; + template + struct value_traits : string_traits + {}; + template <> + struct value_traits : string_traits + {}; + template + struct value_traits : string_traits + {}; +#endif + + // string value_traits specializations - wchar_t-based strings on Windows +#if TOML_ENABLE_WINDOWS_COMPAT + template + struct wstring_traits + { + using native_type = std::string; + static constexpr bool is_native = false; + static constexpr bool is_losslessly_convertible_to_native = true; // narrow + static constexpr bool can_represent_native = std::is_same_v; // widen + static constexpr bool can_partially_represent_native = can_represent_native; + static constexpr auto type = node_type::string; + }; + template <> + struct value_traits : wstring_traits + {}; + template <> + struct value_traits : wstring_traits + {}; + template <> + struct value_traits : wstring_traits + {}; + template + struct value_traits : wstring_traits + {}; + template <> + struct value_traits : wstring_traits + {}; + template + struct value_traits : wstring_traits + {}; +#endif + + // other 'native' value_traits specializations + template + struct native_value_traits + { + using native_type = T; + static constexpr bool is_native = true; + static constexpr bool is_losslessly_convertible_to_native = true; + static constexpr bool can_represent_native = true; + static constexpr bool can_partially_represent_native = true; + static constexpr auto type = NodeType; + }; + template <> + struct value_traits : native_value_traits + {}; + template <> + struct value_traits : native_value_traits + {}; + template <> + struct value_traits