From 430249102aa64dc5ed9108e669d355b3be3cd8a0 Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Wed, 15 Jul 2026 12:43:39 +0200 Subject: [PATCH 01/34] =?UTF-8?q?=F0=9F=A4=96=20Prepare=20Core=20for=20age?= =?UTF-8?q?ntic=20coding=20(#1905)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 *AI text below* 🤖 - pin the MQT templates action to the released v1.4.0 revision - opt Core out of generated `AGENTS.md` synchronization - maintain a Core-specific `AGENTS.md` with real build, test, binding, MLIR, documentation, changelog, generated-file, and GitHub-workflow guidance The shared `docs/ai_usage.md` remains governed by the MQT templates repository, while Core's operational agent instructions can describe its actual layout, commands, generated outputs, and safety constraints. This implements the hybrid AI-guidance strategy without allowing policy to silently diverge. - `prek run --files AGENTS.md .github/workflows/templating.yml` - `git diff --check` - rendered-template verification confirmed that `synchronize-agents-md: false` preserves the local `AGENTS.md`; the rendered documentation build passed `uvx nox -s lint` was also run earlier, but is currently blocked by an unrelated existing `ty` diagnostic in `python/mqt/core/plugins/qiskit/estimator.py:235`. After this merges, the templating workflow should create its usual update PR for the shared AI guide, contribution guide, and pull-request template. Its diff must leave this custom `AGENTS.md` untouched. (cherry picked from commit ec2a388348c94ec5fb561b36b0bb0104657bafd6) Assisted-by: GPT-5.6 via Codex --- .github/workflows/templating.yml | 5 +- AGENTS.md | 244 ++++++++++++++++--------------- 2 files changed, 131 insertions(+), 118 deletions(-) diff --git a/.github/workflows/templating.yml b/.github/workflows/templating.yml index 1e2ed3a9bd..ce247e72ce 100644 --- a/.github/workflows/templating.yml +++ b/.github/workflows/templating.yml @@ -26,12 +26,13 @@ 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@73ca06a4fa58d37cf4520de21228f0008a0b6d90 # v1.4.0 with: token: ${{ steps.create-token.outputs.token }} name: Core organization: ${{ github.repository_owner }} - project-type: c++-python + project-type: c++-mlir-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/AGENTS.md b/AGENTS.md index 1b2bd3efab..2c7b49cfcc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,119 +1,131 @@ - - - -# 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. +- 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 `cmake --preset release`. +- Build it with `cmake --build --preset release`. +- Run all configured C++ tests with `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 + `uv sync --inexact --only-group build --only-group test`. +- Install the package for fast local rebuilds with + `uv sync --inexact --no-dev --no-build-isolation-package mqt-core`. +- Run the Python tests with `uv run --no-sync pytest`; pass a file or `-k` + expression while iterating. +- Run the supported test sessions with `uvx nox -s tests` and + `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 + `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. + +### MLIR and Documentation + +- Build the MLIR documentation with + `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 `uvx nox --non-interactive -s docs`. +- Check documentation links with `uvx nox -s docs -- -b linkcheck`. + +## Generated Files and Validation + +- Do not hand-edit generated stubs, rendered documentation, CMake-generated + files, or template-managed files. +- Run `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. + +## 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. +- Every commit prepared with AI assistance must include the trailer + `Assisted-by: [Model Name] via [Tool Name]`, using the actual model and tool. + +## Handoff Checklist + +- The diff is focused and follows neighboring code conventions. +- Behavioral changes have automated test coverage, and targeted tests pass. +- `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. From 0ae22a6d1be229235d9decc14c72c532b8ad289b Mon Sep 17 00:00:00 2001 From: "mqt-app[bot]" <219534693+mqt-app[bot]@users.noreply.github.com> Date: Wed, 15 Jul 2026 10:53:48 +0000 Subject: [PATCH 02/34] =?UTF-8?q?=F0=9F=93=9D=20Update=20templated=20files?= =?UTF-8?q?=20(#1906)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This pull request updates the files in this repository with the latest changes from the MQT Templates repository. For details on the respective changes, see the [changelog](https://github.com/munich-quantum-toolkit/templates/blob/main/CHANGELOG.md) as well as the [upgrade guide](https://github.com/munich-quantum-toolkit/templates/blob/main/UPGRADING.md). Co-authored-by: mqt-app[bot] <219534693+mqt-app[bot]@users.noreply.github.com> (cherry picked from commit b01a4c6cdb1ae971a5e4c8a7108ff4fdb2448815) Assisted-by: GPT-5.6 via Codex --- .github/pull_request_template.md | 3 +- docs/ai_usage.md | 56 +++++++++++++++++++++----------- docs/contributing.md | 9 +++-- 3 files changed, 46 insertions(+), 22 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 69000717d7..f38e122258 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). +- [ ] 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). - [ ] AI-assisted commits include an `Assisted-by: [Model Name] via [Tool Name]` footer. - [ ] I confirm that I have personally reviewed and understood all AI-generated content, and accept full responsibility for it. diff --git a/docs/ai_usage.md b/docs/ai_usage.md index 7f297cca20..57b50c22e7 100644 --- a/docs/ai_usage.md +++ b/docs/ai_usage.md @@ -31,41 +31,59 @@ 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: +**You must disclose AI assistance.** This helps us understand how the tools are +being used and identify potential issues. Disclose it in the following ways: - **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. +- **Public issue and pull request text**: Use the visible disclosure required in + the [communication policy](#3-communication). ### 5. Licensing and Copyright @@ -125,10 +143,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/contributing.md b/docs/contributing.md index 38d511b079..bbfa844269 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -94,8 +94,13 @@ 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-assisted commits must include an +`Assisted-by: [Model Name] via [Tool Name]` footer. 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 From 7ab110ba0d5a4115e4631bca5f55622fbdf4e567 Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Wed, 15 Jul 2026 13:22:11 +0200 Subject: [PATCH 03/34] =?UTF-8?q?=F0=9F=A4=96=20Add=20ExecPlan=20guidance?= =?UTF-8?q?=20(#1907)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 *AI text below* 🤖 ## Summary - add `.agent/PLANS.md`, a repository-local ExecPlan specification for substantial agent-driven work - direct agents to use task-specific ExecPlans for complex features and significant refactors - preserve the upstream ExecPlan structure while adding MQT Core worktree, policy, and validation requirements ## Why Long-running, cross-cutting Core work benefits from a self-contained plan that records progress, decisions, discoveries, verification, and recovery steps. This makes work easier to review, resume, and hand off between agents without making lightweight fixes bureaucratic. ## Validation - `prek run --files AGENTS.md .agent/PLANS.md` - `git diff --check` `uvx nox -s lint` was also run but remains blocked by the existing unrelated `ty` diagnostic in `python/mqt/core/plugins/qiskit/estimator.py:235`. (cherry picked from commit a228a3dc2614a97447da04d3936a50fe39c9f21c) Assisted-by: GPT-5.6 via Codex --- .agent/PLANS.md | 269 ++++++++++++++++++++++++++++++++++++++++++++++++ AGENTS.md | 8 ++ 2 files changed, 277 insertions(+) create mode 100644 .agent/PLANS.md diff --git a/.agent/PLANS.md b/.agent/PLANS.md new file mode 100644 index 0000000000..bcdcf21df9 --- /dev/null +++ b/.agent/PLANS.md @@ -0,0 +1,269 @@ +# 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 state the task +worktree and branch it owns, preserve unrelated user changes, and never modify +another task's worktree. 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/AGENTS.md b/AGENTS.md index 2c7b49cfcc..af91044b4e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -100,6 +100,14 @@ Use Google-style Python docstrings. Prefer fixing diagnostics from `ruff` and - 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 From 1158a34f62fd917e80c3edfaa7cb9f1bf0b5666d Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Wed, 15 Jul 2026 13:38:15 +0200 Subject: [PATCH 04/34] =?UTF-8?q?=F0=9F=A4=96=20Make=20ExecPlans=20checkou?= =?UTF-8?q?t-independent=20(#1908)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 *AI text below* 🤖 ## Summary - remove the requirement for checked-in ExecPlans to name a task worktree and branch - require repository-relative scope and paths instead - explicitly prohibit local filesystem paths, developer accounts, checkout locations, and ephemeral branches ## Why ExecPlans are committed repository artifacts and must remain portable across clones, machines, and contributors. The previous wording encouraged local development setup details to leak into plans. ## Validation - `prek run --files .agent/PLANS.md` - `git diff --check` - `uvx nox -s lint` (cherry picked from commit b5cb91888a608460790ad2ff038c513ac70b7018) Assisted-by: GPT-5.6 via Codex --- .agent/PLANS.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/.agent/PLANS.md b/.agent/PLANS.md index bcdcf21df9..7cf314d21d 100644 --- a/.agent/PLANS.md +++ b/.agent/PLANS.md @@ -167,12 +167,17 @@ dependency has the required behavior in isolation. ## MQT Core requirements -In addition to the requirements above, an MQT Core ExecPlan must state the task -worktree and branch it owns, preserve unrelated user changes, and never modify -another task's worktree. 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. +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, From fe2643dad53755ffb84c78c2043f633fc6e0422b Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Thu, 23 Jul 2026 14:24:28 +0200 Subject: [PATCH 05/34] Keep agent tool caches inside worktrees (#1931) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 *AI text below* 🤖 Keep cache-producing agent workflows self-contained in each MQT Core worktree. This avoids sandbox failures when `uv`, `uvx`, `ccache`, or `sccache` attempt to access cache directories outside the active workspace, while leaving ordinary contributors free to configure their own caches. This pull request: - adds `.agent/run.sh` to run agent commands with worktree-local `uv`, tool, XDG, `prek`, `ccache`, and `sccache` state; - adds `.agent/clean-caches.sh` for explicit cleanup after a significant batch of work; - configures the CMake presets to use worktree-local compiler caches capped at 4 GiB each; - updates `AGENTS.md` to require the wrapper for cache-producing agent commands and document cleanup expectations; - deliberately does not set `tool.uv.cache-dir` in `pyproject.toml`, so non-agent workflows retain their preferred cache configuration. The cleanup helper targets only the worktree-local `.cache` directory and leaves build trees and other worktree state intact. - `./.agent/run.sh prek run --files AGENTS.md CMakePresets.json .agent/run.sh .agent/clean-caches.sh pyproject.toml` - `./.agent/run.sh uvx nox -s lint` - shell syntax checks for both helper scripts - CMake preset discovery - local `uv` and `ccache` configuration checks - focused CMake configure and build using the localized compiler cache - cleanup verification confirming that build output remains intact - `git diff --check origin/main...HEAD` - [x] The pull request only contains commits that are focused and relevant to this change. - [x] I have added appropriate tests that cover the new/changed functionality, or documented why automated tests are not needed. - [x] I have updated the documentation to reflect these changes. - [x] The changes follow the project's style guidelines and introduce no new warnings. - [x] The changes are fully tested and pass the relevant checks. - [x] I have reviewed my own code changes. **If PR contains AI-assisted content:** - [x] Any agent that created, edited, or submitted GitHub content was explicitly authorized for that scope, as required by our AI Usage Guidelines. - [x] Every agent-authored or agent-edited public text body begins with the visible disclosure `🤖 *AI text below* 🤖` (titles are exempt). - [x] AI-assisted commits include an `Assisted-by: [Model Name] via [Tool Name]` footer. - [x] I confirm that I have personally reviewed and understood all AI-generated content, and accept full responsibility for it. (cherry picked from commit 85851b3f8dac877210f6534521af842ab369222a) Assisted-by: GPT-5.6 via Codex --- .agent/clean-caches.sh | 49 +++++++++++++++++++++++++++++++ .agent/run.sh | 36 +++++++++++++++++++++++ AGENTS.md | 65 ++++++++++++++++++++++++++++++------------ CMakePresets.json | 14 +++++++-- 4 files changed, 144 insertions(+), 20 deletions(-) create mode 100755 .agent/clean-caches.sh create mode 100755 .agent/run.sh 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/AGENTS.md b/AGENTS.md index af91044b4e..5b512ea762 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,9 +48,9 @@ MQT Core. The project-wide policy for AI-assisted contributions is ### C++ -- Configure a release build with `cmake --preset release`. -- Build it with `cmake --build --preset release`. -- Run all configured C++ tests with `ctest --preset release`. +- 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`. @@ -67,36 +67,65 @@ types such as `llvm::SmallVector` and `llvm::function_ref` where appropriate. ### Python and Bindings - Install build and test dependencies with - `uv sync --inexact --only-group build --only-group test`. + `./.agent/run.sh uv sync --inexact --only-group build --only-group test`. - Install the package for fast local rebuilds with - `uv sync --inexact --no-dev --no-build-isolation-package mqt-core`. -- Run the Python tests with `uv run --no-sync pytest`; pass a file or `-k` - expression while iterating. -- Run the supported test sessions with `uvx nox -s tests` and - `uvx nox -s minimums`. Python 3.14 variants are `tests-3.14` and - `minimums-3.14`. + `./.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 - `uvx nox -s stubs`. Never edit generated `.pyi` files in `python/mqt/core/` - manually. + `./.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 - `cmake --build --preset release --target mlir-doc`. + `./.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 `uvx nox --non-interactive -s docs`. -- Check documentation links with `uvx nox -s docs -- -b linkcheck`. +- 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`. ## Generated Files and Validation - Do not hand-edit generated stubs, rendered documentation, CMake-generated files, or template-managed files. -- Run `uvx nox -s lint` after each completed batch of changes. It runs the full - `prek` hook set, including formatting, spelling, type, and metadata checks. +- 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. @@ -131,7 +160,7 @@ task's decisions and progress. - The diff is focused and follows neighboring code conventions. - Behavioral changes have automated test coverage, and targeted tests pass. -- `uvx nox -s lint` passes. +- `./.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 --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", From 6cf43d9b9c47b38b2d03f56e2f61d1ad9f8fa279 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 01:00:19 +0000 Subject: [PATCH 06/34] =?UTF-8?q?=E2=AC=86=EF=B8=8F=F0=9F=90=8D=20Update?= =?UTF-8?q?=20dependency=20scikit-build-core=20to=20v1=20(#1918)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [scikit-build-core](https://redirect.github.com/scikit-build/scikit-build-core) ([changelog](https://scikit-build-core.readthedocs.io/en/latest/changelog.html)) | `~=0.12` → `~=1.0` | ![age](https://developer.mend.io/api/mc/badges/age/pypi/scikit-build-core/1.0.3?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/scikit-build-core/0.12.2/1.0.3?slim=true) | --- ### Release Notes
scikit-build/scikit-build-core (scikit-build-core) ### [`v1.0.3`](https://redirect.github.com/scikit-build/scikit-build-core/releases/tag/v1.0.3): Version 1.0.3 [Compare Source](https://redirect.github.com/scikit-build/scikit-build-core/compare/v1.0.2...v1.0.3) This patch release polishes the setuptools compatibility plugin: `config_settings` are now threaded through to the build command, the source tree stays clean in strict editable mode, and the `SubCommand` protocol is fully implemented. Fixes: - Complete the setuptools `SubCommand` protocol by [@​henryiii](https://redirect.github.com/henryiii) in [#​1502](https://redirect.github.com/scikit-build/scikit-build-core/issues/1502) - Thread `config_settings` to the build command by [@​henryiii](https://redirect.github.com/henryiii) in [#​1499](https://redirect.github.com/scikit-build/scikit-build-core/issues/1499) - Keep source tree clean in strict editable mode by [@​henryiii](https://redirect.github.com/henryiii) in [#​1501](https://redirect.github.com/scikit-build/scikit-build-core/issues/1501) Documentation: - Put uv first in installer tabs and add it to config-settings by [@​henryiii](https://redirect.github.com/henryiii) in [#​1507](https://redirect.github.com/scikit-build/scikit-build-core/issues/1507) - List choices for Literal options in the README table by [@​henryiii](https://redirect.github.com/henryiii) in [#​1506](https://redirect.github.com/scikit-build/scikit-build-core/issues/1506) - Touch up README options tables, setuptools docs by [@​henryiii](https://redirect.github.com/henryiii) in [#​1504](https://redirect.github.com/scikit-build/scikit-build-core/issues/1504) - Show the projects list as a card grid with language/binding badges by [@​henryiii](https://redirect.github.com/henryiii) in [#​1503](https://redirect.github.com/scikit-build/scikit-build-core/issues/1503) - Add high-star projects to the known projects list by [@​henryiii](https://redirect.github.com/henryiii) in [#​1497](https://redirect.github.com/scikit-build/scikit-build-core/issues/1497) CI and testing: - Make linkcheck retry timeouts from slow hosts by [@​henryiii](https://redirect.github.com/henryiii) in [#​1505](https://redirect.github.com/scikit-build/scikit-build-core/issues/1505) - Forward `-C` config-settings in downstream editable install by [@​henryiii](https://redirect.github.com/henryiii) in [#​1498](https://redirect.github.com/scikit-build/scikit-build-core/issues/1498) **Full Changelog**: ### [`v1.0.2`](https://redirect.github.com/scikit-build/scikit-build-core/releases/tag/v1.0.2): Version 1.0.2 [Compare Source](https://redirect.github.com/scikit-build/scikit-build-core/compare/v1.0.1...v1.0.2) This patch release fixes redirect editable installs so that PEP 302 import hooks (such as `beartype.claw`) that register a `sys.path_hooks` entry are honored for redirected modules. Fixes: - Resolve redirected modules through `sys.path_hooks` by [@​henryiii](https://redirect.github.com/henryiii) in [#​1493](https://redirect.github.com/scikit-build/scikit-build-core/issues/1493) Internal: - Stop using deprecated `build.util.project_wheel_metadata` in [#​1494](https://redirect.github.com/scikit-build/scikit-build-core/issues/1494) **Full Changelog**: ### [`v1.0.1`](https://redirect.github.com/scikit-build/scikit-build-core/releases/tag/v1.0.1): Version 1.0.1 [Compare Source](https://redirect.github.com/scikit-build/scikit-build-core/compare/v1.0.0...v1.0.1) This patch release fixes two bugs: shared namespace packages were being truncated in redirect editable installs, and the wheel timestamp clamp could overflow on 32-bit `time_t` platforms. Fixes: - Don't truncate shared namespaces in redirect mode by [@​henryiii](https://redirect.github.com/henryiii) in [#​1483](https://redirect.github.com/scikit-build/scikit-build-core/issues/1483) - Wheel timestamp clamp overflows on 32-bit `time_t` platforms by [@​henryiii](https://redirect.github.com/henryiii) in [#​1484](https://redirect.github.com/scikit-build/scikit-build-core/issues/1484) Internal: - Update pre-commit hooks in [#​1480](https://redirect.github.com/scikit-build/scikit-build-core/issues/1480) **Full Changelog**: ### [`v1.0.0`](https://redirect.github.com/scikit-build/scikit-build-core/releases/tag/v1.0.0): Version 1.0.0 [Compare Source](https://redirect.github.com/scikit-build/scikit-build-core/compare/v0.12.2...v1.0.0) We've worked through over 100 issues and ran extensive review sweeps, fixing over 90 bugs and adding 40+ features! We've added support for PEP 803 (free-threaded stable ABI), PEP 808 (partially dynamic metadata), PEP 829 (`.start` files), and experimental PEP 817 (variants). Editable installs are much better, with lots of edge cases fixed, like namespace packages and resource discovery. For rebuilds, which are still experimental, loaders expose a manual `rebuild()`, inplace mode now supports rebuilds, and rebuilds can optionally target a persistent install tree (which removes several caveats). The setuptools and hatchling plugins are no longer experimental and Hatchling supports editable installs. There's a new unified `scikit-build` CLI, including an `init` command to scaffold new projects. Wheels can be reproducible. Dynamic metadata follows a spec with entry-point discovery and uses `[[tool.dynamic-metadata]]`, and you can write your own plugins. There are new settings: an `env` table for setting environment variables, `install.targets`, `force-include` for SDists and wheels, `sdist.resolve-symlinks`, and a `${SKBUILD__DIR}` prefix for targeting wheel trees; existing settings gained `sdist.inclusion-mode = "explicit"` and list-valued `cmake.build-type`. Imports are lazy on Python 3.15+ (PEP 810), making CLI and backend startup faster. Lots of documentation has been added and updated, along with new examples and samples. We've also polished f2py-cmake and cython-cmake. As (almost) always, if you set a minimum-version, your build should be unaffected. Behavior changes to be aware of: SDists now resolve symlinks by default, with an `sdist.resolve-symlinks` setting providing `"all"`, `"external"`, `"none"`, and `"classic"` modes. The old names of renamed configuration fields (`cmake.minimum-version`, `ninja.minimum-version`, `cmake.verbose`, and `cmake.targets`) now error unless `minimum-version` is set, and the deprecated `tool.scikit-build.metadata` table warns if minimum-version is not set. The empty `pyproject` extra was removed; installers like pip, build, and uv merely warn if you still request it. Also see [the announcement post](https://iscinumpy.dev/post/scikit-build-core-1-0). Features: - CLI: - unified `scikit-build` command with submodules as subcommands in [#​1355](https://redirect.github.com/scikit-build/scikit-build-core/issues/1355) - add `init` command to scaffold starter projects in [#​1357](https://redirect.github.com/scikit-build/scikit-build-core/issues/1357) - Configuration providers: - entry-point configuration providers in [#​1406](https://redirect.github.com/scikit-build/scikit-build-core/issues/1406) - New settings: - `env`: settable environment variables in [#​1377](https://redirect.github.com/scikit-build/scikit-build-core/issues/1377) (replaces [#​1370](https://redirect.github.com/scikit-build/scikit-build-core/issues/1370), unreleased) - `install.targets` for custom install targets in [#​1371](https://redirect.github.com/scikit-build/scikit-build-core/issues/1371) - `sdist.force-include`, `wheel.force-include` to map files to include in [#​1364](https://redirect.github.com/scikit-build/scikit-build-core/issues/1364); entries are served live in redirect editable installs in [#​1441](https://redirect.github.com/scikit-build/scikit-build-core/issues/1441) - `wheel.reproducible` allows building a reproducible wheel (with some setup) in [#​1389](https://redirect.github.com/scikit-build/scikit-build-core/issues/1389) - `editable.rebuild-dir` at a persistent install tree (opt-in) in [#​1375](https://redirect.github.com/scikit-build/scikit-build-core/issues/1375) - Updated settings: - `cmake.build-type` now can be a list for multiple builds in [#​1386](https://redirect.github.com/scikit-build/scikit-build-core/issues/1386) - `wheel.packages` can now include single module files in [#​1395](https://redirect.github.com/scikit-build/scikit-build-core/issues/1395) - `cmake.build-type` honors `CMAKE_BUILD_TYPE` from the environment in [#​1372](https://redirect.github.com/scikit-build/scikit-build-core/issues/1372) - add `"explicit"` SDist inclusion-mode (opt-in include) in [#​1249](https://redirect.github.com/scikit-build/scikit-build-core/issues/1249) - resolve symlinks by default by [@​Doekin](https://redirect.github.com/Doekin) in [#​1265](https://redirect.github.com/scikit-build/scikit-build-core/issues/1265), with `"external"`, `"classic"`, and `"none"` (store directory symlinks) modes in [#​1434](https://redirect.github.com/scikit-build/scikit-build-core/issues/1434) - support PEP 803, the free-threaded stable ABI, including the combined `abi3.abi3t` wheel tag, in [#​1269](https://redirect.github.com/scikit-build/scikit-build-core/issues/1269) and [#​1382](https://redirect.github.com/scikit-build/scikit-build-core/issues/1382) - Other settings: - add `${SKBUILD__DIR}` prefix for wheel-tree targeting in [#​1379](https://redirect.github.com/scikit-build/scikit-build-core/issues/1379) - add `{name}` placeholder for template-formatted settings in [#​1445](https://redirect.github.com/scikit-build/scikit-build-core/issues/1445) - warn on the deprecated `tool.scikit-build.metadata` table, and error on renamed config fields, unless an old minimum-version is set, in [#​1397](https://redirect.github.com/scikit-build/scikit-build-core/issues/1397) and [#​1399](https://redirect.github.com/scikit-build/scikit-build-core/issues/1399) - Dynamic metadata: - support `[[tool.dynamic-metadata]]` in [#​1390](https://redirect.github.com/scikit-build/scikit-build-core/issues/1390) and [#​1416](https://redirect.github.com/scikit-build/scikit-build-core/issues/1416) - honor the `dynamic_wheel` hook when building SDists in [#​1433](https://redirect.github.com/scikit-build/scikit-build-core/issues/1433) - emit METADATA 2.6 for PEP 808 partially-dynamic fields, via vendored pyproject-metadata 0.12.1, in [#​1454](https://redirect.github.com/scikit-build/scikit-build-core/issues/1454) and [#​1455](https://redirect.github.com/scikit-build/scikit-build-core/issues/1455) - Experimental: - add experimental PEP 817 variant support in [#​1284](https://redirect.github.com/scikit-build/scikit-build-core/issues/1284) - Editable: - PEP 829 (`.start` files) support in [#​1297](https://redirect.github.com/scikit-build/scikit-build-core/issues/1297) - expose `rebuild()` on redirect and inplace module loaders in [#​1403](https://redirect.github.com/scikit-build/scikit-build-core/issues/1403) and [#​1411](https://redirect.github.com/scikit-build/scikit-build-core/issues/1411) - Plugins: - de-experimentalize the setuptools and hatchling plugins in [#​1358](https://redirect.github.com/scikit-build/scikit-build-core/issues/1358) - include plugin extras for setuptools/hatchling, and warn if the extra is missing from `build-system.requires`, in [#​1290](https://redirect.github.com/scikit-build/scikit-build-core/issues/1290) and [#​1344](https://redirect.github.com/scikit-build/scikit-build-core/issues/1344) - Hatchling plugin: - editable install support in [#​1282](https://redirect.github.com/scikit-build/scikit-build-core/issues/1282) - reject `wheel.exclude` as unsupported in [#​1431](https://redirect.github.com/scikit-build/scikit-build-core/issues/1431) - Setuptools plugin: - inplace editable support in [#​1278](https://redirect.github.com/scikit-build/scikit-build-core/issues/1278) - add `cmake_install_dir` and `cmake_install_target` in [#​1286](https://redirect.github.com/scikit-build/scikit-build-core/issues/1286) and [#​1374](https://redirect.github.com/scikit-build/scikit-build-core/issues/1374) - add `cmake_process_manifest_hook` in [#​1285](https://redirect.github.com/scikit-build/scikit-build-core/issues/1285) - allow `cmake_with_sdist=False` in [#​1287](https://redirect.github.com/scikit-build/scikit-build-core/issues/1287) - honor `SKBUILD_CONFIGURE_OPTIONS` and `SKBUILD_BUILD_OPTIONS` in the wrapper in [#​1394](https://redirect.github.com/scikit-build/scikit-build-core/issues/1394) Fixes: - Settings: - accept override-only settings via config-settings in [#​1307](https://redirect.github.com/scikit-build/scikit-build-core/issues/1307) - flag scalar config-settings assignment to dict fields in [#​1435](https://redirect.github.com/scikit-build/scikit-build-core/issues/1435) - entry-point providers: treat their config as dynamic for validation gates, with robust signature dispatch and duplicate-name handling, in [#​1424](https://redirect.github.com/scikit-build/scikit-build-core/issues/1424) and [#​1425](https://redirect.github.com/scikit-build/scikit-build-core/issues/1425) - allow `=` inside dict-style env var values like `SKBUILD_CMAKE_DEFINE` in [#​1311](https://redirect.github.com/scikit-build/scikit-build-core/issues/1311) - accept plain strings for list/table fields in config-settings (e.g. `-Cwheel.packages=src/pkg`) in [#​1323](https://redirect.github.com/scikit-build/scikit-build-core/issues/1323) - don't crash on overrides mixing `inherit` with top-level fields in [#​1323](https://redirect.github.com/scikit-build/scikit-build-core/issues/1323) - apply `if.state` overrides when computing `get_requires_for_build_*` in [#​1322](https://redirect.github.com/scikit-build/scikit-build-core/issues/1322) - render `{root}` correctly in templated settings such as `build-dir` in [#​1318](https://redirect.github.com/scikit-build/scikit-build-core/issues/1318) - JSON schema no longer rejects valid `cmake.define` lists in [#​1306](https://redirect.github.com/scikit-build/scikit-build-core/issues/1306) - Dynamic metadata: - load provider-path providers via a meta-path finder in [#​1366](https://redirect.github.com/scikit-build/scikit-build-core/issues/1366) - correct the `DynamicMetadataProtocol.dynamic_metadata` signature in [#​1365](https://redirect.github.com/scikit-build/scikit-build-core/issues/1365) - combine same-field `dynamic_wheel` reports with OR in [#​1437](https://redirect.github.com/scikit-build/scikit-build-core/issues/1437) - error on unknown keys in `regex`/`template` plugin settings instead of silently ignoring them in [#​1319](https://redirect.github.com/scikit-build/scikit-build-core/issues/1319) - handle typed `vcs_versioning.overrides` in mypy in [#​1388](https://redirect.github.com/scikit-build/scikit-build-core/issues/1388) - Wheel: - auto-discover namespace packages in wheel builds in [#​1385](https://redirect.github.com/scikit-build/scikit-build-core/issues/1385) - don't error when a `wheel.packages` source is CMake-generated in [#​1440](https://redirect.github.com/scikit-build/scikit-build-core/issues/1440) - use `Path.as_posix()` for including generated files in SDist/wheel by [@​shiftinv](https://redirect.github.com/shiftinv) in [#​1381](https://redirect.github.com/scikit-build/scikit-build-core/issues/1381) - clamp `SOURCE_DATE_EPOCH` to the ZIP timestamp range in [#​1423](https://redirect.github.com/scikit-build/scikit-build-core/issues/1423) - emit installer-compatible `abi3t` wheel tag in [#​1380](https://redirect.github.com/scikit-build/scikit-build-core/issues/1380) - honor `CMAKE_OSX_DEPLOYMENT_TARGET` in the wheel platform tag in [#​1338](https://redirect.github.com/scikit-build/scikit-build-core/issues/1338) - honor an explicit empty `license-files` to disable license collection in [#​1311](https://redirect.github.com/scikit-build/scikit-build-core/issues/1311) and [#​1322](https://redirect.github.com/scikit-build/scikit-build-core/issues/1322) - write `location = "source"` generated files as UTF-8, don't crash on subdirectories inside the scripts install dir, and fully prune consecutive excluded directories in [#​1322](https://redirect.github.com/scikit-build/scikit-build-core/issues/1322) - SDist: - don't crash on dangling symlinks with `resolve-symlinks = "all"`, and detect symlink loops when walking the source tree, in [#​1341](https://redirect.github.com/scikit-build/scikit-build-core/issues/1341) and [#​1421](https://redirect.github.com/scikit-build/scikit-build-core/issues/1421) - don't prune directories matched only by glob includes in default inclusion mode in [#​1339](https://redirect.github.com/scikit-build/scikit-build-core/issues/1339) - Editable: - make namespace package installs importable, including remapped namespace packages, in [#​1346](https://redirect.github.com/scikit-build/scikit-build-core/issues/1346) and [#​1427](https://redirect.github.com/scikit-build/scikit-build-core/issues/1427) - fix import subpackage propagation in [#​1304](https://redirect.github.com/scikit-build/scikit-build-core/issues/1304) - don't treat versioned sonames as importable modules in [#​1337](https://redirect.github.com/scikit-build/scikit-build-core/issues/1337) - add symlinked module's real dir to `__path__` in [#​1335](https://redirect.github.com/scikit-build/scikit-build-core/issues/1335) - `importlib.resources.files()` in redirect mode in [#​1279](https://redirect.github.com/scikit-build/scikit-build-core/issues/1279) - recognize Cython `__init__.pxd`/`.pyx` as package indicators by [@​vyasr](https://redirect.github.com/vyasr) in [#​1292](https://redirect.github.com/scikit-build/scikit-build-core/issues/1292) - debounce rebuild to once per process in [#​1368](https://redirect.github.com/scikit-build/scikit-build-core/issues/1368) - `rebuild()` no longer hides the build output on failed rebuilds in [#​1318](https://redirect.github.com/scikit-build/scikit-build-core/issues/1318) - refuse to wipe a populated rebuild-dir, and use safe install/build paths for manual `rebuild()`, in [#​1400](https://redirect.github.com/scikit-build/scikit-build-core/issues/1400) and [#​1429](https://redirect.github.com/scikit-build/scikit-build-core/issues/1429) - only block absolute `wheel.install-dir` for rebuilds in [#​1334](https://redirect.github.com/scikit-build/scikit-build-core/issues/1334) - CMake: - cap `SKBUILD_PROJECT_VERSION` to four components in [#​1369](https://redirect.github.com/scikit-build/scikit-build-core/issues/1369) - strip flags from sysconfig CC/CXX to just the executable, keeping compiler wrappers, in [#​1331](https://redirect.github.com/scikit-build/scikit-build-core/issues/1331) and [#​1418](https://redirect.github.com/scikit-build/scikit-build-core/issues/1418) - generator handling: detect two-token `-G Ninja` and `-G` in `CMAKE_ARGS`, detect multi-config generators set via `cmake.define.CMAKE_GENERATOR` so the right `--config` is used, and apply full Ninja handling to `Ninja Multi-Config` in [#​1320](https://redirect.github.com/scikit-build/scikit-build-core/issues/1320) and [#​1352](https://redirect.github.com/scikit-build/scikit-build-core/issues/1352) - free-threaded Windows import library and two-token `-D` parsing in [#​1422](https://redirect.github.com/scikit-build/scikit-build-core/issues/1422) - don't skip the ninja requirement when the generator is forced to Ninja in [#​1442](https://redirect.github.com/scikit-build/scikit-build-core/issues/1442) - more reliably find the Python library on Windows in [#​1310](https://redirect.github.com/scikit-build/scikit-build-core/issues/1310) - warn when CMake will build a mismatched `x86_64` extension on macOS in [#​1404](https://redirect.github.com/scikit-build/scikit-build-core/issues/1404) - `-DCMAKE_SYSTEM_PROCESSOR` in the CMake args suppresses `ARCHFLAGS` handling on macOS, as documented, in [#​1320](https://redirect.github.com/scikit-build/scikit-build-core/issues/1320) - comments inside `cmake_minimum_required(...)` no longer break `cmake.version = "CMakeLists.txt"`, and unparsable files fall back gracefully in [#​1319](https://redirect.github.com/scikit-build/scikit-build-core/issues/1319) - File API: properly handle `Union` by [@​LecrisUT](https://redirect.github.com/LecrisUT) in [#​1272](https://redirect.github.com/scikit-build/scikit-build-core/issues/1272); read the toolchains reply, tolerate optional link fields, and restore missing `Target.sources`/`InstallRule.type` fields in [#​1311](https://redirect.github.com/scikit-build/scikit-build-core/issues/1311) and [#​1319](https://redirect.github.com/scikit-build/scikit-build-core/issues/1319) - CLI: - argument parsing and clear errors for common mistakes in [#​1426](https://redirect.github.com/scikit-build/scikit-build-core/issues/1426) - Hatchling plugin: - ignore hatchling's reserved enable-by-default hook option in [#​1419](https://redirect.github.com/scikit-build/scikit-build-core/issues/1419) - don't double the `wheel.install-dir` prefix, which made wheels unimportable, in [#​1321](https://redirect.github.com/scikit-build/scikit-build-core/issues/1321) - Setuptools plugin: - wrapper path and classic scikit-build compatibility fixes in [#​1295](https://redirect.github.com/scikit-build/scikit-build-core/issues/1295) and [#​1316](https://redirect.github.com/scikit-build/scikit-build-core/issues/1316) - protect `SetupError` in [#​1293](https://redirect.github.com/scikit-build/scikit-build-core/issues/1293) - accept `wheel-free-setuptools` extra in the plugin warning in [#​1413](https://redirect.github.com/scikit-build/scikit-build-core/issues/1413) - don't require `editable.mode` for `build_ext --inplace` in [#​1432](https://redirect.github.com/scikit-build/scikit-build-core/issues/1432) - build-type list rejection, `package_dir` editable root, and free-threaded limited-API gate in [#​1428](https://redirect.github.com/scikit-build/scikit-build-core/issues/1428) - resolve overrides with the correct `wheel`/`editable` state in [#​1321](https://redirect.github.com/scikit-build/scikit-build-core/issues/1321) - Sweeps: - many further small fixes across settings, wheel, editable, File API, generator detection, and the plugins from extensive review sweeps in [#​1305](https://redirect.github.com/scikit-build/scikit-build-core/issues/1305), [#​1306](https://redirect.github.com/scikit-build/scikit-build-core/issues/1306), [#​1311](https://redirect.github.com/scikit-build/scikit-build-core/issues/1311), [#​1318](https://redirect.github.com/scikit-build/scikit-build-core/issues/1318), [#​1319](https://redirect.github.com/scikit-build/scikit-build-core/issues/1319), [#​1320](https://redirect.github.com/scikit-build/scikit-build-core/issues/1320), [#​1321](https://redirect.github.com/scikit-build/scikit-build-core/issues/1321), [#​1322](https://redirect.github.com/scikit-build/scikit-build-core/issues/1322), and [#​1323](https://redirect.github.com/scikit-build/scikit-build-core/issues/1323) Documentation: - New guides and FAQs: - add a workspaces and monorepos guide in [#​1444](https://redirect.github.com/scikit-build/scikit-build-core/issues/1444) - add a Windows debug builds note in [#​1450](https://redirect.github.com/scikit-build/scikit-build-core/issues/1450) - FAQ for IDE IntelliSense / `compile_commands.json` in [#​1447](https://redirect.github.com/scikit-build/scikit-build-core/issues/1447) - explain target output paths on MSVC / multi-config generators in [#​1446](https://redirect.github.com/scikit-build/scikit-build-core/issues/1446) - FAQ on coverage and debugging (gcov/gcovr/GDB) in [#​1409](https://redirect.github.com/scikit-build/scikit-build-core/issues/1409) - tips for shipping a ctypes-loaded shared library in [#​1408](https://redirect.github.com/scikit-build/scikit-build-core/issues/1408) - FAQ for dependency library in `site-packages/bin` or `lib` in [#​1393](https://redirect.github.com/scikit-build/scikit-build-core/issues/1393) - document `cmake.module` entry point and metadata states in [#​1360](https://redirect.github.com/scikit-build/scikit-build-core/issues/1360) - 1.0 refresh: - dedupe, simplify, and restructure the documentation in [#​1448](https://redirect.github.com/scikit-build/scikit-build-core/issues/1448) - review and refresh documentation for the 1.0 release in [#​1402](https://redirect.github.com/scikit-build/scikit-build-core/issues/1402) - add missing versionadded/versionchanged directives for 1.0 in [#​1405](https://redirect.github.com/scikit-build/scikit-build-core/issues/1405) - refresh stale guidance and version pins in [#​1361](https://redirect.github.com/scikit-build/scikit-build-core/issues/1361) - fix incorrect config keys and stale claims in [#​1359](https://redirect.github.com/scikit-build/scikit-build-core/issues/1359) - remove documented unimplemented search settings in [#​1354](https://redirect.github.com/scikit-build/scikit-build-core/issues/1354) - show config-settings and env var forms in the config reference in [#​1347](https://redirect.github.com/scikit-build/scikit-build-core/issues/1347) - use `-C` instead of `--config-settings` in examples in [#​1451](https://redirect.github.com/scikit-build/scikit-build-core/issues/1451) - minor cleanup and style check in [#​1453](https://redirect.github.com/scikit-build/scikit-build-core/issues/1453) - fix typos and grammar in [#​1362](https://redirect.github.com/scikit-build/scikit-build-core/issues/1362) - Clarifications and fixes: - clarify `wheel.packages` semantics (top-level, not a find/where) in [#​1407](https://redirect.github.com/scikit-build/scikit-build-core/issues/1407) - address post-merge review notes on entry-point config in [#​1438](https://redirect.github.com/scikit-build/scikit-build-core/issues/1438) - fix `sdist.inclusion-mode` name and dynamic-metadata field requirement in [#​1420](https://redirect.github.com/scikit-build/scikit-build-core/issues/1420) - clarify README example and unify `cmake_minimum_required` ranges in [#​1392](https://redirect.github.com/scikit-build/scikit-build-core/issues/1392) - improve docs on source in [#​1276](https://redirect.github.com/scikit-build/scikit-build-core/issues/1276) - avoid a common timeout in [#​1308](https://redirect.github.com/scikit-build/scikit-build-core/issues/1308) - Examples: - use cython-cmake and f2py-cmake in getting started examples in [#​1353](https://redirect.github.com/scikit-build/scikit-build-core/issues/1353) - link `Python::Module` in the Fortran example for CMake 4.x in [#​1373](https://redirect.github.com/scikit-build/scikit-build-core/issues/1373) - Site: - fix RTD version and render GitHub-style admonitions in [#​1410](https://redirect.github.com/scikit-build/scikit-build-core/issues/1410) - make sphinx-tippy tooltips follow the active theme in [#​1349](https://redirect.github.com/scikit-build/scikit-build-core/issues/1349) - add GitHub icon to header icon row in [#​1387](https://redirect.github.com/scikit-build/scikit-build-core/issues/1387) - add more projects using scikit-build-core, including pyarrow by [@​raulcd](https://redirect.github.com/raulcd), in [#​1266](https://redirect.github.com/scikit-build/scikit-build-core/issues/1266) and [#​1309](https://redirect.github.com/scikit-build/scikit-build-core/issues/1309) CI and testing: - Cover uv as an install frontend via a workspace in [#​1443](https://redirect.github.com/scikit-build/scikit-build-core/issues/1443) - Add Python 3.15 and 3.15t to CI in [#​1383](https://redirect.github.com/scikit-build/scikit-build-core/issues/1383) - Skip heavy test matrix on docs-only changes in [#​1356](https://redirect.github.com/scikit-build/scikit-build-core/issues/1356) - Adapt to setuptools-scm 10 / vcs-versioning 2 warnings in [#​1350](https://redirect.github.com/scikit-build/scikit-build-core/issues/1350) - Exclude pytest 9.1.0 (params-fixture override regression) in [#​1329](https://redirect.github.com/scikit-build/scikit-build-core/issues/1329) - Pin old-CMake Windows rows to windows-2022, add a VS 2026 row in [#​1326](https://redirect.github.com/scikit-build/scikit-build-core/issues/1326) - Reduce test-suite wall-clock time in [#​1325](https://redirect.github.com/scikit-build/scikit-build-core/issues/1325) - Fix Windows `PermissionError` from concurrent wheelhouse lock races, and clean up incomplete or old editable wheels in the wheelhouse, in [#​1283](https://redirect.github.com/scikit-build/scikit-build-core/issues/1283) and [#​1288](https://redirect.github.com/scikit-build/scikit-build-core/issues/1288) - Secure GitHub Actions workflows in [#​1302](https://redirect.github.com/scikit-build/scikit-build-core/issues/1302) - Use native uv integration in RTD in [#​1274](https://redirect.github.com/scikit-build/scikit-build-core/issues/1274) - Stub macOS lipo arch probe for fake-subprocess tests in [#​1313](https://redirect.github.com/scikit-build/scikit-build-core/issues/1313) and [#​1314](https://redirect.github.com/scikit-build/scikit-build-core/issues/1314) - Migrate beakerlib test to python script by [@​LecrisUT](https://redirect.github.com/LecrisUT) in [#​1376](https://redirect.github.com/scikit-build/scikit-build-core/issues/1376) Internal: - Faster startup via lazy imports (PEP 810) in [#​1415](https://redirect.github.com/scikit-build/scikit-build-core/issues/1415) - Unify wheel build orchestration across backend and hatch plugin in [#​1345](https://redirect.github.com/scikit-build/scikit-build-core/issues/1345) - Rename editable files to `_editable_skbc_` in [#​1332](https://redirect.github.com/scikit-build/scikit-build-core/issues/1332) - Drop the empty `pyproject` extra in [#​1398](https://redirect.github.com/scikit-build/scikit-build-core/issues/1398) - Remove `orjson` from dependencies in [#​1333](https://redirect.github.com/scikit-build/scikit-build-core/issues/1333) - Support `uvx scikit-build-core` too in [#​1396](https://redirect.github.com/scikit-build/scikit-build-core/issues/1396) - Add a utils package by [@​LecrisUT](https://redirect.github.com/LecrisUT) in [#​1268](https://redirect.github.com/scikit-build/scikit-build-core/issues/1268) - Simplification cleanups from a repo sweep in [#​1312](https://redirect.github.com/scikit-build/scikit-build-core/issues/1312) - Use local `TYPE_CHECKING = False` instead of importing from typing in [#​1412](https://redirect.github.com/scikit-build/scikit-build-core/issues/1412) - Adapt to better pathspec types in [#​1277](https://redirect.github.com/scikit-build/scikit-build-core/issues/1277) ##### New Contributors - [@​raulcd](https://redirect.github.com/raulcd) made their first contribution in [#​1266](https://redirect.github.com/scikit-build/scikit-build-core/pull/1266) - [@​shiftinv](https://redirect.github.com/shiftinv) made their first contribution in [#​1381](https://redirect.github.com/scikit-build/scikit-build-core/pull/1381) - [@​Doekin](https://redirect.github.com/Doekin) made their first contribution in [#​1265](https://redirect.github.com/scikit-build/scikit-build-core/pull/1265) **Full Changelog**:
--- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - "every weekend" - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/munich-quantum-toolkit/core). --------- Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Daniel Haag <121057143+denialhaag@users.noreply.github.com> (cherry picked from commit 70ea61397075c9de60ec8c1943f94f1c645071a0) Assisted-by: GPT-5.6 via Codex --- noxfile.py | 1 + pyproject.toml | 4 ++-- uv.lock | 10 +++++----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/noxfile.py b/noxfile.py index 707bab6c84..4e6bdc9d94 100755 --- a/noxfile.py +++ b/noxfile.py @@ -214,6 +214,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..aa153eed72 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" @@ -327,7 +327,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 = [ diff --git a/uv.lock b/uv.lock index ac56ece132..1a7f12bc1d 100644 --- a/uv.lock +++ b/uv.lock @@ -1746,7 +1746,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 = [ @@ -1761,7 +1761,7 @@ dev = [ { 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 = [ @@ -3221,7 +3221,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 +3230,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]] From 3aee4179f7612b874e8bda5f8479f8b8c830ffbb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 25 Jul 2026 02:07:17 +0000 Subject: [PATCH 07/34] =?UTF-8?q?=E2=AC=86=EF=B8=8F=F0=9F=A9=B9=20Update?= =?UTF-8?q?=20patch=20updates=20(#1940)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> (cherry picked from commit b027e181267a90df982826442c0c65a5ddc79b9a) Assisted-by: GPT-5.6 via Codex --- .github/workflows/templating.yml | 2 +- .pre-commit-config.yaml | 10 +-- docs/conf.py | 2 +- python/mqt/core/__init__.py | 4 +- python/mqt/core/load.py | 2 +- python/mqt/core/plugins/qiskit/__init__.py | 4 +- python/mqt/core/plugins/qiskit/backend.py | 6 +- python/mqt/core/plugins/qiskit/converters.py | 2 +- python/mqt/core/plugins/qiskit/estimator.py | 4 +- .../mqt/core/plugins/qiskit/mqt_to_qiskit.py | 2 +- python/mqt/core/plugins/qiskit/sampler.py | 2 +- test/python/fomac/test_fomac.py | 14 ++--- .../plugins/qiskit/test_mock_backend.py | 62 +++++++++---------- test/python/plugins/qiskit/test_provider.py | 10 +-- test/python/test_cli.py | 2 +- test/python/test_optional_dependency.py | 8 +-- 16 files changed, 68 insertions(+), 68 deletions(-) diff --git a/.github/workflows/templating.yml b/.github/workflows/templating.yml index ce247e72ce..d93cbac03a 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@73ca06a4fa58d37cf4520de21228f0008a0b6d90 # v1.4.0 + - uses: munich-quantum-toolkit/templates@02460c7352d8d8a13414c9c082f062b33c3c5ecb # v1.4.1 with: token: ${{ steps.create-token.outputs.token }} name: Core diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 30dbb0025a..4e87cac210 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 @@ -112,7 +112,7 @@ repos: ## Format Markdown files with rumdl - repo: https://github.com/rvben/rumdl-pre-commit - rev: v0.2.28 + rev: v0.2.35 hooks: - id: rumdl args: [--fix] @@ -145,7 +145,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 +156,7 @@ repos: ## Check Python types with ty - repo: https://github.com/astral-sh/ty-pre-commit - rev: v0.0.55 + rev: v0.0.60 hooks: - id: ty args: [--only-dev] diff --git a/docs/conf.py b/docs/conf.py index c8893c795a..d454a9b88c 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: 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/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..bc8db00cc6 100644 --- a/python/mqt/core/plugins/qiskit/mqt_to_qiskit.py +++ b/python/mqt/core/plugins/qiskit/mqt_to_qiskit.py @@ -409,7 +409,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/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/test/python/fomac/test_fomac.py b/test/python/fomac/test_fomac.py index d3d01a4d28..ab1acc650d 100644 --- a/test/python/fomac/test_fomac.py +++ b/test/python/fomac/test_fomac.py @@ -675,11 +675,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 @@ -761,11 +761,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 +784,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 +829,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", ) 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/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: From bbd2834a6142aaf17f1ae35e3144601c06b431d7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 25 Jul 2026 06:26:40 +0000 Subject: [PATCH 08/34] =?UTF-8?q?=E2=AC=86=EF=B8=8F=F0=9F=A9=B9=20Update?= =?UTF-8?q?=20actions/attest=20action=20to=20v4.2.0=20(#1941)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> (cherry picked from commit 5788b46729ecb4565d86a1d961cf28b480abec17) Assisted-by: GPT-5.6 via Codex --- .github/workflows/cd.yml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index d903b8e557..5ac5fdea00 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -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 diff --git a/pyproject.toml b/pyproject.toml index aa153eed72..745cbee7b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -275,7 +275,7 @@ build = "cp3*" skip = ["*-musllinux_*", "cp313t-*"] 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 = [ From 7910327424db08ee93b7cd31f8ed0dfe7db13651 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 25 Jul 2026 05:33:51 +0000 Subject: [PATCH 09/34] =?UTF-8?q?=E2=AC=86=EF=B8=8F=F0=9F=A9=B9=20Update?= =?UTF-8?q?=20pre-commit=20hook=20astral-sh/ty-pre-commit=20to=20v0.0.61?= =?UTF-8?q?=20(#1942)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> (cherry picked from commit 96b6517cc3b35e4b57f3a0697d8adff3e20a540c) Assisted-by: GPT-5.6 via Codex --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4e87cac210..83d414fd60 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -156,7 +156,7 @@ repos: ## Check Python types with ty - repo: https://github.com/astral-sh/ty-pre-commit - rev: v0.0.60 + rev: v0.0.61 hooks: - id: ty args: [--only-dev] From 099162eb840e4447e8c060c60fd2cbba5c420fb4 Mon Sep 17 00:00:00 2001 From: "mqt-app[bot]" <219534693+mqt-app[bot]@users.noreply.github.com> Date: Sat, 25 Jul 2026 03:54:45 +0000 Subject: [PATCH 10/34] =?UTF-8?q?=F0=9F=93=9D=20Update=20templated=20files?= =?UTF-8?q?=20(#1943)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This pull request updates the files in this repository with the latest changes from the MQT Templates repository. For details on the respective changes, see the [changelog](https://github.com/munich-quantum-toolkit/templates/blob/main/CHANGELOG.md) as well as the [upgrade guide](https://github.com/munich-quantum-toolkit/templates/blob/main/UPGRADING.md). Co-authored-by: mqt-app[bot] <219534693+mqt-app[bot]@users.noreply.github.com> (cherry picked from commit da48c796796b05cab5054cbd28aeb5ec56b07814) Assisted-by: GPT-5.6 via Codex --- docs/contributing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/contributing.md b/docs/contributing.md index bbfa844269..8834f43f97 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -597,7 +597,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. @@ -614,7 +614,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 From 07c481542f0eca09b8d30b3028f3433b3179a18a Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Wed, 29 Jul 2026 15:31:08 +0200 Subject: [PATCH 11/34] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Improve=20Python=20C?= =?UTF-8?q?I=20performance=20(#1944)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 *AI text below* 🤖 - Update all reusable workflow pins to the immutable v2.2.1 release commit. - Share one scikit-build-core build directory across Python test sessions. - Configure coverage.py's `sysmon` core and parallel data collection centrally. - Require pytest-cov 7.1 for the coverage configuration. The shared build directory lets the minimum- and regular-resolution nox sessions reuse the same CMake and Ninja state. On Windows, reusable workflows v2.2.1 initialize native MSVC and use Ninja so the existing job-local `sccache` installation is active. The final test-only experiment passed on macOS and Windows in [run 30434315809](https://github.com/munich-quantum-toolkit/core/actions/runs/30434315809). The full Core CI run for the same source revision also passed on every supported runner in [run 30434310296](https://github.com/munich-quantum-toolkit/core/actions/runs/30434310296). The strongest reuse was on Windows: after the minimum-resolution builds, all five regular-resolution sessions completed in 47 seconds and `sccache` reported a 35.93% hit rate in [run 30404816970](https://github.com/munich-quantum-toolkit/core/actions/runs/30404816970). - [x] `uv lock --check` - [x] Full Core lint session - [x] macOS and Windows performance experiment - [x] Full Core CI matrix - [x] No mutable workflow references or benchmark-only workflow remain - [ ] I confirm that I have personally reviewed and understood all AI-generated content, and accept full responsibility for it. --------- Co-authored-by: Daniel Haag <121057143+denialhaag@users.noreply.github.com> (cherry picked from commit 3e3c7bac7c049478658728dc76bef7a176fa4b7d) Assisted-by: GPT-5.6 via Codex --- .github/workflows/cd.yml | 4 ++-- .github/workflows/ci.yml | 32 +++++++++++++++++--------------- .github/workflows/upstream.yml | 6 +++--- noxfile.py | 3 --- pyproject.toml | 8 +++++--- uv.lock | 4 ++-- 6 files changed, 29 insertions(+), 28 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 5ac5fdea00..3be030b7dc 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 }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9df818c94c..667b698e91 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,9 @@ 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 + with: + additional-python-files: "mlir/**" cpp-tests-ubuntu: name: 🇨‌ Test 🐧 @@ -30,7 +32,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 +54,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 +76,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 +96,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 +115,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 +134,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 +147,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 +159,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 @@ -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/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/noxfile.py b/noxfile.py index 4e6bdc9d94..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", diff --git a/pyproject.toml b/pyproject.toml index 745cbee7b5..fe073acf9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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", @@ -351,7 +353,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/uv.lock b/uv.lock index 1a7f12bc1d..6089a448a5 100644 --- a/uv.lock +++ b/uv.lock @@ -1757,7 +1757,7 @@ 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" }, @@ -1787,7 +1787,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" }, From ecb6cd933949b6d4a810e43a9040092c61b3aa9a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 25 Jul 2026 21:39:16 +0000 Subject: [PATCH 12/34] =?UTF-8?q?=E2=AC=86=EF=B8=8F=F0=9F=A9=B9=20Update?= =?UTF-8?q?=20pre-commit=20hook=20rvben/rumdl-pre-commit=20to=20v0.2.36=20?= =?UTF-8?q?(#1945)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> (cherry picked from commit 9a9433db82e5cdc40d0110eb23a8109d40fad9d2) Assisted-by: GPT-5.6 via Codex --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 83d414fd60..8420c9c8be 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -112,7 +112,7 @@ repos: ## Format Markdown files with rumdl - repo: https://github.com/rvben/rumdl-pre-commit - rev: v0.2.35 + rev: v0.2.36 hooks: - id: rumdl args: [--fix] From bddaecd32dbc149f741ee156cc4811a00ba2fdd2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 26 Jul 2026 13:47:58 +0000 Subject: [PATCH 13/34] =?UTF-8?q?=E2=AC=86=EF=B8=8F=F0=9F=A9=B9=20Update?= =?UTF-8?q?=20release-drafter/release-drafter=20action=20to=20v7.6.0=20(#1?= =?UTF-8?q?946)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> (cherry picked from commit 32a52264d26849a2d66b11193e82fde47acd1ce2) Assisted-by: GPT-5.6 via Codex --- .github/workflows/release-drafter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From e56fd9661bb60ee356dbf3a9833d982833c88f11 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 26 Jul 2026 15:34:25 +0000 Subject: [PATCH 14/34] =?UTF-8?q?=E2=AC=86=EF=B8=8F=F0=9F=A9=B9=20Update?= =?UTF-8?q?=20pypa/gh-action-pypi-publish=20action=20to=20v1.14.1=20(#1947?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> (cherry picked from commit 54b264ed5c0b64c9431ba436bae3f32c9cad447a) Assisted-by: GPT-5.6 via Codex --- .github/workflows/cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 3be030b7dc..d03cbbbd39 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -53,4 +53,4 @@ jobs: 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 From 8f3b31d6c97130df9d6067e6d6631949ee34bd46 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 26 Jul 2026 16:58:40 +0000 Subject: [PATCH 15/34] =?UTF-8?q?=E2=AC=86=EF=B8=8F=F0=9F=A9=B9=20Update?= =?UTF-8?q?=20pre-commit=20hook=20rvben/rumdl-pre-commit=20to=20v0.2.37=20?= =?UTF-8?q?(#1948)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> (cherry picked from commit afd855be798dda8b763dda68378a3cf31017c85e) Assisted-by: GPT-5.6 via Codex --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8420c9c8be..05b50d232d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -112,7 +112,7 @@ repos: ## Format Markdown files with rumdl - repo: https://github.com/rvben/rumdl-pre-commit - rev: v0.2.36 + rev: v0.2.37 hooks: - id: rumdl args: [--fix] From 88849c7bdbce6520dc10c1af0d0094473d3fe00b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 02:29:16 +0000 Subject: [PATCH 16/34] =?UTF-8?q?=E2=AC=86=EF=B8=8F=F0=9F=94=92=EF=B8=8F?= =?UTF-8?q?=20Lock=20file=20maintenance=20(#1949)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> (cherry picked from commit 07fafad95cc18d0d568cd76f5a91f8431d610f4c) Assisted-by: GPT-5.6 via Codex --- uv.lock | 1392 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 713 insertions(+), 679 deletions(-) diff --git a/uv.lock b/uv.lock index 6089a448a5..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" }, @@ -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 = [ @@ -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]] From c0daa578f16463717dd89d035ce8cb2188d7602d Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Wed, 29 Jul 2026 14:58:33 +0200 Subject: [PATCH 17/34] =?UTF-8?q?=F0=9F=90=9B=20Complete=20QDMI=20child-de?= =?UTF-8?q?vice=20string=20conversions=20(#1952)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 *AI text below* 🤖 Complete the public `qdmi::toString` mappings for the child-device session parameter and device property introduced with child-device support. This keeps diagnostics and property-name formatting exhaustive for the current QDMI enum surface. This small fix was identified while reviewing #1912 and is extracted because it has no dependency on configurable device discovery. The existing child-device changelog entry now also attributes this follow-up to #1952 and retains the original human author attribution. - Focused child-device string-conversion tests: 2 passed - Complete FoMaC C++ test binary: 171 passed - `uvx nox -s lint` - `git diff --check` - [x] The pull request only contains commits that are focused and relevant to this change. - [x] I have added appropriate tests that cover the changed functionality. - [x] Documentation and migration guidance are not needed for this diagnostic-string completion. - [x] The existing changelog entry includes this PR and all human authors. - [x] The change follows the project's style guidelines and introduces no new warnings. - [x] The changes are fully tested and pass the relevant local checks. - [x] I have reviewed my own code changes. **If PR contains AI-assisted content:** - [x] 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). - [x] Every agent-authored or agent-edited public text body begins with the visible disclosure `🤖 *AI text below* 🤖` (titles are exempt). - [x] AI-assisted commits include an `Assisted-by: [Model Name] via [Tool Name]` footer. - [x] I confirm that I have personally reviewed and understood all AI-generated content, and accept full responsibility for it. (cherry picked from commit 537a6946c48c2916e32637fd790f018ec051a840) Assisted-by: GPT-5.6 via Codex --- CHANGELOG.md | 3 ++- include/mqt-core/qdmi/common/Common.hpp | 4 ++++ test/fomac/test_fomac.cpp | 7 +++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5562e48fbf..f05d1fd122 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ releases may include breaking changes. ### Added - ✨ 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 @@ -579,6 +579,7 @@ changelogs._ +[#1952]: https://github.com/munich-quantum-toolkit/core/pull/1952 [#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 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/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index f04edf2fd8..d8c28f362a 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")); From d246be91e2f9472705bf5bd70636b4862e08fe8b Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Mon, 27 Jul 2026 19:49:22 +0200 Subject: [PATCH 18/34] =?UTF-8?q?=F0=9F=90=9B=20Keep=20GoogleTest=20discov?= =?UTF-8?q?ery=20artifacts=20in=20the=20build=20tree=20(#1954)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 *AI text below* 🤖 ## Description CMake 4.3 writes a `cmake_test_discovery_*.json` bookkeeping file to the directory passed to `gtest_discover_tests(WORKING_DIRECTORY ...)`. Our test helpers passed source directories there because the same argument also controls the runtime working directory of each discovered test. As a result, configuring and building tests could dirty the source tree. Run GoogleTest discovery from the current binary directory so the bookkeeping file remains a build artifact. Preserve the existing runtime working directory by setting the discovered tests' `WORKING_DIRECTORY` property separately. The property name is deferred through a generator expression so CMake's argument parser does not interpret it as a second discovery option. This is extracted from #1912 because it is independent of the QDMI configuration work. ## Validation - Configured and built representative targets with CMake 4.3.2. - Confirmed that no `cmake_test_discovery_*.json` files appear outside `build/`. - Confirmed that the ordinary helper still runs a FoMaC test from its source directory. - Confirmed that the custom-working-directory helper still runs an IR test from its configured build directory. - `uvx nox -s lint` - `git diff --check` ## Checklist - [x] The pull request only contains commits that are focused and relevant to this change. - [x] I have added appropriate tests that cover the new/changed functionality. (Existing tests exercise both helper variants.) - [x] I have updated the documentation to reflect these changes. (No user-facing documentation change is needed.) - [x] I have added entries to the changelog for any noteworthy additions, changes, fixes, or removals. (Not user-facing.) - [x] I have added migration instructions to the upgrade guide (if needed). (No migration is needed.) - [x] The changes follow the project's style guidelines and introduce no new warnings. - [x] The changes are fully tested and pass the CI checks. (Local checks pass; CI is pending.) - [x] I have reviewed my own code changes. **If PR contains AI-assisted content:** - [x] 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). - [x] Every agent-authored or agent-edited public text body begins with the visible disclosure `🤖 *AI text below* 🤖` (titles are exempt). - [x] AI-assisted commits include an `Assisted-by: [Model Name] via [Tool Name]` footer. - [x] I confirm that I have personally reviewed and understood all AI-generated content, and accept full responsibility for it. (cherry picked from commit d14b33f23ca10cd4590d71f9a5f7575e46e106c8) Assisted-by: GPT-5.6 via Codex --- cmake/PackageAddTest.cmake | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) 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 From 7ebc64b1f71936e6ff9c505d4ad008b5e17e9299 Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Wed, 29 Jul 2026 17:08:34 +0200 Subject: [PATCH 19/34] =?UTF-8?q?=F0=9F=A4=96=20Refine=20agent=20review=20?= =?UTF-8?q?and=20test=20guidance=20(#1960)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 *AI text below* 🤖 ## Description This updates the locally maintained MQT Core agent guide with lessons from recent reviews: - keep tests in the corresponding test tree, organized by the subsystem that owns the behavior, and reserve subprocess tests for genuine CLI contracts; - avoid promoting optional production tools into the default build solely for testing; and - keep MLIR pass, pipeline, and option descriptions aligned with their actual scope, defaults, limitations, and failure modes. It also keeps agent reviews focused on substantive contribution quality. The shared human-facing AI policy is being updated separately in munich-quantum-toolkit/templates#383. ## Checklist - [x] The pull request only contains commits that are focused and relevant to this change. - [x] No behavioral tests are needed for this documentation-only change. - [x] I have updated the documentation to reflect these changes. - [x] No changelog entry is needed for this internal agent-guide update. - [x] No migration instructions are needed. - [x] The changes follow the project's style guidelines and introduce no new warnings. - [x] The full local lint suite passes. - [x] I have reviewed my own code changes. **If PR contains AI-assisted content:** - [x] The agent-managed GitHub actions were explicitly authorized for this scope. - [x] This agent-authored PR description begins with the required visible disclosure. - [x] I confirm that I have reviewed and understood the AI-generated content and accept full responsibility for it. (cherry picked from commit 7c8aeb2d1f41d51666de1cb2f28b191be80c30d0) Assisted-by: GPT-5.6 via Codex --- AGENTS.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 5b512ea762..bf6ae4ed9d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,6 +31,14 @@ MQT Core. The project-wide policy for AI-assisted contributions is - 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 @@ -118,6 +126,10 @@ Use Google-style Python docstrings. Prefer fixing diagnostics from `ruff` and `./.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 @@ -153,8 +165,9 @@ task's decisions and progress. 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. -- Every commit prepared with AI assistance must include the trailer - `Assisted-by: [Model Name] via [Tool Name]`, using the actual model and tool. +- Review findings should focus on substantive correctness, contracts, + maintainability, tests, documentation, licensing, and validation rather than + optional process metadata. ## Handoff Checklist From 684e7acff7e8ee6e09b8888f96803cfcfe58f63f Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Wed, 29 Jul 2026 17:38:02 +0200 Subject: [PATCH 20/34] =?UTF-8?q?=F0=9F=A7=B9=20Simplify=20cibuildwheel=20?= =?UTF-8?q?v4=20configuration=20(#1962)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 *AI text below* 🤖 ## Description Simplify the cibuildwheel configuration after the reusable packaging workflow moved to cibuildwheel v4: - remove the unsupported `cp313t-*` skip selector; - rely on cibuildwheel v4's built-in ABI3 audit instead of appending a duplicate audit to the repair command. The generic `cp3??t-*` test skip remains because test dependencies do not yet provide the required free-threaded wheels. Consequently, `cp314t` continues to be built but is not tested in the wheel job. The corresponding migration guidance is documented in [workflows#424](https://github.com/munich-quantum-toolkit/workflows/pull/424). ## Validation - `uvx --from cibuildwheel==4.1.0 cibuildwheel --platform macos --print-build-identifiers .` - `uvx nox --non-interactive -s lint` - `git diff --check` ## Checklist - [x] The pull request only contains commits that are focused and relevant to this change. - [x] Tests are not applicable to this build-configuration cleanup. - [x] Documentation, changelog, and upgrade-guide changes are not needed in this consumer repository; migration guidance lives in workflows#424. - [x] The changes follow the project's style guidelines and introduce no new warnings. - [x] The local validation checks pass. - [x] I have reviewed the proposed changes. **If PR contains AI-assisted content:** - [x] The agent was explicitly authorized to create these pull requests. - [x] This body begins with the required visible disclosure. - [x] The AI-assisted commit includes an `Assisted-by: GPT-5.6 via Codex` footer. - [x] I confirm that I have personally reviewed and understood all AI-generated content, and accept full responsibility for it. (cherry picked from commit 369e4ae86748c2d697f1de330fd5b6347104c6da) Assisted-by: GPT-5.6 via Codex --- pyproject.toml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fe073acf9e..dd38ea5057 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -274,7 +274,7 @@ 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", "test/circuits", "json/na"] @@ -296,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 = [ From 3d03a0d60617a7e1c9121ea9062bf19f1a6a012c Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Wed, 29 Jul 2026 22:48:39 +0200 Subject: [PATCH 21/34] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Update=20templates?= =?UTF-8?q?=20to=20v1.4.2=20(#1964)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 *AI text below* 🤖 ## Summary - pin the templating workflow to the exact v1.4.2 release commit - apply the generated Release Drafter schema migration and AI-disclosure policy updates - rename the custom Core category to `CI & Tooling` and include the `tooling` label This supersedes the stale v1.4.1-generated changes in #1963. In particular, rendering v1.4.2 no longer changes the MLIR dependency semantics in `docs/installation.md`. ## Validation - `./.agent/run.sh uvx nox --non-interactive -s lint` - `git diff --check` - rendered from signed release commit `d0c5adb86d19a5095f871cb6184cfdab298e943c` ## AI assistance GPT-5.6 via Codex rendered the released templates, prepared the rollout changes, and ran the validation. I reviewed the resulting diff and checks. (cherry picked from commit df4bd768668b617a68ee9ab42314ebb994881aec) Assisted-by: GPT-5.6 via Codex --- .github/pull_request_template.md | 2 +- .github/release-drafter.yml | 122 ++++++++++-------- .../release_drafter_categories.json | 2 +- .github/workflows/templating.yml | 2 +- docs/ai_usage.md | 18 +-- docs/contributing.md | 7 +- 6 files changed, 88 insertions(+), 65 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index f38e122258..3c231295f4 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -29,5 +29,5 @@ This checklist serves as a reminder of a couple of things that ensure your pull - [ ] 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). -- [ ] AI-assisted commits include an `Assisted-by: [Model Name] via [Tool Name]` footer. +- [ ] 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/templating.yml b/.github/workflows/templating.yml index d93cbac03a..2b31befe21 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@02460c7352d8d8a13414c9c082f062b33c3c5ecb # v1.4.1 + - uses: munich-quantum-toolkit/templates@d0c5adb86d19a5095f871cb6184cfdab298e943c # v1.4.2 with: token: ${{ steps.create-token.outputs.token }} name: Core diff --git a/docs/ai_usage.md b/docs/ai_usage.md index 57b50c22e7..f0d98cf5cc 100644 --- a/docs/ai_usage.md +++ b/docs/ai_usage.md @@ -76,14 +76,16 @@ beginning of the edited field. Transparency helps the community understand the role of these tools and develop best practices. -**You must disclose AI assistance.** This helps us understand how the tools are -being used and identify potential issues. Disclose it in the following ways: - -- **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`) -- **Public issue and pull request text**: Use the visible disclosure required in - the [communication policy](#3-communication). +**AI assistance must be disclosed in the PR description.** This helps reviewers +understand how the tools were used and where human verification was applied. + +- **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 diff --git a/docs/contributing.md b/docs/contributing.md index 8834f43f97..7face58961 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -98,9 +98,10 @@ any AI-assisted contribution. In short: 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-assisted commits must include an -`Assisted-by: [Model Name] via [Tool Name]` footer. AI assistance must not be -used for contributions to issues labeled "good first issue". +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 From 968e4b1a715cb027aad8fcdd42dbe8614f166413 Mon Sep 17 00:00:00 2001 From: simon1hofmann <119581649+simon1hofmann@users.noreply.github.com> Date: Wed, 22 Jul 2026 15:36:26 +0200 Subject: [PATCH 22/34] =?UTF-8?q?=E2=9C=A8=20Add=20native=20RCCX=20(relati?= =?UTF-8?q?ve-phase=20Toffoli)=20gate=20support=20(#1886)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backport-of: 85a65460dd98a841f47fa44d016181ad55f31e55 Assisted-by: GPT-5.6 via Codex --- CHANGELOG.md | 5 + bindings/ir/operations/register_optype.cpp | 5 + bindings/ir/register_quantum_computation.cpp | 40 +++++++ docs/qdmi/qdmi_backend.md | 2 +- include/mqt-core/ir/QuantumComputation.hpp | 13 +++ include/mqt-core/ir/operations/OpType.hpp | 9 ++ include/mqt-core/ir/operations/OpType.inc | 1 + .../ir/operations/StandardOperation.hpp | 3 + include/mqt-core/qasm3/StdGates.hpp | 12 +-- .../mqt-core/zx/FunctionalityConstruction.hpp | 7 ++ python/mqt/core/ir/__init__.pyi | 35 ++++++ python/mqt/core/ir/operations.pyi | 8 ++ .../mqt/core/plugins/qiskit/mqt_to_qiskit.py | 2 + .../mqt/core/plugins/qiskit/qiskit_to_mqt.py | 5 + src/dd/Operations.cpp | 52 +++++++++ src/ir/QuantumComputation.cpp | 28 +++++ src/ir/operations/OpType.cpp | 1 + src/ir/operations/StandardOperation.cpp | 6 +- src/qasm3/Parser.cpp | 3 + src/qdmi/devices/dd/Device.cpp | 1 + src/zx/FunctionalityConstruction.cpp | 92 ++++++++++++++++ test/dd/test_dd_functionality.cpp | 11 +- test/dd/test_package.cpp | 30 ++++++ test/ir/test_io.cpp | 100 ++++++++++++++++-- test/ir/test_qfr_functionality.cpp | 8 ++ test/python/plugins/qiskit/test_backend.py | 15 +++ test/python/plugins/test_qiskit.py | 18 +++- test/zx/test_zx_functionality.cpp | 79 ++++++++++++++ 28 files changed, 569 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f05d1fd122..3502b2a1e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ releases may include breaking changes. ### Added +- ✨ Add native relative-phase CCX (`rccx`) support across the IR, DD package, + ZX diagrams, OpenQASM import/export, and Python/Qiskit bindings ([#1886]) + ([**@simon1hofmann**]) - ✨ Add support for QDMI child devices to the driver and FoMaC libraries ([#1897], [#1952]) ([**@burgholzer**]) - ✨ Add typed custom property and result queries to the C++ and Python FoMaC @@ -583,6 +586,7 @@ changelogs._ [#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 [#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 @@ -748,6 +752,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 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/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/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/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/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/plugins/qiskit/mqt_to_qiskit.py b/python/mqt/core/plugins/qiskit/mqt_to_qiskit.py index bc8db00cc6..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: 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/src/dd/Operations.cpp b/src/dd/Operations.cpp index 62345e2ad5..0c844b460a 100644 --- a/src/dd/Operations.cpp +++ b/src/dd/Operations.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,47 @@ namespace dd { +namespace { + +MatrixDD getRccxDD(Package& dd, const qc::Controls& controls, + const std::vector& targets) { + // Builds the RCCX DD by multiplying the relative-phase Toffoli + // decomposition. Could be improved to a direct bottom-up DD construction + // like makeGateDD / makeTwoQubitGateDD. + const auto q0 = targets[0]; + const auto q1 = targets[1]; + const auto q2 = targets[2]; + + const auto applySingle = [&](const qc::OpType type) { + return getStandardOperationDD(dd, type, {}, controls, {q2}); + }; + const auto applyControlledX = [&](const qc::Qubit ctrl) { + qc::Controls mergedControls = controls; + mergedControls.emplace(ctrl, qc::Control::Type::Pos); + return getStandardOperationDD(dd, qc::X, {}, mergedControls, {q2}); + }; + + const std::vector> steps = { + [&] { return applySingle(qc::H); }, + [&] { return applySingle(qc::T); }, + [&] { return applyControlledX(q1); }, + [&] { return applySingle(qc::Tdg); }, + [&] { return applyControlledX(q0); }, + [&] { return applySingle(qc::T); }, + [&] { return applyControlledX(q1); }, + [&] { return applySingle(qc::Tdg); }, + [&] { return applySingle(qc::H); }, + }; + + auto result = Package::makeIdent(); + for (const auto& step : steps) { + result = dd.multiply(step(), result); + } + return result; +} + +} // namespace + MatrixDD getStandardOperationDD(Package& dd, const qc::OpType type, const std::vector& params, const qc::Controls& controls, @@ -58,6 +100,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"); + } + if (type == qc::RCCX) { + return getRccxDD(dd, controls, targets); + } + } throw std::runtime_error("Unexpected operation type"); } @@ -85,6 +136,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/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/dd/Device.cpp b/src/qdmi/devices/dd/Device.cpp index 4fc828b3e4..f03f42f33a 100644 --- a/src/qdmi/devices/dd/Device.cpp +++ b/src/qdmi/devices/dd/Device.cpp @@ -114,6 +114,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{ 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/dd/test_dd_functionality.cpp b/test/dd/test_dd_functionality.cpp index ae3d8a0967..55c1580a4f 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); } diff --git a/test/dd/test_package.cpp b/test/dd/test_package.cpp index bcacd5bdea..109d949c1d 100644 --- a/test/dd/test_package.cpp +++ b/test/dd/test_package.cpp @@ -2498,6 +2498,36 @@ TEST(DDPackageTest, XXPlusYYGateDDConstruction) { } } +TEST(DDPackageTest, RCCXGateDDConstruction) { + constexpr auto nrQubits = 3U; + const auto dd = std::make_unique(nrQubits); + + const auto rccxGateDD = + getDD(qc::StandardOperation(qc::Targets{0, 1, 2}, qc::RCCX), *dd); + + auto gateDD = Package::makeIdent(); + const auto applySingle = [&](const qc::OpType type, const qc::Qubit target) { + gateDD = + dd->multiply(getDD(qc::StandardOperation(target, type), *dd), gateDD); + }; + const auto applyCx = [&](const qc::Qubit ctrl, const qc::Qubit target) { + gateDD = dd->multiply( + getDD(qc::StandardOperation(ctrl, target, qc::X), *dd), gateDD); + }; + + applySingle(qc::H, 2); + applySingle(qc::T, 2); + applyCx(1, 2); + applySingle(qc::Tdg, 2); + applyCx(0, 2); + applySingle(qc::T, 2); + applyCx(1, 2); + applySingle(qc::Tdg, 2); + applySingle(qc::H, 2); + + EXPECT_EQ(rccxGateDD, gateDD); +} + TEST(DDPackageTest, InnerProductTopNodeConjugation) { // Test comes from experimental results // 2 qubit state is rotated Rxx(-2) equivalent to 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/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/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/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 From 837f8d240ca7c3e325c3b094b48a9fc55b8407ef Mon Sep 17 00:00:00 2001 From: simon1hofmann <119581649+simon1hofmann@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:38:36 +0200 Subject: [PATCH 23/34] =?UTF-8?q?=E2=9C=A8=20Add=20bottom-up=20`makeThreeQ?= =?UTF-8?q?ubitGateDD`=20for=20RCCX=20(#1950)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 *AI text below* 🤖 ## Summary - Add `ThreeQubitGateMatrix` / `makeThreeQubitGateDD` for direct bottom-up DD construction of three-qubit gates (same style as `makeGateDD` / `makeTwoQubitGateDD`), starting with RCCX via `opToThreeQubitGateMatrix`. - Use MSB-among-operands bit order for three-qubit matrices (`targets[0]` = high bit), matching `makeTwoQubitGateDD` and QCO (`embed` / `CtrlOp`); RCCX is the `5/6/7` form (not the Qiskit little-endian `3/5/7` labeling). - Route RCCX through the new path in `getStandardOperationDD` instead of the multiply-based construction. - Share control-wrapping, range checks, and terminal materialization helpers across 1q/2q/3q gate DD builders. - Wire QCO `buildFunctionality` / `simulate` dense path through `makeThreeQubitGateDD` for 3-wire unitaries (plain copy; full-width `makeDDFromMatrix` still remaps QCO MSB → global DD LSB). - Consolidate RCCX DD construction tests (target permutations, overload wrappers, extra pos/neg controls) and cover partial-width 3q dense `inv` in QCO DD functionality tests. ## Checklist - [x] The pull request only contains commits that are focused and relevant to this change. - [x] I have added appropriate tests that cover the new/changed functionality. - [x] I have updated the documentation to reflect these changes. - [x] I have added entries to the changelog for any noteworthy additions, changes, fixes, or removals. - [ ] I have added migration instructions to the upgrade guide (if needed). - [ ] The changes follow the project's style guidelines and introduce no new warnings. - [ ] The changes are fully tested and pass the CI checks. - [x] I have reviewed my own code changes. **If PR contains AI-assisted content:** - [x] 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). - [x] Every agent-authored or agent-edited public text body begins with the visible disclosure `🤖 *AI text below* 🤖` (titles are exempt). - [ ] AI-assisted commits include an `Assisted-by: [Model Name] via [Tool Name]` footer. - [x] I confirm that I have personally reviewed and understood all AI-generated content, and accept full responsibility for it. --------- Signed-off-by: Lukas Burgholzer Co-authored-by: Cursor Co-authored-by: Lukas Burgholzer Backport-of: b43afafc515b4cd977b58438087c0ba688befd6e Assisted-by: GPT-5.6 via Codex --- CHANGELOG.md | 5 +- include/mqt-core/dd/DDDefinitions.hpp | 5 + include/mqt-core/dd/GateMatrixDefinitions.hpp | 9 + include/mqt-core/dd/Package.hpp | 43 +++ src/dd/GateMatrixDefinitions.cpp | 22 ++ src/dd/Operations.cpp | 48 +-- src/dd/Package.cpp | 353 ++++++++++++------ test/dd/test_dd_functionality.cpp | 4 + test/dd/test_package.cpp | 150 ++++++-- 9 files changed, 453 insertions(+), 186 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3502b2a1e5..5695ebbc52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,8 +13,8 @@ releases may include breaking changes. ### Added - ✨ Add native relative-phase CCX (`rccx`) support across the IR, DD package, - ZX diagrams, OpenQASM import/export, and Python/Qiskit bindings ([#1886]) - ([**@simon1hofmann**]) + 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], [#1952]) ([**@burgholzer**]) - ✨ Add typed custom property and result queries to the C++ and Python FoMaC @@ -583,6 +583,7 @@ changelogs._ [#1952]: https://github.com/munich-quantum-toolkit/core/pull/1952 +[#1950]: https://github.com/munich-quantum-toolkit/core/pull/1950 [#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 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/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 0c844b460a..76a682b989 100644 --- a/src/dd/Operations.cpp +++ b/src/dd/Operations.cpp @@ -30,7 +30,6 @@ #include #include #include -#include #include #include #include @@ -39,47 +38,6 @@ namespace dd { -namespace { - -MatrixDD getRccxDD(Package& dd, const qc::Controls& controls, - const std::vector& targets) { - // Builds the RCCX DD by multiplying the relative-phase Toffoli - // decomposition. Could be improved to a direct bottom-up DD construction - // like makeGateDD / makeTwoQubitGateDD. - const auto q0 = targets[0]; - const auto q1 = targets[1]; - const auto q2 = targets[2]; - - const auto applySingle = [&](const qc::OpType type) { - return getStandardOperationDD(dd, type, {}, controls, {q2}); - }; - const auto applyControlledX = [&](const qc::Qubit ctrl) { - qc::Controls mergedControls = controls; - mergedControls.emplace(ctrl, qc::Control::Type::Pos); - return getStandardOperationDD(dd, qc::X, {}, mergedControls, {q2}); - }; - - const std::vector> steps = { - [&] { return applySingle(qc::H); }, - [&] { return applySingle(qc::T); }, - [&] { return applyControlledX(q1); }, - [&] { return applySingle(qc::Tdg); }, - [&] { return applyControlledX(q0); }, - [&] { return applySingle(qc::T); }, - [&] { return applyControlledX(q1); }, - [&] { return applySingle(qc::Tdg); }, - [&] { return applySingle(qc::H); }, - }; - - auto result = Package::makeIdent(); - for (const auto& step : steps) { - result = dd.multiply(step(), result); - } - return result; -} - -} // namespace - MatrixDD getStandardOperationDD(Package& dd, const qc::OpType type, const std::vector& params, const qc::Controls& controls, @@ -105,9 +63,9 @@ MatrixDD getStandardOperationDD(Package& dd, const qc::OpType type, throw std::invalid_argument( "Expected three target qubits for three-qubit gate"); } - if (type == qc::RCCX) { - return getRccxDD(dd, controls, targets); - } + return dd.makeThreeQubitGateDD(opToThreeQubitGateMatrix(type, params), + controls, targets[0U], targets[1U], + targets[2U]); } throw std::runtime_error("Unexpected operation type"); } diff --git a/src/dd/Package.cpp b/src/dd/Package.cpp index b169f8c776..ec285db32c 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,134 @@ 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 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(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 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 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 +321,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 +355,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 +391,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(); diff --git a/test/dd/test_dd_functionality.cpp b/test/dd/test_dd_functionality.cpp index 55c1580a4f..0ed4c13b5e 100644 --- a/test/dd/test_dd_functionality.cpp +++ b/test/dd/test_dd_functionality.cpp @@ -290,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); @@ -298,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_package.cpp b/test/dd/test_package.cpp index 109d949c1d..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); @@ -2499,33 +2547,89 @@ TEST(DDPackageTest, XXPlusYYGateDDConstruction) { } TEST(DDPackageTest, RCCXGateDDConstruction) { - constexpr auto nrQubits = 3U; + constexpr auto nrQubits = 5U; const auto dd = std::make_unique(nrQubits); - - const auto rccxGateDD = - getDD(qc::StandardOperation(qc::Targets{0, 1, 2}, qc::RCCX), *dd); - - auto gateDD = Package::makeIdent(); - const auto applySingle = [&](const qc::OpType type, const qc::Qubit target) { - gateDD = - dd->multiply(getDD(qc::StandardOperation(target, type), *dd), gateDD); - }; - const auto applyCx = [&](const qc::Qubit ctrl, const qc::Qubit target) { + 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(ctrl, target, qc::X), *dd), gateDD); + 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); }; - applySingle(qc::H, 2); - applySingle(qc::T, 2); - applyCx(1, 2); - applySingle(qc::Tdg, 2); - applyCx(0, 2); - applySingle(qc::T, 2); - applyCx(1, 2); - applySingle(qc::Tdg, 2); - applySingle(qc::H, 2); - - EXPECT_EQ(rccxGateDD, 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) { From bd5c7dbbf1f661ffd05010c283a5a54a4c3eb1fc Mon Sep 17 00:00:00 2001 From: Roberto Turrado Camblor Date: Tue, 16 Jun 2026 20:48:17 +0200 Subject: [PATCH 24/34] Add QIR Program Format Support to the DDSIM QDMI Device (#1766) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First contribution to UnitaryHACK for me! 🎉 And man, I've enjoyed working on this one! 😀 > [!NOTE] > The current PR still does not cover all the functionality proposed in the #1695 issue. In particular, the support for QIR adaptive base modules and strings is not yet implemented. But the current solution is ready for review. There are two main pieces of work: 1. The introduction of an MQT Core QIR JIT library (`MQT::CoreQIRJIT`, `src/qir/jit`). This library contains almost all of the previous `Runner.cpp` code: LLVM JIT initialization, QIR runtime symbols registration, and program execution. Compared to that previous Runner code, it also lets you parse an IR directly from a string, and not only from a file. The main purpose of the QIR JIT library is to encapsulate the LLVM JIT code so that it can also be used from the QDMI device. The interface is a `qir::jit::Session` type that can be constructed from an input file or from a byte string (text for LLVM assembly or binary for LLVM bitcode), and then run with or without arguments (e.g., the Runner CLI can run a JIT program with arguments, and the QDMI device without arguments). 2. The job submission in `Device.cpp`. By using the MQT Core QIR JIT library, the device can just run a JIT program, get the results, and process them (filter zero and one addresses, sort the addresses, and transform the measurements into a bit string). The new functionality is tested via two tests in `results_sampling_test.cpp`, one that exercises the QIR base module path, and the other that exercises the QIR base string path. For this PR I have used Claude Opus 4.7, mainly from a CLion terminal, and for different purposes: from understanding existing code, to drafting code snippets, catching bugs and typos, running clang-format and clang-tidy and proposing fixes for clang-tidy warnings, reviewing wording in commit messages and docs... Fixes #1695 - [X] The pull request only contains commits that are focused and relevant to this change. - [X] I have added appropriate tests that cover the new/changed functionality. - [X] I have updated the documentation to reflect these changes. - [X] I have added entries to the changelog for any noteworthy additions, changes, fixes, or removals. - [X] I have added migration instructions to the upgrade guide (if needed). - [X] The changes follow the project's style guidelines and introduce no new warnings. - [X] The changes are fully tested and pass the CI checks. - [X] I have reviewed my own code changes. **If PR contains AI-assisted content:** - [X] 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). - [X] AI-assisted commits include an `Assisted-by: [Model Name] via [Tool Name]` footer. - [X] I confirm that I have personally reviewed and understood all AI-generated content, and accept full responsibility for it. --------- Signed-off-by: Roberto Turrado Camblor Co-authored-by: Yannick Stade <100073938+ystade@users.noreply.github.com> (cherry picked from commit 12cf546485cd608d1103d73c089bb8ba04839d07) Assisted-by: GPT-5.6 via Codex --- CHANGELOG.md | 4 + CMakeLists.txt | 4 + docs/qir/index.md | 6 + include/mqt-core/qdmi/devices/dd/Device.hpp | 54 ++- include/mqt-core/qir/jit/IRRewriter.hpp | 50 +++ include/mqt-core/qir/jit/Session.hpp | 126 ++++++ include/mqt-core/qir/runtime/Runtime.hpp | 68 ++- src/qdmi/devices/dd/CMakeLists.txt | 4 + src/qdmi/devices/dd/Device.cpp | 192 +++++++-- src/qir/CMakeLists.txt | 4 + src/qir/jit/CMakeLists.txt | 45 ++ src/qir/jit/IRRewriter.cpp | 60 +++ src/qir/jit/Session.cpp | 396 ++++++++++++++++++ src/qir/runner/CMakeLists.txt | 6 +- src/qir/runner/Runner.cpp | 285 +------------ src/qir/runtime/CMakeLists.txt | 2 +- src/qir/runtime/QIR.cpp | 6 +- src/qir/runtime/Runtime.cpp | 84 ++-- test/CMakeLists.txt | 2 +- test/circuits/BellPairAdaptive.ll | 61 +++ test/{qir => circuits}/BellPairDynamic.ll | 12 +- test/{qir => circuits}/BellPairStatic.ll | 4 +- test/{qir => circuits}/GHZ4Dynamic.ll | 0 test/qdmi/devices/dd/CMakeLists.txt | 9 + test/qdmi/devices/dd/helpers/circuits.hpp | 2 +- test/qdmi/devices/dd/helpers/test_utils.cpp | 14 +- test/qdmi/devices/dd/job_parameters_test.cpp | 14 +- .../qdmi/devices/dd/results_sampling_test.cpp | 136 +++++- .../devices/dd/results_statevector_test.cpp | 43 ++ test/qir/CMakeLists.txt | 2 + test/qir/helpers/CMakeLists.txt | 13 + test/qir/helpers/test_utils.cpp | 31 ++ test/qir/helpers/test_utils.hpp | 25 ++ test/qir/jit/CMakeLists.txt | 21 + test/qir/jit/test_ir_rewriter.cpp | 98 +++++ test/qir/jit/test_jit_session.cpp | 63 +++ test/qir/runner/CMakeLists.txt | 2 +- test/qir/runtime/CMakeLists.txt | 2 +- test/qir/runtime/test_qir_runtime.cpp | 44 +- 39 files changed, 1590 insertions(+), 404 deletions(-) create mode 100644 include/mqt-core/qir/jit/IRRewriter.hpp create mode 100644 include/mqt-core/qir/jit/Session.hpp create mode 100644 src/qir/jit/CMakeLists.txt create mode 100644 src/qir/jit/IRRewriter.cpp create mode 100644 src/qir/jit/Session.cpp create mode 100644 test/circuits/BellPairAdaptive.ll rename test/{qir => circuits}/BellPairDynamic.ll (85%) rename test/{qir => circuits}/BellPairStatic.ll (94%) rename test/{qir => circuits}/GHZ4Dynamic.ll (100%) create mode 100644 test/qir/helpers/CMakeLists.txt create mode 100644 test/qir/helpers/test_utils.cpp create mode 100644 test/qir/helpers/test_utils.hpp create mode 100644 test/qir/jit/CMakeLists.txt create mode 100644 test/qir/jit/test_ir_rewriter.cpp create mode 100644 test/qir/jit/test_jit_session.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 5695ebbc52..83b269819a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ releases may include breaking changes. libraries ([#1895]) ([**@burgholzer**]) - ✨ Add support for custom job parameters to C++ and Python FoMaC library ([#1887]) ([**@flowerthrower**], [**@burgholzer**]) +- ✨ Add QIR program format support to the DDSIM QDMI device ([#1766]) + ([**@rturrado**]) ### Changed @@ -596,6 +598,7 @@ changelogs._ [#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 @@ -771,6 +774,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..d74d42d26f 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,6 +121,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) diff --git a/docs/qir/index.md b/docs/qir/index.md index 1236354533..dd9336388f 100644 --- a/docs/qir/index.md +++ b/docs/qir/index.md @@ -44,3 +44,9 @@ The `mqt-core-qir-runner` can be used to execute a QIR file (typically with a This will simulate the circuit and print the measurement results to the console. The runner supports the QIR Base Profile. + +### 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. 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/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/Runtime.hpp b/include/mqt-core/qir/runtime/Runtime.hpp index 009b3d66dd..8459225d90 100644 --- a/include/mqt-core/qir/runtime/Runtime.hpp +++ b/include/mqt-core/qir/runtime/Runtime.hpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -49,6 +50,7 @@ struct ArrayImpl { }; namespace qir { + // Primary template template static constexpr bool IS_STD_ARRAY_V = false; // Specialization for std::array @@ -183,6 +185,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 +196,37 @@ 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; + } + }; + private: static constexpr uintptr_t MIN_DYN_QUBIT_ADDRESS = 0x10000; enum class AddressMode : uint8_t { UNKNOWN, DYNAMIC, STATIC }; @@ -203,13 +237,13 @@ class Runtime { std::vector qubitPermutation; static constexpr uintptr_t MIN_DYN_RESULT_ADDRESS = 0x10000; std::unordered_map rRegister; + std::string recordedOutputs; 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; Runtime(); explicit Runtime(uint64_t randomSeed); @@ -269,7 +303,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 +327,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 +340,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 +390,25 @@ class Runtime { auto deref(Result* result) -> ResultStruct&; auto rFree(Result* result) -> void; auto equal(Result* result1, Result* result2) -> bool; + + /// Append the value referenced by `result` to the recorded outputs bit + /// string in record order. + auto recordOutput(Result* result) -> void; + + /// @returns the outputs declared by the program as a bit string in record + /// order. + auto getRecordedOutputs() 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() -> std::ostream&; + auto setOstream(std::ostream& other) -> void; + auto resetOstream() -> void; }; + } // namespace qir diff --git a/src/qdmi/devices/dd/CMakeLists.txt b/src/qdmi/devices/dd/CMakeLists.txt index bfebf8a92a..ee4a2a3458 100644 --- a/src/qdmi/devices/dd/CMakeLists.txt +++ b/src/qdmi/devices/dd/CMakeLists.txt @@ -40,6 +40,10 @@ 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}" diff --git a/src/qdmi/devices/dd/Device.cpp b/src/qdmi/devices/dd/Device.cpp index f03f42f33a..37dee1301f 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; @@ -347,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; @@ -355,7 +376,21 @@ 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 auto* text = static_cast(value); + program_ = std::string(text, size - 1); + } 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: @@ -381,50 +416,135 @@ 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"); + for (size_t i = 0; i < numShots_; ++i) { + runtime.reset(); + if (const auto rc = jitSession.run(); rc != 0) { + throw std::runtime_error( + llvm::formatv("QIR program failed with error: {}", rc)); + } + // Update the measurement counts. + ++counts_[runtime.getRecordedOutputs()]; + } + }); +} +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/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..11c86ee446 --- /dev/null +++ b/src/qir/jit/Session.cpp @@ -0,0 +1,396 @@ +/* + * 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 +#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 { + +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__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); + }); +} + +#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); + + // 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..4577bbfa0a 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) # 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..4ef8fc200e 100644 --- a/src/qir/runner/Runner.cpp +++ b/src/qir/runner/Runner.cpp @@ -8,48 +8,16 @@ * Licensed under the MIT License */ -#include "qir/runtime/QIR.h" +#include "qir/jit/Session.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 +32,18 @@ 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)); + return jitSession.run(InputArgv, InputFile); + } 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..a52c5c1cb5 100644 --- a/src/qir/runtime/QIR.cpp +++ b/src/qir/runtime/QIR.cpp @@ -381,8 +381,10 @@ 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"; + auto& runtime = qir::Runtime::getInstance(); + runtime.recordOutput(result); + runtime.getOstream() << label << ": " + << (__quantum__rt__read_result(result) ? 1 : 0) << "\n"; } } // extern "C" diff --git a/src/qir/runtime/Runtime.cpp b/src/qir/runtime/Runtime.cpp index 0ca6a70c6b..16f53c1d88 100644 --- a/src/qir/runtime/Runtime.cpp +++ b/src/qir/runtime/Runtime.cpp @@ -21,11 +21,14 @@ #include #include #include +#include #include #include +#include #include #include #include +#include #include #include #include @@ -47,14 +50,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 +58,12 @@ auto Runtime::reset() -> void { rRegister.emplace(reinterpret_cast(RESULT_ONE_ADDRESS), ResultStruct{.refcount = 0, .r = true}); // NOLINTEND(performance-no-int-to-ptr) + recordedOutputs.clear(); + currentMaxQubitAddress = MIN_DYN_QUBIT_ADDRESS; + currentMaxQubitId = 0; + currentMaxResultAddress = MIN_DYN_RESULT_ADDRESS; + qState.reset(); + mt.seed(generateRandomSeed()); } Runtime::Runtime() : Runtime(generateRandomSeed()) {} @@ -70,9 +71,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 +83,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 +164,24 @@ auto Runtime::equal(Result* result1, Result* result2) -> bool { return deref(result1).r == deref(result2).r; } +auto Runtime::recordOutput(Result* result) -> void { + recordedOutputs.push_back(deref(result).r ? '1' : '0'); +} + +auto Runtime::getRecordedOutputs() const -> const std::string& { + return recordedOutputs; +} + +auto Runtime::takeState() -> QState { + QState ret = std::move(qState); + reset(); + return ret; +} + +auto Runtime::getOstream() -> std::ostream& { return *os; } + +auto Runtime::setOstream(std::ostream& other) -> void { os = &other; } + +auto Runtime::resetOstream() -> void { os = &std::cout; } + } // namespace qir diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2e3ab6a543..ca42cf59d3 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -14,9 +14,9 @@ add_subdirectory(dd) add_subdirectory(ir) add_subdirectory(na) add_subdirectory(zx) +add_subdirectory(qir) add_subdirectory(qdmi) add_subdirectory(fomac) -add_subdirectory(qir) # copy test circuits to build directory file(COPY ${PROJECT_SOURCE_DIR}/test/circuits DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/test/circuits/BellPairAdaptive.ll b/test/circuits/BellPairAdaptive.ll new file mode 100644 index 0000000000..441546ebce --- /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 1} +!1 = !{i32 7, !"qir_minor_version", i32 0} +!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 85% rename from test/qir/BellPairDynamic.ll rename to test/circuits/BellPairDynamic.ll index 15696c4816..1d80f9e034 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) @@ -48,5 +48,5 @@ attributes #1 = { "irreversible" } !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} +!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 94% rename from test/qir/BellPairStatic.ll rename to test/circuits/BellPairStatic.ll index eb98b571ce..972ab7261a 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 diff --git a/test/qir/GHZ4Dynamic.ll b/test/circuits/GHZ4Dynamic.ll similarity index 100% rename from test/qir/GHZ4Dynamic.ll rename to test/circuits/GHZ4Dynamic.ll 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/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..044b13ec1e 100644 --- a/test/qdmi/devices/dd/job_parameters_test.cpp +++ b/test/qdmi/devices/dd/job_parameters_test.cpp @@ -90,8 +90,16 @@ TEST(JobParameters, ProgramFormatSupport) { 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 +108,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..622e14b83b 100644 --- a/test/qdmi/devices/dd/results_sampling_test.cpp +++ b/test/qdmi/devices/dd/results_sampling_test.cpp @@ -18,27 +18,133 @@ #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 { + +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 SHOTS = 1024; + + 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, SHOTS), QDMI_SUCCESS); + EXPECT_EQ(qdmi_test::submitAndWait(j.job, 0), QDMI_SUCCESS); + return qdmi_test::getHistogram(j.job); + } + + 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 the number of SHOTS. + const auto sum = std::accumulate(vals.cbegin(), vals.cend(), size_t{0}); + EXPECT_EQ(sum, 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"; })); + } +}; - 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; +#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; } - EXPECT_EQ(sum, shots); +}; + +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"))); } +#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/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..5f1ff5feaf --- /dev/null +++ b/test/qir/jit/test_jit_session.cpp @@ -0,0 +1,63 @@ +/* + * 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 { qir::Runtime::getInstance().resetOstream(); } +}; + +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().getRecordedOutputs().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().getRecordedOutputs().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().getRecordedOutputs().empty()); +} + +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..7486517916 100644 --- a/test/qir/runtime/test_qir_runtime.cpp +++ b/test/qir/runtime/test_qir_runtime.cpp @@ -10,6 +10,7 @@ #include "ir/Definitions.hpp" #include "qir/runtime/QIR.h" +#include "qir/runtime/Runtime.hpp" #include #include @@ -18,9 +19,7 @@ #include #include #include -#include #include -#include #ifdef _WIN32 #define SYSTEM _wsystem @@ -34,10 +33,9 @@ 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(); } }; } // namespace @@ -263,7 +261,7 @@ TEST_F(QIRRuntimeTest, SwapGate) { __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"); + EXPECT_EQ(sink.str(), "r0: 0\nr1: 1\n"); } TEST_F(QIRRuntimeTest, CSwapGate) { @@ -366,7 +364,7 @@ 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")); + EXPECT_THAT(sink.str(), testing::AnyOf("r0: 0\nr1: 0\n", "r0: 1\nr1: 1\n")); } TEST_F(QIRRuntimeTest, BellPairDynamic) { @@ -384,7 +382,7 @@ 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")); + EXPECT_THAT(sink.str(), testing::AnyOf("r0: 0\nr1: 0\n", "r0: 1\nr1: 1\n")); __quantum__rt__result_update_reference_count(r0, -1); __quantum__rt__result_update_reference_count(r1, -1); } @@ -404,7 +402,7 @@ 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")); + EXPECT_THAT(sink.str(), testing::AnyOf("r0: 0\nr1: 0\n", "r0: 1\nr1: 1\n")); } TEST_F(QIRRuntimeTest, BellPairDynamicReverse) { @@ -422,7 +420,7 @@ 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")); + EXPECT_THAT(sink.str(), testing::AnyOf("r0: 0\nr1: 0\n", "r0: 1\nr1: 1\n")); __quantum__rt__result_update_reference_count(r0, -1); __quantum__rt__result_update_reference_count(r1, -1); } @@ -505,6 +503,30 @@ 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)); +} + namespace { class QIRFilesTest : public ::testing::TestWithParam {}; From d2c511c0b556025da3ae8d9d1e201cde22c97e4b Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 1 Jul 2026 00:23:41 +0200 Subject: [PATCH 25/34] =?UTF-8?q?=F0=9F=90=9B=20Fix=20QIR=20function=20nam?= =?UTF-8?q?es=20for=20adjoint=20gates=20(#1830)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description This PR fixes the QIR function names for adjoint gates. For example, `__quantum__qis__sdg__body` is replaced with `__quantum__qis__s__adj`. ## Checklist - [x] The pull request only contains commits that are focused and relevant to this change. - [x] ~~I have added appropriate tests that cover the new/changed functionality.~~ - [x] ~~I have updated the documentation to reflect these changes.~~ - [x] I have added entries to the changelog for any noteworthy additions, changes, fixes, or removals. - [x] ~~I have added migration instructions to the upgrade guide (if needed).~~ - [x] The changes follow the project's style guidelines and introduce no new warnings. - [x] The changes are fully tested and pass the CI checks. - [x] I have reviewed my own code changes. Backport-of: 334da06e6ad68686706234775788d51407c307ba Assisted-by: GPT-5.6 via Codex --- CHANGELOG.md | 5 +++++ include/mqt-core/qir/runtime/QIR.h | 8 ++++---- src/qir/jit/Session.cpp | 8 ++++---- src/qir/runtime/QIR.cpp | 8 ++++---- test/qir/runtime/test_qir_runtime.cpp | 8 ++++---- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83b269819a..2e4dbaa683 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,10 @@ releases may include breaking changes. - ⬆️ Raise the minimum supported QDMI version to 1.3.2 ([#1897]) ([**@burgholzer**]) +### Fixed + +- 🐛 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)._ @@ -595,6 +599,7 @@ changelogs._ [#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 diff --git a/include/mqt-core/qir/runtime/QIR.h b/include/mqt-core/qir/runtime/QIR.h index fd1463a0f8..2f68b8d883 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); diff --git a/src/qir/jit/Session.cpp b/src/qir/jit/Session.cpp index 11c86ee446..4eb3541956 100644 --- a/src/qir/jit/Session.cpp +++ b/src/qir/jit/Session.cpp @@ -164,13 +164,13 @@ void JitSession::registerRuntimeSymbols() { 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__s__adj); REGISTER_SYMBOL(__quantum__qis__sx__body); - REGISTER_SYMBOL(__quantum__qis__sxdg__body); + REGISTER_SYMBOL(__quantum__qis__sx__adj); REGISTER_SYMBOL(__quantum__qis__sqrtx__body); - REGISTER_SYMBOL(__quantum__qis__sqrtxdg__body); + REGISTER_SYMBOL(__quantum__qis__sqrtx__adj); REGISTER_SYMBOL(__quantum__qis__t__body); - REGISTER_SYMBOL(__quantum__qis__tdg__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); diff --git a/src/qir/runtime/QIR.cpp b/src/qir/runtime/QIR.cpp index a52c5c1cb5..2dbb818591 100644 --- a/src/qir/runtime/QIR.cpp +++ b/src/qir/runtime/QIR.cpp @@ -146,7 +146,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 +156,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 +166,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 +176,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); } diff --git a/test/qir/runtime/test_qir_runtime.cpp b/test/qir/runtime/test_qir_runtime.cpp index 7486517916..eb6be2d90c 100644 --- a/test/qir/runtime/test_qir_runtime.cpp +++ b/test/qir/runtime/test_qir_runtime.cpp @@ -73,7 +73,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) { @@ -85,7 +85,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) { @@ -97,7 +97,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) { @@ -109,7 +109,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) { From b03dcfe0be2c0228938c5e4ef9a4a0430f845a11 Mon Sep 17 00:00:00 2001 From: Roberto Turrado Camblor Date: Sun, 5 Jul 2026 13:24:12 +0200 Subject: [PATCH 26/34] =?UTF-8?q?=E2=9C=A8=20Add=20non-bit=20record=20outp?= =?UTF-8?q?ut=20functions=20to=20the=20QIR=20runtime=20(#1799)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit a8cb36f697b7edab51d5883a06559467c1757ee5) Assisted-by: GPT-5.6 via Codex --- include/mqt-core/qir/runtime/QIR.h | 25 +++++ include/mqt-core/qir/runtime/Runtime.hpp | 21 ++-- src/qdmi/devices/dd/Device.cpp | 2 +- src/qir/jit/Session.cpp | 5 + src/qir/runtime/QIR.cpp | 38 ++++++- src/qir/runtime/Runtime.cpp | 23 ++-- test/circuits/AdaptiveRecordOutputs.ll | 102 ++++++++++++++++++ test/circuits/BellPairAdaptive.ll | 4 +- test/circuits/BellPairDynamic.ll | 4 +- test/circuits/BellPairStatic.ll | 4 +- test/circuits/GHZ4Dynamic.ll | 4 +- .../qdmi/devices/dd/results_sampling_test.cpp | 38 ++++++- test/qir/jit/test_jit_session.cpp | 6 +- test/qir/runtime/test_qir_runtime.cpp | 48 +++++++++ 14 files changed, 290 insertions(+), 34 deletions(-) create mode 100644 test/circuits/AdaptiveRecordOutputs.ll diff --git a/include/mqt-core/qir/runtime/QIR.h b/include/mqt-core/qir/runtime/QIR.h index 2f68b8d883..89254efe07 100644 --- a/include/mqt-core/qir/runtime/QIR.h +++ b/include/mqt-core/qir/runtime/QIR.h @@ -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 8459225d90..2ca9d5639a 100644 --- a/include/mqt-core/qir/runtime/Runtime.hpp +++ b/include/mqt-core/qir/runtime/Runtime.hpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -237,7 +238,7 @@ class Runtime { std::vector qubitPermutation; static constexpr uintptr_t MIN_DYN_RESULT_ADDRESS = 0x10000; std::unordered_map rRegister; - std::string recordedOutputs; + std::string measurements; uintptr_t currentMaxQubitAddress; qc::Qubit currentMaxQubitId; uintptr_t currentMaxResultAddress; @@ -391,13 +392,17 @@ class Runtime { auto rFree(Result* result) -> void; auto equal(Result* result1, Result* result2) -> bool; - /// Append the value referenced by `result` to the recorded outputs bit - /// string in record order. - auto recordOutput(Result* result) -> void; + /// Append a measurement bit to the measurement string. + auto appendMeasurementBit(bool result) -> void; - /// @returns the outputs declared by the program as a bit string in record - /// order. - auto getRecordedOutputs() const -> const std::string&; + /// @returns the accumulated measurement string. + auto getMeasurements() const -> const std::string&; + + /// Emit `label:\n` to the output stream. + auto outputContainer(int64_t elementCount, const char* label) const -> void; + + /// Emit `label: valueStr\n` to the output stream. + auto outputValue(std::string_view valueStr, const char* label) const -> void; /// Move the quantum state out of the runtime. /// Then reset the runtime to a clean state ready for the next job. @@ -406,7 +411,7 @@ class Runtime { /// @returns the moved @c QState from the runtime. auto takeState() -> QState; - auto getOstream() -> std::ostream&; + auto getOstream() const -> std::ostream&; auto setOstream(std::ostream& other) -> void; auto resetOstream() -> void; }; diff --git a/src/qdmi/devices/dd/Device.cpp b/src/qdmi/devices/dd/Device.cpp index 37dee1301f..b26f1f2ff2 100644 --- a/src/qdmi/devices/dd/Device.cpp +++ b/src/qdmi/devices/dd/Device.cpp @@ -491,7 +491,7 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::submitQIRProgramSampling() llvm::formatv("QIR program failed with error: {}", rc)); } // Update the measurement counts. - ++counts_[runtime.getRecordedOutputs()]; + ++counts_[runtime.getMeasurements()]; } }); } diff --git a/src/qir/jit/Session.cpp b/src/qir/jit/Session.cpp index 4eb3541956..db04ffd48f 100644 --- a/src/qir/jit/Session.cpp +++ b/src/qir/jit/Session.cpp @@ -208,6 +208,11 @@ void JitSession::registerRuntimeSymbols() { 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); }); } diff --git a/src/qir/runtime/QIR.cpp b/src/qir/runtime/QIR.cpp index 2dbb818591..6f225d4f5f 100644 --- a/src/qir/runtime/QIR.cpp +++ b/src/qir/runtime/QIR.cpp @@ -15,7 +15,8 @@ #include #include -#include +#include +#include #include extern "C" { @@ -381,10 +382,39 @@ bool __quantum__rt__read_result(Result* result) { } void __quantum__rt__result_record_output(Result* result, const char* label) { + const bool bit = __quantum__rt__read_result(result); auto& runtime = qir::Runtime::getInstance(); - runtime.recordOutput(result); - runtime.getOstream() << label << ": " - << (__quantum__rt__read_result(result) ? 1 : 0) << "\n"; + runtime.outputValue(bit ? "1" : "0", label); + // Accumulate new measurement bit. + runtime.appendMeasurementBit(bit); +} + +void __quantum__rt__bool_record_output(bool value, const char* label) { + qir::Runtime::getInstance().outputValue(value ? "1" : "0", label); +} + +void __quantum__rt__int_record_output(int64_t value, const char* label) { + qir::Runtime::getInstance().outputValue(std::to_string(value), label); +} + +void __quantum__rt__float_record_output(double value, const char* label) { + // 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; + qir::Runtime::getInstance().outputValue(oss.str(), label); +} + +void __quantum__rt__tuple_record_output(int64_t elementCount, + const char* label) { + qir::Runtime::getInstance().outputContainer(elementCount, label); +} + +void __quantum__rt__array_record_output(int64_t size, const char* label) { + qir::Runtime::getInstance().outputContainer(size, label); } } // extern "C" diff --git a/src/qir/runtime/Runtime.cpp b/src/qir/runtime/Runtime.cpp index 16f53c1d88..6c998b75ac 100644 --- a/src/qir/runtime/Runtime.cpp +++ b/src/qir/runtime/Runtime.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -58,7 +59,7 @@ auto Runtime::reset() -> void { rRegister.emplace(reinterpret_cast(RESULT_ONE_ADDRESS), ResultStruct{.refcount = 0, .r = true}); // NOLINTEND(performance-no-int-to-ptr) - recordedOutputs.clear(); + measurements.clear(); currentMaxQubitAddress = MIN_DYN_QUBIT_ADDRESS; currentMaxQubitId = 0; currentMaxResultAddress = MIN_DYN_RESULT_ADDRESS; @@ -164,12 +165,12 @@ auto Runtime::equal(Result* result1, Result* result2) -> bool { return deref(result1).r == deref(result2).r; } -auto Runtime::recordOutput(Result* result) -> void { - recordedOutputs.push_back(deref(result).r ? '1' : '0'); +auto Runtime::appendMeasurementBit(bool result) -> void { + measurements.push_back(result ? '1' : '0'); } -auto Runtime::getRecordedOutputs() const -> const std::string& { - return recordedOutputs; +auto Runtime::getMeasurements() const -> const std::string& { + return measurements; } auto Runtime::takeState() -> QState { @@ -178,7 +179,17 @@ auto Runtime::takeState() -> QState { return ret; } -auto Runtime::getOstream() -> std::ostream& { return *os; } +auto Runtime::outputContainer(int64_t /* elementCount */, + const char* label) const -> void { + *os << (label != nullptr ? label : "") << ":\n"; +} + +auto Runtime::outputValue(std::string_view valueStr, const char* label) const + -> void { + *os << (label != nullptr ? label : "") << ": " << valueStr << "\n"; +} + +auto Runtime::getOstream() const -> std::ostream& { return *os; } auto Runtime::setOstream(std::ostream& other) -> void { os = &other; } 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 index 441546ebce..5bf8ce3b77 100644 --- a/test/circuits/BellPairAdaptive.ll +++ b/test/circuits/BellPairAdaptive.ll @@ -55,7 +55,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 true} !3 = !{i32 1, !"dynamic_result_management", i1 true} diff --git a/test/circuits/BellPairDynamic.ll b/test/circuits/BellPairDynamic.ll index 1d80f9e034..0b07f89b7d 100644 --- a/test/circuits/BellPairDynamic.ll +++ b/test/circuits/BellPairDynamic.ll @@ -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} +!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/BellPairStatic.ll b/test/circuits/BellPairStatic.ll index 972ab7261a..910eb1397e 100644 --- a/test/circuits/BellPairStatic.ll +++ b/test/circuits/BellPairStatic.ll @@ -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/circuits/GHZ4Dynamic.ll b/test/circuits/GHZ4Dynamic.ll index 6586c03588..f61c0041b5 100644 --- a/test/circuits/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/qdmi/devices/dd/results_sampling_test.cpp b/test/qdmi/devices/dd/results_sampling_test.cpp index 622e14b83b..150d295d4d 100644 --- a/test/qdmi/devices/dd/results_sampling_test.cpp +++ b/test/qdmi/devices/dd/results_sampling_test.cpp @@ -52,14 +52,15 @@ class HistogramTest : public ::testing::Test { #endif using Histogram = std::pair, std::vector>; - static constexpr size_t SHOTS = 1024; + 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, SHOTS), 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); } @@ -69,15 +70,32 @@ class HistogramTest : public ::testing::Test { // 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 the number of SHOTS. + // Values should sum up to NUM_SHOTS. const auto sum = std::accumulate(vals.cbegin(), vals.cend(), size_t{0}); - EXPECT_EQ(sum, SHOTS); + 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 @@ -144,6 +162,18 @@ TEST_F(QIRHistogramTestString, Adaptive) { 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) { diff --git a/test/qir/jit/test_jit_session.cpp b/test/qir/jit/test_jit_session.cpp index 5f1ff5feaf..3c614dd18f 100644 --- a/test/qir/jit/test_jit_session.cpp +++ b/test/qir/jit/test_jit_session.cpp @@ -37,7 +37,7 @@ 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().getRecordedOutputs().empty()); + EXPECT_FALSE(qir::Runtime::getInstance().getMeasurements().empty()); } TEST_F(JitSessionTest, SamplingRecordsOutputs) { @@ -45,14 +45,14 @@ TEST_F(JitSessionTest, SamplingRecordsOutputs) { // qir::Execution::Sampling is the default Execution mode const qir::JitSession session(path.string()); ASSERT_EQ(session.run(), 0); - EXPECT_FALSE(qir::Runtime::getInstance().getRecordedOutputs().empty()); + 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().getRecordedOutputs().empty()); + EXPECT_TRUE(qir::Runtime::getInstance().getMeasurements().empty()); } TEST(JitSessionErrors, MalformedIRThrows) { diff --git a/test/qir/runtime/test_qir_runtime.cpp b/test/qir/runtime/test_qir_runtime.cpp index eb6be2d90c..4a08b0030b 100644 --- a/test/qir/runtime/test_qir_runtime.cpp +++ b/test/qir/runtime/test_qir_runtime.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -527,6 +528,53 @@ TEST_F(QIRRuntimeTest, TakeStateReturnsStateAndResetsRuntime) { EXPECT_NO_THROW(__quantum__qis__h__body(q0)); } +TEST_F(QIRRuntimeTest, AdaptiveRecordOutputs) { + __quantum__rt__initialize(nullptr); + 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"); + + std::ostringstream expected; + expected << "outputs:\n" + << " measurements:\n" + << " m0: " << b0 << "\n" + << " m1: " << b1 << "\n" + << " m2: " << b2 << "\n" + << " hamming_weight: " << weight << "\n" + << " mean: " << mean << "\n"; + EXPECT_EQ(sink.str(), 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 {}; From b1708ef4eb7e19c03b0a1d7bd98816f3aa768638 Mon Sep 17 00:00:00 2001 From: Roberto Turrado Camblor Date: Thu, 9 Jul 2026 17:20:45 +0200 Subject: [PATCH 27/34] =?UTF-8?q?=E2=9C=A8=20Add=20QIR=20Output=20Schemas?= =?UTF-8?q?=20support=20to=20the=20QIR=20runtime=20(#1877)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR completes the QIR Output Schemas support in the runtime. PR #1799 added the non-bit `__quantum__rt__*_record_output` entry points in MQT Core's own `label: value` format. This follow-up switches the runtime to the spec-mandated tab-separated output records, adds `HEADER` and per-shot `START` / `METADATA` / `END` framing, and drives the Labeled / Ordered schema selection from the QIR program's `output_labeling_schema` function attribute. QIR Output Schemas spec: https://github.com/qir-alliance/qir-spec/tree/main/specification/output_schemas 1. **The QIR program drives the schema selection**. `JitSession::initialize` reads the `output_labeling_schema` attribute from the entry-point function and calls `Runtime::setOutputSchema`. The value `ordered` selects Ordered; any other value, a valueless attribute, or a missing attribute selects Labeled (the Runtime's chosen default; the spec does not define one). 2. **QIR record-output calls delegate to a per-type Runtime API**. `Runtime` exposes per-type output methods: `output{Result,Bool,Int,Float,Tuple,Array}`. Each `__quantum__rt__*_record_output` in `QIR.cpp` becomes a one-line delegation. The methods share a private `outputType` helper that emits `OUTPUT\t\t[\tlabel]\n` and honors the active schema (label column dropped in Ordered mode). 3. **Runtime exposes framing to clients**. `Runtime` exposes `outputProgramHeader`, `outputShotStart`, `outputShotEnd`. The `mqt-core-qir-runner` and the DDSIM QDMI Device emit the two `HEADER` records once before running, and bracket each shot with `START` / `END`. `outputShotStart` also emits `METADATA\toutput_labeling_schema\t\n` so the output is self-describing. Tests: `test_qir_runtime.cpp` covers the framing shape, per-type line shape, and Labeled vs. Ordered branches. `test_jit_session.cpp` covers the attribute-driven schema selection for `ordered`, `labeled`, and absent-attribute cases. Fixes #1798 - [x] The pull request only contains commits that are focused and relevant to this change. - [x] I have added appropriate tests that cover the new/changed functionality. - [x] I have updated the documentation to reflect these changes. - [x] I have added entries to the changelog for any noteworthy additions, changes, fixes, or removals. - [ ] I have added migration instructions to the upgrade guide (if needed). - [x] The changes follow the project's style guidelines and introduce no new warnings. - [ ] The changes are fully tested and pass the CI checks. - [x] I have reviewed my own code changes. **If PR contains AI-assisted content:** - [x] 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). - [x] AI-assisted commits include an `Assisted-by: [Model Name] via [Tool Name]` footer. - [x] I confirm that I have personally reviewed and understood all AI-generated content, and accept full responsibility for it. --------- Signed-off-by: rturrado Signed-off-by: Roberto Turrado Camblor (cherry picked from commit afd8180f461eed5e5ccd65bee5984e6dfee29c06) Assisted-by: GPT-5.6 via Codex --- CHANGELOG.md | 3 + docs/qir/index.md | 12 +- include/mqt-core/qir/runtime/Runtime.hpp | 58 +++++++-- src/qdmi/devices/dd/Device.cpp | 6 +- src/qir/jit/Session.cpp | 29 +++++ src/qir/runner/CMakeLists.txt | 2 +- src/qir/runner/Runner.cpp | 8 +- src/qir/runtime/QIR.cpp | 21 +--- src/qir/runtime/Runtime.cpp | 72 +++++++++-- test/qir/jit/test_jit_session.cpp | 39 +++++- test/qir/runtime/test_qir_runtime.cpp | 153 ++++++++++++++++++++--- 11 files changed, 347 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e4dbaa683..1a99900fa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ releases may include breaking changes. 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**]) @@ -594,6 +596,7 @@ changelogs._ [#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 diff --git a/docs/qir/index.md b/docs/qir/index.md index dd9336388f..4b082af8a2 100644 --- a/docs/qir/index.md +++ b/docs/qir/index.md @@ -42,8 +42,16 @@ 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 diff --git a/include/mqt-core/qir/runtime/Runtime.hpp b/include/mqt-core/qir/runtime/Runtime.hpp index 2ca9d5639a..756ff1fe1d 100644 --- a/include/mqt-core/qir/runtime/Runtime.hpp +++ b/include/mqt-core/qir/runtime/Runtime.hpp @@ -214,7 +214,7 @@ class Runtime { /// 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 + /// If @c dd was moved out (e.g., by @ref Runtime::takeState), a new package /// is allocated. auto reset() -> void { if (dd) { @@ -228,6 +228,8 @@ class Runtime { } }; + 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 }; @@ -245,6 +247,9 @@ class Runtime { 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); @@ -290,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(); @@ -398,12 +410,6 @@ class Runtime { /// @returns the accumulated measurement string. auto getMeasurements() const -> const std::string&; - /// Emit `label:\n` to the output stream. - auto outputContainer(int64_t elementCount, const char* label) const -> void; - - /// Emit `label: valueStr\n` to the output stream. - auto outputValue(std::string_view valueStr, const char* label) const -> void; - /// 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 @@ -414,6 +420,44 @@ class Runtime { 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/src/qdmi/devices/dd/Device.cpp b/src/qdmi/devices/dd/Device.cpp index b26f1f2ff2..cb45a24de3 100644 --- a/src/qdmi/devices/dd/Device.cpp +++ b/src/qdmi/devices/dd/Device.cpp @@ -484,9 +484,13 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::submitQIRProgramSampling() }, program_); auto jitSession = qir::JitSession(irBytes, "QDMI job"); + runtime.outputProgramHeader(); for (size_t i = 0; i < numShots_; ++i) { runtime.reset(); - if (const auto rc = jitSession.run(); rc != 0) { + runtime.outputShotStart(); + const auto rc = jitSession.run(); + runtime.outputShotEnd(); + if (rc != 0) { throw std::runtime_error( llvm::formatv("QIR program failed with error: {}", rc)); } diff --git a/src/qir/jit/Session.cpp b/src/qir/jit/Session.cpp index db04ffd48f..947f3758ea 100644 --- a/src/qir/jit/Session.cpp +++ b/src/qir/jit/Session.cpp @@ -12,6 +12,7 @@ #include "qir/jit/IRRewriter.hpp" #include "qir/runtime/QIR.h" +#include "qir/runtime/Runtime.hpp" #include #include @@ -44,6 +45,7 @@ #include #include +#include #include #include #include @@ -57,6 +59,28 @@ 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() { @@ -239,6 +263,11 @@ void JitSession::initialize( } 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) { diff --git a/src/qir/runner/CMakeLists.txt b/src/qir/runner/CMakeLists.txt index 4577bbfa0a..f52b85bbfb 100644 --- a/src/qir/runner/CMakeLists.txt +++ b/src/qir/runner/CMakeLists.txt @@ -12,7 +12,7 @@ if(NOT TARGET ${TARGET_NAME}) add_llvm_tool(${TARGET_NAME} Runner.cpp DEPENDS intrinsics_gen EXPORT_SYMBOLS) # Add link libraries - target_link_libraries(${TARGET_NAME} PRIVATE MQT::CoreQIRJIT) + 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 4ef8fc200e..b6626cec41 100644 --- a/src/qir/runner/Runner.cpp +++ b/src/qir/runner/Runner.cpp @@ -9,6 +9,7 @@ */ #include "qir/jit/Session.hpp" +#include "qir/runtime/Runtime.hpp" #include #include @@ -42,7 +43,12 @@ auto main(int argc, char* argv[]) -> int { try { auto jitSession = qir::JitSession(llvm::StringRef(InputFile)); - return jitSession.run(InputArgv, 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())); } diff --git a/src/qir/runtime/QIR.cpp b/src/qir/runtime/QIR.cpp index 6f225d4f5f..1d756a063d 100644 --- a/src/qir/runtime/QIR.cpp +++ b/src/qir/runtime/QIR.cpp @@ -15,8 +15,6 @@ #include #include -#include -#include #include extern "C" { @@ -384,37 +382,30 @@ bool __quantum__rt__read_result(Result* result) { void __quantum__rt__result_record_output(Result* result, const char* label) { const bool bit = __quantum__rt__read_result(result); auto& runtime = qir::Runtime::getInstance(); - runtime.outputValue(bit ? "1" : "0", label); + 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().outputValue(value ? "1" : "0", label); + qir::Runtime::getInstance().outputBool(value, label); } void __quantum__rt__int_record_output(int64_t value, const char* label) { - qir::Runtime::getInstance().outputValue(std::to_string(value), label); + qir::Runtime::getInstance().outputInt(value, label); } void __quantum__rt__float_record_output(double value, const char* label) { - // 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; - qir::Runtime::getInstance().outputValue(oss.str(), label); + qir::Runtime::getInstance().outputFloat(value, label); } void __quantum__rt__tuple_record_output(int64_t elementCount, const char* label) { - qir::Runtime::getInstance().outputContainer(elementCount, label); + qir::Runtime::getInstance().outputTuple(elementCount, label); } void __quantum__rt__array_record_output(int64_t size, const char* label) { - qir::Runtime::getInstance().outputContainer(size, label); + qir::Runtime::getInstance().outputArray(size, label); } } // extern "C" diff --git a/src/qir/runtime/Runtime.cpp b/src/qir/runtime/Runtime.cpp index 6c998b75ac..8b2555b2a9 100644 --- a/src/qir/runtime/Runtime.cpp +++ b/src/qir/runtime/Runtime.cpp @@ -179,20 +179,76 @@ auto Runtime::takeState() -> QState { return ret; } -auto Runtime::outputContainer(int64_t /* elementCount */, - const char* label) const -> void { - *os << (label != nullptr ? label : "") << ":\n"; +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::outputValue(std::string_view valueStr, const char* label) const +auto Runtime::outputTuple(int64_t elementCount, const char* label) const -> void { - *os << (label != nullptr ? label : "") << ": " << valueStr << "\n"; + outputType("TUPLE", std::to_string(elementCount), label); } -auto Runtime::getOstream() const -> std::ostream& { return *os; } +auto Runtime::outputArray(int64_t elementCount, const char* label) const + -> void { + outputType("ARRAY", std::to_string(elementCount), label); +} -auto Runtime::setOstream(std::ostream& other) -> void { os = &other; } +auto Runtime::outputProgramHeader() const -> void { + *os << "HEADER\tschema_id\t" << outputSchema << "\n"; + *os << "HEADER\tschema_version\t2.1\n"; +} -auto Runtime::resetOstream() -> void { os = &std::cout; } +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/test/qir/jit/test_jit_session.cpp b/test/qir/jit/test_jit_session.cpp index 3c614dd18f..8a7a362dda 100644 --- a/test/qir/jit/test_jit_session.cpp +++ b/test/qir/jit/test_jit_session.cpp @@ -30,7 +30,11 @@ class JitSessionTest : public testing::Test { runtime.reset(); runtime.setOstream(sink); } - void TearDown() override { qir::Runtime::getInstance().resetOstream(); } + void TearDown() override { + auto& runtime = qir::Runtime::getInstance(); + runtime.resetOstream(); + runtime.setOutputSchema(qir::Runtime::OutputSchema::Labeled); + } }; TEST_F(JitSessionTest, LoadModuleFromMemory) { @@ -55,6 +59,39 @@ TEST_F(JitSessionTest, StateExtractionLeavesNoRecordedOutputs) { 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); diff --git a/test/qir/runtime/test_qir_runtime.cpp b/test/qir/runtime/test_qir_runtime.cpp index 4a08b0030b..2987e0870e 100644 --- a/test/qir/runtime/test_qir_runtime.cpp +++ b/test/qir/runtime/test_qir_runtime.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #ifdef _WIN32 @@ -36,11 +37,89 @@ class QIRRuntimeTest : public testing::Test { protected: std::ostringstream sink; void SetUp() override { Runtime::getInstance().setOstream(sink); } - void TearDown() override { Runtime::getInstance().resetOstream(); } + 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); @@ -256,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(sink.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) { @@ -356,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); @@ -365,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(sink.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); @@ -383,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(sink.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); } @@ -394,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); @@ -403,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(sink.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); @@ -421,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(sink.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); } @@ -530,6 +639,8 @@ TEST_F(QIRRuntimeTest, TakeStateReturnsStateAndResetsRuntime) { 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(); @@ -553,22 +664,24 @@ TEST_F(QIRRuntimeTest, AdaptiveRecordOutputs) { // 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"); + __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 << "outputs:\n" - << " measurements:\n" - << " m0: " << b0 << "\n" - << " m1: " << b1 << "\n" - << " m2: " << b2 << "\n" - << " hamming_weight: " << weight << "\n" - << " mean: " << mean << "\n"; - EXPECT_EQ(sink.str(), expected.str()); + 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); From 1d60122dc93c4442165dfe52b767e47b756c635d Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Wed, 29 Jul 2026 21:36:11 +0200 Subject: [PATCH 28/34] =?UTF-8?q?=E2=9C=A8=20Add=20configurable=20QDMI=20d?= =?UTF-8?q?evice=20discovery=20(#1912)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 *AI text below* 🤖 This PR extracts the additive, MQT Core 3.x-compatible QDMI configuration foundation from #1901. It adds stable device identities and configurable discovery while retaining the current client interface, `qdmi::Driver`, FoMaC, Qiskit, and neutral-atom APIs. The breaking object-model refactor remains in #1901 and will be rebased onto this foundation. - Discover versioned definitions from `qdmi.json`, `[tool.qdmi]`, environment configuration, packaged fragments, and C++ runtime overrides. - Merge definitions by stable device ID with field-wise precedence, explicit disabled-ID reservation, strict validation, relative-path resolution, and explicit or isolated configuration modes. - Register and open devices by stable ID through C++, FoMaC, and Python. Fresh opens create independent sessions and apply per-call overrides without changing the configured client catalog. - Generate relocatable fragments for built-in devices and export neutral `QDMI_DEVICE_ID`, `QDMI_DEVICE_PREFIX`, and `QDMI_MANIFEST_NAME` CMake target metadata. Installed consumers can copy selected device runtimes without project-specific loader code. - Vendor the current toml++ single header used by the native TOML parser. - Document administration, project, environment, C++, Python, relocation, and static-consumer workflows. The public QDMI client C interface, `MQT::CoreQDMIDriver`, the FoMaC object model, and the existing Qiskit and neutral-atom APIs remain available. The unstable C++ `Driver::addDynamicDeviceLibrary` and Python `add_dynamic_device_library` entry points are replaced by `DeviceDefinition` registration and stable-ID opening; `UPGRADING.md` documents that migration. Configuration discovery and registration do not load device code. A library is loaded when a stable ID is explicitly opened or when the configured client catalog is first materialized. Separately installed device wheels are not scanned automatically; they can register a fallback definition or have their runtime and generated fragment colocated by the consuming build. - Release configure and complete 471-step build with LLVM/MLIR 22.1.3 - QDMI Driver suite: 109 passed - Registry suite: 12 passed - FoMaC suite: 174 passed - Neutral-atom FoMaC suite: 2 passed - Imported-device consumer tests: 2 passed - DDSIM suite: 45 passed - QIR JIT, runtime, and runner suites: 9, 63, and 5 passed - Python stub generation through the aggregate wheel target - `uvx nox -s lint` - `git diff --check` Fixes #1362 Fixes #1363 (cherry picked from commit 72c5f5f03d4974baf9d62c871f7b9d97e8450f14) Assisted-by: GPT-5.6 via Codex --- .github/workflows/ci.yml | 3 + .pre-commit-config.yaml | 3 + CHANGELOG.md | 10 + CMakeLists.txt | 1 + UPGRADING.md | 37 + bindings/fomac/fomac.cpp | 226 +- cmake/AddMQTQDMIDevice.cmake | 145 + cmake/mqt-core-config.cmake.in | 1 + docs/qdmi/configuration.md | 174 + docs/qdmi/driver.md | 4 +- docs/qdmi/index.md | 1 + include/mqt-core/fomac/FoMaC.hpp | 120 +- include/mqt-core/qdmi/driver/Driver.hpp | 114 +- python/mqt/core/fomac.pyi | 141 +- src/CMakeLists.txt | 1 + src/fomac/FoMaC.cpp | 54 +- src/qdmi/devices/dd/CMakeLists.txt | 6 +- src/qdmi/devices/na/CMakeLists.txt | 6 +- src/qdmi/devices/sc/CMakeLists.txt | 6 +- src/qdmi/driver/CMakeLists.txt | 43 +- src/qdmi/driver/DeviceRegistry.cpp | 523 + src/qdmi/driver/DeviceRegistry.hpp | 38 + src/qdmi/driver/Driver.cpp | 251 +- test/fomac/CMakeLists.txt | 20 +- test/fomac/test_fomac.cpp | 30 +- test/na/fomac/CMakeLists.txt | 20 +- test/python/fomac/test_fomac.py | 66 +- test/qdmi/CMakeLists.txt | 1 + test/qdmi/driver/CMakeLists.txt | 77 +- .../driver/imported_device/CMakeLists.txt | 28 + test/qdmi/driver/metadata_device.cpp | 11 + test/qdmi/driver/session_device.cpp | 261 + test/qdmi/driver/test_driver.cpp | 338 +- test/qdmi/registry/CMakeLists.txt | 15 + test/qdmi/registry/test_device_registry.cpp | 454 + vendor/tomlplusplus/README.md | 10 + vendor/tomlplusplus/toml.hpp | 17899 ++++++++++++++++ 37 files changed, 20780 insertions(+), 358 deletions(-) create mode 100644 cmake/AddMQTQDMIDevice.cmake create mode 100644 docs/qdmi/configuration.md create mode 100644 src/qdmi/driver/DeviceRegistry.cpp create mode 100644 src/qdmi/driver/DeviceRegistry.hpp create mode 100644 test/qdmi/driver/imported_device/CMakeLists.txt create mode 100644 test/qdmi/driver/metadata_device.cpp create mode 100644 test/qdmi/driver/session_device.cpp create mode 100644 test/qdmi/registry/CMakeLists.txt create mode 100644 test/qdmi/registry/test_device_registry.cpp create mode 100644 vendor/tomlplusplus/README.md create mode 100644 vendor/tomlplusplus/toml.hpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 667b698e91..539e9ce41d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -167,6 +167,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 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 05b50d232d..bf4f87d766 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -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 @@ -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) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a99900fa4..844ee2779f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ releases may include breaking changes. ### Added +- ✨ 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**]) @@ -30,6 +34,11 @@ releases may include breaking changes. - ⬆️ 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 @@ -592,6 +601,7 @@ changelogs._ [#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 diff --git a/CMakeLists.txt b/CMakeLists.txt index d74d42d26f..254229afb6 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) diff --git a/UPGRADING.md b/UPGRADING.md index 714e0f93e7..406164f5bd 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -6,6 +6,43 @@ of changes including minor and patch releases, please refer to the ## [Unreleased] +### 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..00b93043c7 100644 --- a/bindings/fomac/fomac.cpp +++ b/bindings/fomac/fomac.cpp @@ -14,16 +14,18 @@ #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 @@ -70,6 +72,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 +101,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, @@ -570,80 +594,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"); -Examples: - Load a device library with configuration: + 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. - >>> 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" - ... ) +Every call creates a fresh device session while keeping the stable registration +unchanged. Opening the device loads trusted native device code. - Now the device can be used directly: +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. - >>> from mqt.core.plugins.qiskit import QDMIBackend - >>> backend = QDMIBackend(device=device))pb"); +Returns: + Device: The opened device, ready for direct backend construction. + +Raises: + IndexError: If the ID is not registered. + RuntimeError: If the device library cannot be loaded or initialized.)pb"); } } // namespace mqt 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/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/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..bb7f934dc0 100644 --- a/docs/qdmi/driver.md +++ b/docs/qdmi/driver.md @@ -18,7 +18,9 @@ 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: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). ## 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/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index 472d6bda05..f57ba2d8b6 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,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -308,7 +310,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 +353,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 +413,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,8 +506,8 @@ 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))); @@ -517,7 +529,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 +547,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 +558,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 +577,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 +601,7 @@ class Device { const CustomJobParameter& value); /// @brief The underlying device pointer. - QDMI_Device device_; + std::shared_ptr device_; friend class Session; }; @@ -598,6 +618,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(); } @@ -716,8 +740,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, 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. */ - explicit Job(QDMI_Job job) : job_(job, QDMI_job_free) {} + std::shared_ptr device_; std::unique_ptr job_{ nullptr, QDMI_job_free}; @@ -797,8 +829,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 +841,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 +862,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 +883,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 +997,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 +1009,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 +1030,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 +1040,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 +1059,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 +1079,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/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/python/mqt/core/fomac.pyi b/python/mqt/core/fomac.pyi index 3e8bd79642..e6494a113d 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, @@ -511,13 +513,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 +608,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/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/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index 4b42160f9b..06e9ad7d55 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 @@ -28,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -134,10 +136,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 +200,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 +228,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 +246,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 +292,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 +304,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 +313,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; } @@ -322,8 +327,9 @@ Job Device::submitJob(const std::string& program, const std::optional& custom4, const std::optional& custom5) const { 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, @@ -402,6 +408,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, @@ -685,6 +701,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 +741,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 +778,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/qdmi/devices/dd/CMakeLists.txt b/src/qdmi/devices/dd/CMakeLists.txt index ee4a2a3458..966028aaba 100644 --- a/src/qdmi/devices/dd/CMakeLists.txt +++ b/src/qdmi/devices/dd/CMakeLists.txt @@ -45,11 +45,7 @@ if(NOT TARGET ${TARGET_NAME}) 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/na/CMakeLists.txt b/src/qdmi/devices/na/CMakeLists.txt index fb77c9164c..6e10993b4b 100644 --- a/src/qdmi/devices/na/CMakeLists.txt +++ b/src/qdmi/devices/na/CMakeLists.txt @@ -123,11 +123,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..c2981e9411 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,22 @@ 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 $ - $) + mqt_copy_qdmi_runtime(${TARGET_NAME} ${QDMI_DEVICE_TARGETS}) else() - target_link_options( - ${TARGET_NAME} - INTERFACE - $> - $> - $>) + add_dependencies(${TARGET_NAME} ${QDMI_DEVICE_TARGETS}) + foreach(device IN LISTS QDMI_DEVICE_TARGETS) + target_link_options(${TARGET_NAME} INTERFACE + $>) + endforeach() 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..9bc695e16b 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/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 d8c28f362a..fc8b218a6b 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -1099,7 +1099,7 @@ TEST(AuthenticationTest, SessionConstructionWithAuthFile) { } SessionConfig config2; - config2.authFile = tmpPath.string(); + config2.authFile = tmpPath; EXPECT_NO_THROW({ const Session session(config2); }); // Clean up @@ -1229,6 +1229,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/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 ab1acc650d..1897066803 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] @@ -743,9 +753,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) @@ -869,7 +881,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/qdmi/CMakeLists.txt b/test/qdmi/CMakeLists.txt index 595fc88d15..a1af9424f3 100644 --- a/test/qdmi/CMakeLists.txt +++ b/test/qdmi/CMakeLists.txt @@ -8,3 +8,4 @@ add_subdirectory(devices) add_subdirectory(driver) +add_subdirectory(registry) 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..ba3c075d69 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,19 @@ 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() { + static_cast(qdmi::Driver::get().registerDeviceIfAbsent( + {.id = "test.session-overrides", + .library = MQT_CORE_QDMI_SESSION_DEVICE, + .prefix = "TEST_SESSION", + .session = {.baseUrl = "registered-base", + .token = "registered-token", + .custom1 = "registered-custom"}})); +} +} // namespace + // Instantiate the test suite with different parameters INSTANTIATE_TEST_SUITE_P( // Custom instantiation name @@ -804,26 +853,241 @@ 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}, 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}; + 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}, 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}; + 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}; + 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"}); + 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}); + 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 +1102,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 +1150,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 +1170,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 +1186,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 +1194,35 @@ 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(); + auto* const first = + openTestDevice(library, prefix, {.custom1 = "first-session"}); + const auto equivalentLibrary = std::filesystem::path(library).parent_path() / + "." / + std::filesystem::path(library).filename(); + auto* const second = openTestDevice(equivalentLibrary.string(), prefix, + {.custom1 = "second-session"}); + + 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..25e4f84e34 --- /dev/null +++ b/test/qdmi/registry/CMakeLists.txt @@ -0,0 +1,15 @@ +# 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) + mqt_copy_qdmi_runtime(${TARGET_NAME}) +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..1a1a069558 --- /dev/null +++ b/test/qdmi/registry/test_device_registry.cpp @@ -0,0 +1,454 @@ +/* + * 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; + 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"); +} + +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)); + } +} + +TEST(DeviceRegistry, ReadsProjectConfigurationFromPyprojectToml) { + const TemporaryDirectory directory; + 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; + directory.write("pyproject.toml", R"( + [tool.qdmi] + devices = [{ id = "toml", library = "toml.so", prefix = "TOML" }] + )"); + 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; + directory.write("user/mqt-core/qdmi.json", R"({ + "schema-version": 1, + "qdmi": {"devices": [{ + "id": "layered", "library": "user.so", "prefix": "USER", + "session": {"custom1": "user-default"} + }]} + })"); + 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); + } + { + 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/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