Skip to content

♻️ Replace pybind11 with nanobind#323

Merged
denialhaag merged 7 commits intomainfrom
nanobind
Jan 14, 2026
Merged

♻️ Replace pybind11 with nanobind#323
denialhaag merged 7 commits intomainfrom
nanobind

Conversation

@denialhaag
Copy link
Member

Description

This PR fully replaces pybind11 with nanobind. This change will allow us to ship Stable ABI wheels, saving us PyPI space.

Checklist:

  • The pull request only contains commits that are focused and relevant to this change.
  • I have added appropriate tests that cover the new/changed functionality.
  • I have updated the documentation to reflect these changes.
  • The changes follow the project's style guidelines and introduce no new warnings.
  • The changes are fully tested and pass the CI checks.
  • I have reviewed my own code changes.

@denialhaag denialhaag self-assigned this Jan 14, 2026
@denialhaag denialhaag added minor python Pull requests that update python code c++ labels Jan 14, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 14, 2026

Warning

Rate limit exceeded

@denialhaag has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 18 minutes and 30 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between edb00b0 and c1a326e.

📒 Files selected for processing (2)
  • bindings/bindings.cpp
  • python/mqt/qusat/pyqusat.pyi
📝 Walkthrough

Walkthrough

This PR migrates the project from pybind11 to nanobind for C++/Python bindings and updates CI, build, packaging, CMake, stub generation, and related tooling; it also bumps mqt.core to ~3.4.0 and adds CPython 3.12 stable-ABI wheel settings.

Changes

Cohort / File(s) Summary
CI & Workflows
.github/workflows/ci.yml
cpp-linter now installs nanobind==2.10.2; Python linter invocations enable check-stubs: true.
Pre-commit
.pre-commit-config.yaml
Updated disallow-caps regex from PyBind to Nanobind.
Top-level CMake
CMakeLists.txt
BINDINGS declared CACHE INTERNAL; added Python_FIND_FRAMEWORK CACHE string; extended find_package(Python ...) to include ${SKBUILD_SABI_COMPONENT}.
External deps
cmake/ExternalDependencies.cmake
Replaced pybind11/pybind11_json handling with nanobind discovery/FetchContent; bumped MQT Core min/version to 3.4.0.
Bindings CMake & install
bindings/CMakeLists.txt
Switched to add_mqt_python_binding_nanobind, added MODULE_NAME = pyqusat, removed pybind11_json from link libs, added install rule for bindings target.
Bindings implementation
bindings/bindings.cpp
Replaced pybind11 with nanobind (NB_MODULE); changed check_equivalence binding to a nanobind lambda that calls checkEquivalence(...).dump() and returns json.loads(...); updated includes, namespaces, and prints.
Packaging / build config
pyproject.toml
Replaced pybind11 with nanobind>=2.10.2; bumped mqt.core to ~3.4.0; added wheel.py-api = "cp312" and cp312-specific cibuildwheel overrides and repair-audit commands; updated wheel-repair exclusions.
Stubs & tooling
noxfile.py, python/mqt/qusat/pyqusat.pyi
Added stubs nox session that runs nanobind.stubgen and formatting checks; updated .pyi to reference mqt.core.ir.QuantumComputation and added `inputs: Sequence[str]

Sequence Diagram(s)

sequenceDiagram
  participant Py as Python caller
  participant NB as nanobind module (pyqusat)
  participant Cpp as C++ core (checkEquivalence)
  participant JSON as Python json module

  Py->>NB: call check_equivalence(circ1, circ2, inputs)
  NB->>Cpp: checkEquivalence(circ1, circ2, inputs) -> result_obj
  Cpp-->>NB: result_obj.dump() (JSON string)
  NB->>JSON: json.loads(dumped_json)
  JSON-->>NB: dict result
  NB-->>Py: return dict
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped from PyBind to Nano with delight,

Swapped macros and CMake in the moonlight.
CI hums, stubs appear in a flutter,
JSON returns — neat as a buttered cutter.
Carrots for bindings, onward we write!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Replace pybind11 with nanobind' is clear, concise, and directly summarizes the main objective of the PR.
Description check ✅ Passed The description includes a clear summary of the change with motivation (Stable ABI wheels and PyPI space savings) and the completed checklist items confirm project requirements are met.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@bindings/bindings.cpp`:
- Around line 49-59: The docstring passed to m.def for "check_equivalence"
concatenates two adjacent string literals without a separating space, producing
"inputs.If"; update the docstring in the lambda registration
(m.def("check_equivalence", ...)) to insert a space or newline between the two
literal fragments so the rendered help text reads "...inputs. If no inputs..."
and maintains proper spacing.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 513db9b and 7667f0b.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • .github/workflows/ci.yml
  • .pre-commit-config.yaml
  • CMakeLists.txt
  • bindings/CMakeLists.txt
  • bindings/bindings.cpp
  • cmake/ExternalDependencies.cmake
  • pyproject.toml
🧰 Additional context used
🧠 Learnings (19)
📓 Common learnings
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/dd/register_matrix_dds.cpp:64-109
Timestamp: 2025-12-15T01:54:22.129Z
Learning: In the munich-quantum-toolkit/core repository, after migrating to nanobind, docstrings for Python bindings are now added directly in the C++ binding code (using R"pb(...)pb" syntax) and stub files (.pyi) are auto-generated using the `bindings/generate-stubs.sh` script. This replaces the previous pybind11 approach where docstrings were manually maintained in stub files.
Learnt from: burgholzer
Repo: munich-quantum-toolkit/qcec PR: 817
File: pyproject.toml:81-82
Timestamp: 2026-01-09T17:58:10.350Z
Learning: In the Munich Quantum Toolkit projects using nanobind, setting `wheel.py-api = "cp312"` in `[tool.scikit-build]` enables Stable ABI wheels only for Python 3.12+ (where nanobind supports it), while automatically building regular non-ABI3 wheels for earlier Python versions (3.10, 3.11) and free-threading builds (3.14t). This allows a single configuration to appropriately handle both old and new Python versions without forcing incompatible ABI requirements.
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1355
File: bindings/fomac/fomac.cpp:227-264
Timestamp: 2025-12-07T01:21:27.544Z
Learning: In the munich-quantum-toolkit/core repository, docstrings for Python bindings are added to the corresponding stub files (.pyi) rather than directly in the pybind11 C++ bindings code. This practice may change if the project adopts nanobind with automatic stub generation.
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/ir/register_permutation.cpp:153-171
Timestamp: 2025-12-22T01:25:21.609Z
Learning: In the munich-quantum-toolkit/core repository, when using nanobind iterator factory functions like `make_key_iterator` and `make_iterator`, the unqualified form (without explicit `nb::` prefix) is preferred. The clang-tidy configuration suggests removal of explicit namespace qualification, relying on ADL (Argument-Dependent Lookup) to resolve these functions correctly.
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/fomac/fomac.cpp:111-116
Timestamp: 2025-12-19T00:05:54.428Z
Learning: In the munich-quantum-toolkit/core repository after migrating to nanobind, lifetime management differs from pybind11: `nb::keep_alive<nurse, patient>()` does not exist in nanobind. Instead, use `nb::rv_policy::reference_internal` when binding methods that return objects referencing internal state of the parent object (e.g., Session::getDevices returning Device objects that depend on the Session). This tells nanobind to keep the parent alive while children exist.
Learnt from: burgholzer
Repo: munich-quantum-toolkit/yaqs PR: 212
File: CHANGELOG.md:12-15
Timestamp: 2025-10-14T14:37:38.047Z
Learning: In the munich-quantum-toolkit/yaqs project, changelog entries follow the template: "- $TITLE ([#$NUMBER]($URL)) ([**AUTHOR**](https://github.com/$AUTHOR))". Issue references should not be included in changelog entries; the PR number is sufficient for traceability.
Learnt from: DRovara
Repo: munich-quantum-toolkit/core PR: 1108
File: mlir/lib/Dialect/MQTOpt/Transforms/ReplaceBasisStateControlsWithIfPattern.cpp:219-221
Timestamp: 2025-10-09T13:14:10.178Z
Learning: The MQT Core project (munich-quantum-toolkit/core repository) uses the C++20 standard, not C++17. C++20 features such as abbreviated function templates (e.g., `const auto&` parameters) are supported and valid in this codebase.
📚 Learning: 2025-12-15T01:54:22.129Z
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/dd/register_matrix_dds.cpp:64-109
Timestamp: 2025-12-15T01:54:22.129Z
Learning: In the munich-quantum-toolkit/core repository, after migrating to nanobind, docstrings for Python bindings are now added directly in the C++ binding code (using R"pb(...)pb" syntax) and stub files (.pyi) are auto-generated using the `bindings/generate-stubs.sh` script. This replaces the previous pybind11 approach where docstrings were manually maintained in stub files.

Applied to files:

  • .github/workflows/ci.yml
  • bindings/CMakeLists.txt
  • CMakeLists.txt
  • pyproject.toml
  • cmake/ExternalDependencies.cmake
  • bindings/bindings.cpp
📚 Learning: 2026-01-09T17:58:10.350Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/qcec PR: 817
File: pyproject.toml:81-82
Timestamp: 2026-01-09T17:58:10.350Z
Learning: In the Munich Quantum Toolkit projects using nanobind, setting `wheel.py-api = "cp312"` in `[tool.scikit-build]` enables Stable ABI wheels only for Python 3.12+ (where nanobind supports it), while automatically building regular non-ABI3 wheels for earlier Python versions (3.10, 3.11) and free-threading builds (3.14t). This allows a single configuration to appropriately handle both old and new Python versions without forcing incompatible ABI requirements.

Applied to files:

  • .github/workflows/ci.yml
  • pyproject.toml
  • cmake/ExternalDependencies.cmake
📚 Learning: 2025-11-04T15:22:19.558Z
Learnt from: marcelwa
Repo: munich-quantum-toolkit/core PR: 1243
File: test/python/qdmi/qiskit/conftest.py:155-157
Timestamp: 2025-11-04T15:22:19.558Z
Learning: The munich-quantum-toolkit/core repository requires Python 3.10 or later, so Python 3.10+ features (such as `zip(..., strict=...)`, pattern matching, etc.) are acceptable and should not be flagged as compatibility issues.

Applied to files:

  • .github/workflows/ci.yml
  • pyproject.toml
📚 Learning: 2025-12-07T01:21:27.544Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1355
File: bindings/fomac/fomac.cpp:227-264
Timestamp: 2025-12-07T01:21:27.544Z
Learning: In the munich-quantum-toolkit/core repository, docstrings for Python bindings are added to the corresponding stub files (.pyi) rather than directly in the pybind11 C++ bindings code. This practice may change if the project adopts nanobind with automatic stub generation.

Applied to files:

  • bindings/CMakeLists.txt
  • pyproject.toml
  • cmake/ExternalDependencies.cmake
  • bindings/bindings.cpp
📚 Learning: 2025-10-10T08:09:54.528Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1246
File: bindings/CMakeLists.txt:0-0
Timestamp: 2025-10-10T08:09:54.528Z
Learning: In the Munich Quantum Toolkit (MQT) Core project, scikit-build-core is configured with `wheel.install-dir = "mqt/core"` in pyproject.toml, which automatically prefixes all CMake `DESTINATION` paths with `mqt/core/` during wheel installation. Therefore, CMake install destinations are relative to the `mqt/core` package namespace, not `site-packages`.

Applied to files:

  • bindings/CMakeLists.txt
  • pyproject.toml
  • cmake/ExternalDependencies.cmake
📚 Learning: 2025-12-28T17:14:53.890Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1403
File: src/qdmi/na/CMakeLists.txt:31-38
Timestamp: 2025-12-28T17:14:53.890Z
Learning: In the munich-quantum-toolkit/core repository, the NA device generator target (mqt-core-qdmi-na-device-gen) is intentionally propagated to MQT_CORE_TARGETS via list(APPEND) because it's publicly linked to the NA device library (the NA device uses a public function from the generator). The SC device generator is not propagated because it has no such dependency with the SC device library.

Applied to files:

  • bindings/CMakeLists.txt
  • cmake/ExternalDependencies.cmake
📚 Learning: 2025-10-10T08:10:16.394Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1246
File: test/python/na/test_na_fomac.py:35-0
Timestamp: 2025-10-10T08:10:16.394Z
Learning: In the munich-quantum-toolkit/core repository, scikit-build-core is configured with `wheel.install-dir = "mqt/core"` in pyproject.toml, which means CMake `install()` commands with `DESTINATION <path>` install files relative to `mqt/core/` in the wheel, making them accessible via `files("mqt.core").joinpath("<path>")`.

Applied to files:

  • bindings/CMakeLists.txt
  • pyproject.toml
  • cmake/ExternalDependencies.cmake
📚 Learning: 2025-12-28T17:13:36.900Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1403
File: pyproject.toml:98-102
Timestamp: 2025-12-28T17:13:36.900Z
Learning: In the munich-quantum-toolkit/core project, scikit-build-core is intelligent enough to skip build targets listed in pyproject.toml that don't exist for a given platform, so platform-specific targets (like `-dyn` targets conditioned on `NOT WIN32`) can be unconditionally listed in `build.targets` without causing Windows build failures.

Applied to files:

  • bindings/CMakeLists.txt
  • pyproject.toml
📚 Learning: 2025-12-13T20:08:45.549Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/qmap PR: 862
File: pyproject.toml:65-66
Timestamp: 2025-12-13T20:08:45.549Z
Learning: In the qmap project (pyproject.toml), maintain broad compatibility with dependencies across supported Python versions. Avoid artificially raising minimum version requirements unless there's a specific need (e.g., to guarantee binary wheel availability for certain Python versions, or to access required features). The goal is to keep the software as broadly compatible as possible with the rest of the ecosystem.

Applied to files:

  • pyproject.toml
📚 Learning: 2025-12-15T01:59:17.023Z
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1383
File: python/mqt/core/ir/operations.pyi:9-16
Timestamp: 2025-12-15T01:59:17.023Z
Learning: In the munich-quantum-toolkit/core repository, stub files (.pyi) are auto-generated by nanobind's stubgen tool and should not be manually modified for style preferences, as changes would be overwritten during regeneration.

Applied to files:

  • pyproject.toml
  • bindings/bindings.cpp
📚 Learning: 2025-12-14T16:51:52.504Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core-plugins-catalyst PR: 23
File: .readthedocs.yaml:13-18
Timestamp: 2025-12-14T16:51:52.504Z
Learning: In the munich-quantum-toolkit/core-plugins-catalyst repository, LLVM and MLIR toolchains are required for the documentation build because `uv run` includes a full build of the package, which compiles C++/MLIR extensions using scikit-build-core.

Applied to files:

  • pyproject.toml
📚 Learning: 2025-10-11T19:39:32.050Z
Learnt from: denialhaag
Repo: munich-quantum-toolkit/debugger PR: 160
File: pyproject.toml:54-54
Timestamp: 2025-10-11T19:39:32.050Z
Learning: Qiskit packages use cp39-abi3 wheels (stable ABI) which are forward-compatible with Python 3.9+ including Python 3.14, even if the package classifiers don't explicitly list Python 3.14 support.

Applied to files:

  • pyproject.toml
📚 Learning: 2025-10-09T22:15:59.924Z
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1246
File: pyproject.toml:340-341
Timestamp: 2025-10-09T22:15:59.924Z
Learning: Qiskit publishes ABI3 wheels (e.g., cp39-abi3) that are forward-compatible with newer Python versions including Python 3.14, so no explicit Python 3.14 wheels are required for qiskit to work on Python 3.14.

Applied to files:

  • pyproject.toml
📚 Learning: 2025-11-03T23:09:26.881Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1287
File: test/qdmi/dd/CMakeLists.txt:9-21
Timestamp: 2025-11-03T23:09:26.881Z
Learning: The CMake functions `generate_device_defs_executable` and `generate_prefixed_qdmi_headers` used in QDMI device test CMakeLists.txt files are provided by the external QDMI library (fetched via FetchContent from https://github.com/Munich-Quantum-Software-Stack/qdmi.git), specifically in the cmake/PrefixHandling.cmake module of the QDMI repository.

Applied to files:

  • cmake/ExternalDependencies.cmake
📚 Learning: 2025-12-05T17:45:37.602Z
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1360
File: .github/workflows/reusable-mlir-tests.yml:40-43
Timestamp: 2025-12-05T17:45:37.602Z
Learning: In the munich-quantum-toolkit/core repository, patch releases of LLVM dependencies don't require documentation updates, changelog entries, or additional tests beyond what's validated by passing CI checks.

Applied to files:

  • cmake/ExternalDependencies.cmake
📚 Learning: 2025-12-22T01:25:21.609Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/ir/register_permutation.cpp:153-171
Timestamp: 2025-12-22T01:25:21.609Z
Learning: In the munich-quantum-toolkit/core repository, when using nanobind iterator factory functions like `make_key_iterator` and `make_iterator`, the unqualified form (without explicit `nb::` prefix) is preferred. The clang-tidy configuration suggests removal of explicit namespace qualification, relying on ADL (Argument-Dependent Lookup) to resolve these functions correctly.

Applied to files:

  • cmake/ExternalDependencies.cmake
  • bindings/bindings.cpp
📚 Learning: 2025-12-19T00:05:54.428Z
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/fomac/fomac.cpp:111-116
Timestamp: 2025-12-19T00:05:54.428Z
Learning: In the munich-quantum-toolkit/core repository after migrating to nanobind, lifetime management differs from pybind11: `nb::keep_alive<nurse, patient>()` does not exist in nanobind. Instead, use `nb::rv_policy::reference_internal` when binding methods that return objects referencing internal state of the parent object (e.g., Session::getDevices returning Device objects that depend on the Session). This tells nanobind to keep the parent alive while children exist.

Applied to files:

  • bindings/bindings.cpp
📚 Learning: 2025-12-21T22:35:08.572Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/fomac/fomac.cpp:348-364
Timestamp: 2025-12-21T22:35:08.572Z
Learning: In the munich-quantum-toolkit/core repository's nanobind bindings, use `.sig("...")` on parameter arguments that have vector or container defaults (e.g., `"sites"_a.sig("...") = std::vector<fomac::Session::Device::Site>{}`) to prevent exposing mutable defaults in the Python API, which would be flagged as a code smell by Python linters. This pattern is preferred over removing `.sig("...")` even though it shows `...` in the stub signature.

Applied to files:

  • bindings/bindings.cpp
🪛 Cppcheck (2.19.0)
bindings/bindings.cpp

[information] 13-13: Include file

(missingIncludeSystem)


[information] 14-14: Include file

(missingIncludeSystem)


[information] 15-15: Include file

(missingIncludeSystem)


[information] 16-16: Include file

(missingIncludeSystem)


[information] 17-17: Include file

(missingIncludeSystem)


[information] 14-14: Include file

(missingInclude)


[information] 15-15: Include file

(missingInclude)


[information] 17-17: Include file

(missingIncludeSystem)


[information] 18-18: Include file

(missingIncludeSystem)


[information] 19-19: Include file

(missingIncludeSystem)


[information] 20-20: Include file

(missingIncludeSystem)


[information] 12-12: Include file

(missingInclude)


[information] 14-14: Include file

(missingIncludeSystem)


[information] 15-15: Include file

(missingIncludeSystem)


[information] 16-16: Include file

(missingIncludeSystem)


[style] 53-53: The function 'from_json' is never used.

(unusedFunction)

🔇 Additional comments (15)
.github/workflows/ci.yml (1)

102-102: LGTM!

The binding library installation correctly updated from pybind11 to nanobind, and the version (2.10.2) is consistent with pyproject.toml.

.pre-commit-config.yaml (1)

133-133: LGTM!

The capitalization rule correctly updated to catch "Nanobind" (should be lowercase "nanobind"), consistent with the library's official naming convention.

bindings/CMakeLists.txt (2)

28-37: LGTM!

The migration to add_mqt_python_binding_nanobind is correctly implemented with the required MODULE_NAME and INSTALL_DIR parameters. Removal of pybind11_json from link libraries is appropriate since nanobind handles JSON differently.


42-45: The explicit install() directive is not redundant and is necessary for scikit-build-core integration.

The add_mqt_python_binding_nanobind() function with INSTALL_DIR . handles basic installation, but scikit-build-core requires an explicit install() directive to properly manage Python wheel packaging. The COMPONENT name in the install() directive (mqt-qusat_Python) must match the component specified in pyproject.toml's install.components setting. scikit-build-core uses this configuration to filter and package only the specified CMake install components into the wheel. Removing this directive would break wheel creation.

cmake/ExternalDependencies.cmake (2)

31-36: LGTM!

The nanobind detection pattern using python -m nanobind --cmake_dir followed by find_package(nanobind CONFIG REQUIRED) is the correct approach. This matches the standard nanobind CMake integration pattern.


39-42: LGTM!

The MQT Core version bump to 3.4.0 is required for nanobind support, as MQT Core 3.4.0 provides the add_mqt_python_binding_nanobind CMake helper function used in bindings/CMakeLists.txt.

pyproject.toml (5)

3-6: LGTM!

Build dependencies correctly updated to nanobind>=2.10.2 and mqt.core~=3.4.0, consistent with the migration.


66-67: LGTM!

Setting wheel.py-api = "cp312" correctly enables Stable ABI wheels for Python 3.12+ where nanobind supports it. Based on learnings, this configuration automatically builds regular non-ABI3 wheels for earlier Python versions (3.10, 3.11) and free-threading builds.


299-301: LGTM!

Library exclusion versions correctly updated to 3.4 to match the MQT Core 3.4.0 dependency.


335-338: LGTM!

Dependency groups correctly updated to use nanobind and mqt.core 3.4.0, maintaining consistency with build-system requirements.


315-318: Verify abi3audit integration for Stable ABI validation.

The abi3audit --strict --report step validates ABI3 compliance for CPython 3.12+ wheels and fails the build on any detected violations. Using inherit.repair-wheel-command = "append" ensures this runs after the platform-specific repair commands.

This override correctly matches cp312-*, cp313-*, cp314-*, and so on, validating all Stable ABI wheels.

CMakeLists.txt (2)

22-24: LGTM on BINDINGS cache variable change.

Changing from CACHE BOOL ... FORCE to CACHE INTERNAL is appropriate since this is an internal setting that shouldn't be exposed in the CMake GUI when building bindings.


29-31: Stable ABI configuration looks correct.

The Python_FIND_FRAMEWORK LAST preference and ${SKBUILD_SABI_COMPONENT} addition properly configure the build for Stable ABI wheels with nanobind. Based on learnings, this enables Stable ABI for Python 3.12+ while automatically building regular wheels for earlier versions.

Also applies to: 42-43

bindings/bindings.cpp (2)

14-20: LGTM on nanobind migration.

The include changes and namespace alias are correct. The NOLINT(misc-include-cleaner) comments are appropriate since these STL headers provide implicit template specializations for type casters.


43-44: Correct module initialization for nanobind.

The NB_MODULE macro and explicit import of mqt.core.ir are required for the qc::QuantumComputation type to be properly recognized in the bindings.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@bindings/bindings.cpp`:
- Around line 48-58: The m.def for check_equivalence exposes a mutable vector
default for "inputs"_a; append a .sig specifying the parameter types to avoid
exposing a mutable default (e.g. chain .sig("(qc::QuantumComputation&,
qc::QuantumComputation&, std::vector<std::string>)") to the existing m.def call)
so the "inputs"_a default remains safe for the Python API.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7667f0b and 290d78e.

📒 Files selected for processing (1)
  • bindings/bindings.cpp
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/dd/register_matrix_dds.cpp:64-109
Timestamp: 2025-12-15T01:54:22.129Z
Learning: In the munich-quantum-toolkit/core repository, after migrating to nanobind, docstrings for Python bindings are now added directly in the C++ binding code (using R"pb(...)pb" syntax) and stub files (.pyi) are auto-generated using the `bindings/generate-stubs.sh` script. This replaces the previous pybind11 approach where docstrings were manually maintained in stub files.
Learnt from: burgholzer
Repo: munich-quantum-toolkit/qcec PR: 817
File: pyproject.toml:81-82
Timestamp: 2026-01-09T17:58:10.350Z
Learning: In the Munich Quantum Toolkit projects using nanobind, setting `wheel.py-api = "cp312"` in `[tool.scikit-build]` enables Stable ABI wheels only for Python 3.12+ (where nanobind supports it), while automatically building regular non-ABI3 wheels for earlier Python versions (3.10, 3.11) and free-threading builds (3.14t). This allows a single configuration to appropriately handle both old and new Python versions without forcing incompatible ABI requirements.
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/ir/register_permutation.cpp:153-171
Timestamp: 2025-12-22T01:25:21.609Z
Learning: In the munich-quantum-toolkit/core repository, when using nanobind iterator factory functions like `make_key_iterator` and `make_iterator`, the unqualified form (without explicit `nb::` prefix) is preferred. The clang-tidy configuration suggests removal of explicit namespace qualification, relying on ADL (Argument-Dependent Lookup) to resolve these functions correctly.
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1355
File: bindings/fomac/fomac.cpp:227-264
Timestamp: 2025-12-07T01:21:27.544Z
Learning: In the munich-quantum-toolkit/core repository, docstrings for Python bindings are added to the corresponding stub files (.pyi) rather than directly in the pybind11 C++ bindings code. This practice may change if the project adopts nanobind with automatic stub generation.
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/fomac/fomac.cpp:111-116
Timestamp: 2025-12-19T00:05:54.428Z
Learning: In the munich-quantum-toolkit/core repository after migrating to nanobind, lifetime management differs from pybind11: `nb::keep_alive<nurse, patient>()` does not exist in nanobind. Instead, use `nb::rv_policy::reference_internal` when binding methods that return objects referencing internal state of the parent object (e.g., Session::getDevices returning Device objects that depend on the Session). This tells nanobind to keep the parent alive while children exist.
Learnt from: burgholzer
Repo: munich-quantum-toolkit/yaqs PR: 212
File: CHANGELOG.md:12-15
Timestamp: 2025-10-14T14:37:38.047Z
Learning: In the munich-quantum-toolkit/yaqs project, changelog entries follow the template: "- $TITLE ([#$NUMBER]($URL)) ([**AUTHOR**](https://github.com/$AUTHOR))". Issue references should not be included in changelog entries; the PR number is sufficient for traceability.
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1246
File: bindings/CMakeLists.txt:0-0
Timestamp: 2025-10-10T08:09:54.528Z
Learning: In the Munich Quantum Toolkit (MQT) Core project, scikit-build-core is configured with `wheel.install-dir = "mqt/core"` in pyproject.toml, which automatically prefixes all CMake `DESTINATION` paths with `mqt/core/` during wheel installation. Therefore, CMake install destinations are relative to the `mqt/core` package namespace, not `site-packages`.
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1383
File: python/mqt/core/ir/operations.pyi:9-16
Timestamp: 2025-12-15T01:59:17.023Z
Learning: In the munich-quantum-toolkit/core repository, stub files (.pyi) are auto-generated by nanobind's stubgen tool and should not be manually modified for style preferences, as changes would be overwritten during regeneration.
Learnt from: DRovara
Repo: munich-quantum-toolkit/core PR: 1108
File: mlir/lib/Dialect/MQTOpt/Transforms/ReplaceBasisStateControlsWithIfPattern.cpp:219-221
Timestamp: 2025-10-09T13:14:10.178Z
Learning: The MQT Core project (munich-quantum-toolkit/core repository) uses the C++20 standard, not C++17. C++20 features such as abbreviated function templates (e.g., `const auto&` parameters) are supported and valid in this codebase.
Learnt from: marcelwa
Repo: munich-quantum-toolkit/core PR: 1243
File: test/python/qdmi/qiskit/conftest.py:155-157
Timestamp: 2025-11-04T15:22:19.558Z
Learning: The munich-quantum-toolkit/core repository requires Python 3.10 or later, so Python 3.10+ features (such as `zip(..., strict=...)`, pattern matching, etc.) are acceptable and should not be flagged as compatibility issues.
📚 Learning: 2025-12-15T01:54:22.129Z
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/dd/register_matrix_dds.cpp:64-109
Timestamp: 2025-12-15T01:54:22.129Z
Learning: In the munich-quantum-toolkit/core repository, after migrating to nanobind, docstrings for Python bindings are now added directly in the C++ binding code (using R"pb(...)pb" syntax) and stub files (.pyi) are auto-generated using the `bindings/generate-stubs.sh` script. This replaces the previous pybind11 approach where docstrings were manually maintained in stub files.

Applied to files:

  • bindings/bindings.cpp
📚 Learning: 2025-12-07T01:21:27.544Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1355
File: bindings/fomac/fomac.cpp:227-264
Timestamp: 2025-12-07T01:21:27.544Z
Learning: In the munich-quantum-toolkit/core repository, docstrings for Python bindings are added to the corresponding stub files (.pyi) rather than directly in the pybind11 C++ bindings code. This practice may change if the project adopts nanobind with automatic stub generation.

Applied to files:

  • bindings/bindings.cpp
📚 Learning: 2025-12-22T01:25:21.609Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/ir/register_permutation.cpp:153-171
Timestamp: 2025-12-22T01:25:21.609Z
Learning: In the munich-quantum-toolkit/core repository, when using nanobind iterator factory functions like `make_key_iterator` and `make_iterator`, the unqualified form (without explicit `nb::` prefix) is preferred. The clang-tidy configuration suggests removal of explicit namespace qualification, relying on ADL (Argument-Dependent Lookup) to resolve these functions correctly.

Applied to files:

  • bindings/bindings.cpp
📚 Learning: 2025-12-19T00:05:54.428Z
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/fomac/fomac.cpp:111-116
Timestamp: 2025-12-19T00:05:54.428Z
Learning: In the munich-quantum-toolkit/core repository after migrating to nanobind, lifetime management differs from pybind11: `nb::keep_alive<nurse, patient>()` does not exist in nanobind. Instead, use `nb::rv_policy::reference_internal` when binding methods that return objects referencing internal state of the parent object (e.g., Session::getDevices returning Device objects that depend on the Session). This tells nanobind to keep the parent alive while children exist.

Applied to files:

  • bindings/bindings.cpp
📚 Learning: 2025-12-21T22:35:08.572Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/fomac/fomac.cpp:348-364
Timestamp: 2025-12-21T22:35:08.572Z
Learning: In the munich-quantum-toolkit/core repository's nanobind bindings, use `.sig("...")` on parameter arguments that have vector or container defaults (e.g., `"sites"_a.sig("...") = std::vector<fomac::Session::Device::Site>{}`) to prevent exposing mutable defaults in the Python API, which would be flagged as a code smell by Python linters. This pattern is preferred over removing `.sig("...")` even though it shows `...` in the stub signature.

Applied to files:

  • bindings/bindings.cpp
📚 Learning: 2025-12-15T01:59:17.023Z
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1383
File: python/mqt/core/ir/operations.pyi:9-16
Timestamp: 2025-12-15T01:59:17.023Z
Learning: In the munich-quantum-toolkit/core repository, stub files (.pyi) are auto-generated by nanobind's stubgen tool and should not be manually modified for style preferences, as changes would be overwritten during regeneration.

Applied to files:

  • bindings/bindings.cpp
🪛 Cppcheck (2.19.0)
bindings/bindings.cpp

[information] 13-13: Include file

(missingIncludeSystem)


[information] 14-14: Include file

(missingIncludeSystem)


[information] 15-15: Include file

(missingIncludeSystem)


[information] 16-16: Include file

(missingIncludeSystem)


[information] 17-17: Include file

(missingIncludeSystem)


[information] 14-14: Include file

(missingInclude)


[information] 15-15: Include file

(missingInclude)


[information] 17-17: Include file

(missingIncludeSystem)


[information] 18-18: Include file

(missingIncludeSystem)


[information] 19-19: Include file

(missingIncludeSystem)


[information] 20-20: Include file

(missingIncludeSystem)


[information] 12-12: Include file

(missingInclude)


[information] 14-14: Include file

(missingIncludeSystem)


[information] 15-15: Include file

(missingIncludeSystem)


[information] 16-16: Include file

(missingIncludeSystem)


[style] 53-53: The function 'from_json' is never used.

(unusedFunction)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: 🇨‌ Test 🐧 (ubuntu-24.04-arm, gcc, Release) / 🐧 ubuntu-24.04-arm gcc Release
  • GitHub Check: 🇨‌ Test 🐧 (ubuntu-24.04, gcc, Release) / 🐧 ubuntu-24.04 gcc Release
  • GitHub Check: 🇨‌ Test 🐧 (ubuntu-24.04, gcc, Debug) / 🐧 ubuntu-24.04 gcc Debug
  • GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
  • GitHub Check: 🇨‌ Test 🏁 (windows-11-arm, msvc, Release) / 🏁 windows-11-arm msvc Release
🔇 Additional comments (4)
bindings/bindings.cpp (4)

14-20: LGTM! Header and namespace migration to nanobind is correct.

The nanobind STL headers are properly included with appropriate NOLINT comments, and the namespace alias follows project conventions.


28-32: LGTM! Error handling correctly uses nb::print.

The exception handler properly uses the nanobind equivalent for printing.


42-46: LGTM! Module definition and dependency import are correct.

The NB_MODULE macro and the explicit import of mqt.core.ir ensure proper initialization order for the bindings.


60-62: LGTM!

The generate_dimacs binding follows standard nanobind patterns.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@read-the-docs-community
Copy link

read-the-docs-community bot commented Jan 14, 2026

Documentation build overview

📚 mqt-qusat | 🛠️ Build #31009932 | 📁 Comparing c1a326e against latest (513db9b)


🔍 Preview build

Show files changed (2 files in total): 📝 2 modified | ➕ 0 added | ➖ 0 deleted
File Status
api/mqt/qusat/index.html 📝 modified
api/mqt/qusat/pyqusat/index.html 📝 modified

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@noxfile.py`:
- Around line 177-217: In the stubs(session: nox.Session) function, add a guard
after pyi_files = list(package_root.glob("**/*.pyi")) to handle the empty-list
case: if pyi_files is empty, log or notify and return (or skip the subsequent
prek runs) so you don't invoke session.run("prek", ...) with no file arguments;
reference the stubs function and the pyi_files variable to locate where to
insert the early return/skip logic.

In `@python/mqt/qusat/pyqusat.pyi`:
- Around line 13-16: The stub for check_equivalence has a mutable default list
(inputs: list[str] = []) which triggers linter warnings; update the nanobind
signature used to generate the stub by changing the nb::sig() string for
check_equivalence in bindings.cpp (currently using "inputs: list[str] = []"
around the nb::sig call) to use the placeholder default "inputs: list[str] =
..." so the generated .pyi shows an immutable ellipsis default instead of a
mutable empty list.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 290d78e and b7d6182.

📒 Files selected for processing (4)
  • .github/workflows/ci.yml
  • bindings/bindings.cpp
  • noxfile.py
  • python/mqt/qusat/pyqusat.pyi
🧰 Additional context used
🧠 Learnings (11)
📓 Common learnings
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/dd/register_matrix_dds.cpp:64-109
Timestamp: 2025-12-15T01:54:22.129Z
Learning: In the munich-quantum-toolkit/core repository, after migrating to nanobind, docstrings for Python bindings are now added directly in the C++ binding code (using R"pb(...)pb" syntax) and stub files (.pyi) are auto-generated using the `bindings/generate-stubs.sh` script. This replaces the previous pybind11 approach where docstrings were manually maintained in stub files.
Learnt from: burgholzer
Repo: munich-quantum-toolkit/qcec PR: 817
File: pyproject.toml:81-82
Timestamp: 2026-01-09T17:58:10.350Z
Learning: In the Munich Quantum Toolkit projects using nanobind, setting `wheel.py-api = "cp312"` in `[tool.scikit-build]` enables Stable ABI wheels only for Python 3.12+ (where nanobind supports it), while automatically building regular non-ABI3 wheels for earlier Python versions (3.10, 3.11) and free-threading builds (3.14t). This allows a single configuration to appropriately handle both old and new Python versions without forcing incompatible ABI requirements.
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/ir/register_permutation.cpp:153-171
Timestamp: 2025-12-22T01:25:21.609Z
Learning: In the munich-quantum-toolkit/core repository, when using nanobind iterator factory functions like `make_key_iterator` and `make_iterator`, the unqualified form (without explicit `nb::` prefix) is preferred. The clang-tidy configuration suggests removal of explicit namespace qualification, relying on ADL (Argument-Dependent Lookup) to resolve these functions correctly.
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1355
File: bindings/fomac/fomac.cpp:227-264
Timestamp: 2025-12-07T01:21:27.544Z
Learning: In the munich-quantum-toolkit/core repository, docstrings for Python bindings are added to the corresponding stub files (.pyi) rather than directly in the pybind11 C++ bindings code. This practice may change if the project adopts nanobind with automatic stub generation.
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/fomac/fomac.cpp:111-116
Timestamp: 2025-12-19T00:05:54.428Z
Learning: In the munich-quantum-toolkit/core repository after migrating to nanobind, lifetime management differs from pybind11: `nb::keep_alive<nurse, patient>()` does not exist in nanobind. Instead, use `nb::rv_policy::reference_internal` when binding methods that return objects referencing internal state of the parent object (e.g., Session::getDevices returning Device objects that depend on the Session). This tells nanobind to keep the parent alive while children exist.
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1246
File: bindings/CMakeLists.txt:0-0
Timestamp: 2025-10-10T08:09:54.528Z
Learning: In the Munich Quantum Toolkit (MQT) Core project, scikit-build-core is configured with `wheel.install-dir = "mqt/core"` in pyproject.toml, which automatically prefixes all CMake `DESTINATION` paths with `mqt/core/` during wheel installation. Therefore, CMake install destinations are relative to the `mqt/core` package namespace, not `site-packages`.
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1383
File: python/mqt/core/ir/operations.pyi:9-16
Timestamp: 2025-12-15T01:59:17.023Z
Learning: In the munich-quantum-toolkit/core repository, stub files (.pyi) are auto-generated by nanobind's stubgen tool and should not be manually modified for style preferences, as changes would be overwritten during regeneration.
Learnt from: DRovara
Repo: munich-quantum-toolkit/core PR: 1108
File: mlir/lib/Dialect/MQTOpt/Transforms/ReplaceBasisStateControlsWithIfPattern.cpp:219-221
Timestamp: 2025-10-09T13:14:10.178Z
Learning: The MQT Core project (munich-quantum-toolkit/core repository) uses the C++20 standard, not C++17. C++20 features such as abbreviated function templates (e.g., `const auto&` parameters) are supported and valid in this codebase.
Learnt from: marcelwa
Repo: munich-quantum-toolkit/core PR: 1243
File: test/python/qdmi/qiskit/conftest.py:155-157
Timestamp: 2025-11-04T15:22:19.558Z
Learning: The munich-quantum-toolkit/core repository requires Python 3.10 or later, so Python 3.10+ features (such as `zip(..., strict=...)`, pattern matching, etc.) are acceptable and should not be flagged as compatibility issues.
📚 Learning: 2025-12-15T01:54:22.129Z
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/dd/register_matrix_dds.cpp:64-109
Timestamp: 2025-12-15T01:54:22.129Z
Learning: In the munich-quantum-toolkit/core repository, after migrating to nanobind, docstrings for Python bindings are now added directly in the C++ binding code (using R"pb(...)pb" syntax) and stub files (.pyi) are auto-generated using the `bindings/generate-stubs.sh` script. This replaces the previous pybind11 approach where docstrings were manually maintained in stub files.

Applied to files:

  • noxfile.py
  • bindings/bindings.cpp
  • .github/workflows/ci.yml
📚 Learning: 2025-12-15T01:59:17.023Z
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1383
File: python/mqt/core/ir/operations.pyi:9-16
Timestamp: 2025-12-15T01:59:17.023Z
Learning: In the munich-quantum-toolkit/core repository, stub files (.pyi) are auto-generated by nanobind's stubgen tool and should not be manually modified for style preferences, as changes would be overwritten during regeneration.

Applied to files:

  • noxfile.py
  • bindings/bindings.cpp
  • .github/workflows/ci.yml
📚 Learning: 2025-12-07T01:21:27.544Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1355
File: bindings/fomac/fomac.cpp:227-264
Timestamp: 2025-12-07T01:21:27.544Z
Learning: In the munich-quantum-toolkit/core repository, docstrings for Python bindings are added to the corresponding stub files (.pyi) rather than directly in the pybind11 C++ bindings code. This practice may change if the project adopts nanobind with automatic stub generation.

Applied to files:

  • noxfile.py
  • bindings/bindings.cpp
  • .github/workflows/ci.yml
📚 Learning: 2025-12-22T01:25:21.609Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/ir/register_permutation.cpp:153-171
Timestamp: 2025-12-22T01:25:21.609Z
Learning: In the munich-quantum-toolkit/core repository, when using nanobind iterator factory functions like `make_key_iterator` and `make_iterator`, the unqualified form (without explicit `nb::` prefix) is preferred. The clang-tidy configuration suggests removal of explicit namespace qualification, relying on ADL (Argument-Dependent Lookup) to resolve these functions correctly.

Applied to files:

  • bindings/bindings.cpp
📚 Learning: 2025-12-19T00:05:54.428Z
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/fomac/fomac.cpp:111-116
Timestamp: 2025-12-19T00:05:54.428Z
Learning: In the munich-quantum-toolkit/core repository after migrating to nanobind, lifetime management differs from pybind11: `nb::keep_alive<nurse, patient>()` does not exist in nanobind. Instead, use `nb::rv_policy::reference_internal` when binding methods that return objects referencing internal state of the parent object (e.g., Session::getDevices returning Device objects that depend on the Session). This tells nanobind to keep the parent alive while children exist.

Applied to files:

  • bindings/bindings.cpp
📚 Learning: 2025-12-21T22:35:08.572Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/fomac/fomac.cpp:348-364
Timestamp: 2025-12-21T22:35:08.572Z
Learning: In the munich-quantum-toolkit/core repository's nanobind bindings, use `.sig("...")` on parameter arguments that have vector or container defaults (e.g., `"sites"_a.sig("...") = std::vector<fomac::Session::Device::Site>{}`) to prevent exposing mutable defaults in the Python API, which would be flagged as a code smell by Python linters. This pattern is preferred over removing `.sig("...")` even though it shows `...` in the stub signature.

Applied to files:

  • bindings/bindings.cpp
📚 Learning: 2026-01-09T17:58:10.350Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/qcec PR: 817
File: pyproject.toml:81-82
Timestamp: 2026-01-09T17:58:10.350Z
Learning: In the Munich Quantum Toolkit projects using nanobind, setting `wheel.py-api = "cp312"` in `[tool.scikit-build]` enables Stable ABI wheels only for Python 3.12+ (where nanobind supports it), while automatically building regular non-ABI3 wheels for earlier Python versions (3.10, 3.11) and free-threading builds (3.14t). This allows a single configuration to appropriately handle both old and new Python versions without forcing incompatible ABI requirements.

Applied to files:

  • .github/workflows/ci.yml
📚 Learning: 2025-11-04T15:22:19.558Z
Learnt from: marcelwa
Repo: munich-quantum-toolkit/core PR: 1243
File: test/python/qdmi/qiskit/conftest.py:155-157
Timestamp: 2025-11-04T15:22:19.558Z
Learning: The munich-quantum-toolkit/core repository requires Python 3.10 or later, so Python 3.10+ features (such as `zip(..., strict=...)`, pattern matching, etc.) are acceptable and should not be flagged as compatibility issues.

Applied to files:

  • .github/workflows/ci.yml
📚 Learning: 2026-01-14T14:38:00.745Z
Learnt from: denialhaag
Repo: munich-quantum-toolkit/syrec PR: 514
File: cmake/ExternalDependencies.cmake:28-32
Timestamp: 2026-01-14T14:38:00.745Z
Learning: In Munich Quantum Toolkit projects, the standard pattern for Python module CMake discovery (e.g., nanobind, pybind11) uses execute_process with Python -m <module> --cmake_dir without ERROR_QUIET or explicit nanobind_ROOT validation before find_package, as the error messages from find_package failures are considered sufficiently clear.

Applied to files:

  • .github/workflows/ci.yml
📚 Learning: 2025-12-02T10:08:36.022Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core-plugins-catalyst PR: 1
File: .github/workflows/cd.yml:40-48
Timestamp: 2025-12-02T10:08:36.022Z
Learning: In munich-quantum-toolkit workflows, the reusable-python-packaging-sdist.yml workflow uploads the sdist artifact with the name `cibw-sdist` (or `dev-cibw-sdist` for pull requests), which means it is covered by the `cibw-*` glob pattern when downloading artifacts for deployment.

Applied to files:

  • .github/workflows/ci.yml
🪛 Cppcheck (2.19.0)
bindings/bindings.cpp

[information] 13-13: Include file

(missingIncludeSystem)


[information] 14-14: Include file

(missingIncludeSystem)


[information] 15-15: Include file

(missingIncludeSystem)


[information] 16-16: Include file

(missingIncludeSystem)


[information] 17-17: Include file

(missingIncludeSystem)


[information] 14-14: Include file

(missingInclude)


[information] 15-15: Include file

(missingInclude)


[information] 17-17: Include file

(missingIncludeSystem)


[information] 18-18: Include file

(missingIncludeSystem)


[information] 19-19: Include file

(missingIncludeSystem)


[information] 20-20: Include file

(missingIncludeSystem)


[information] 12-12: Include file

(missingInclude)


[information] 14-14: Include file

(missingIncludeSystem)


[information] 15-15: Include file

(missingIncludeSystem)


[information] 16-16: Include file

(missingIncludeSystem)


[style] 53-53: The function 'from_json' is never used.

(unusedFunction)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: 🇨‌ Test 🐧 (ubuntu-24.04, gcc, Release) / 🐧 ubuntu-24.04 gcc Release
  • GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
  • GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
  • GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
  • GitHub Check: 🐍 Test (macos-14) / 🐍 macos-14
  • GitHub Check: 🇨‌ Test 🍎 (macos-14, clang, Release) / 🍎 macos-14 clang Release
  • GitHub Check: 🇨‌ Test 🏁 (windows-2022, msvc, Debug) / 🏁 windows-2022 msvc Debug
  • GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
  • GitHub Check: 🇨‌ Test 🍎 (macos-15-intel, clang, Release) / 🍎 macos-15-intel clang Release
  • GitHub Check: 🇨‌ Test 🏁 (windows-11-arm, msvc, Release) / 🏁 windows-11-arm msvc Release
  • GitHub Check: 🇨‌ Test 🏁 (windows-2022, msvc, Release) / 🏁 windows-2022 msvc Release
  • GitHub Check: 🇨‌ Coverage / 📈 Coverage
  • GitHub Check: 🇨‌ Lint / 🚨 Lint
🔇 Additional comments (7)
.github/workflows/ci.yml (2)

143-143: LGTM!

Enabling check-stubs: true ensures the auto-generated stubs are validated in CI, which aligns well with the new stub generation workflow in noxfile.py.


102-102: Nanobind version pinning is correct. The upgrade from pybind11==3.0.1 to nanobind==2.10.2 aligns with the migration objectives. Version 2.10.2 is a recent stable release (published December 10, 2025) that fixes a regression affecting 32-bit architecture compilation, and it's consistent with the nanobind>=2.10.2 constraint already specified in pyproject.toml.

python/mqt/qusat/pyqusat.pyi (1)

18-19: LGTM!

The updated signature and docstring for generate_dimacs are appropriate and align with the nanobind migration.

bindings/bindings.cpp (4)

11-20: LGTM!

The nanobind includes and namespace setup follow the correct patterns. The NOLINT(misc-include-cleaner) comments are appropriate since the STL headers are needed for type conversion even if not directly referenced.


43-44: Correct module initialization pattern.

The NB_MODULE macro and explicit import of mqt.core.ir ensure the dependent module is properly loaded before using qc::QuantumComputation types.


49-62: JSON conversion via Python's json.loads works but has overhead.

The lambda wrapper converts the C++ nlohmann::json to a Python dict by serializing to string and parsing via Python's json.loads. This is functional but incurs serialization/deserialization overhead on every call.

For small result objects this is fine, but if performance becomes a concern, consider using nanobind's native JSON support or direct dict construction. The current approach is pragmatic for the migration.

Regarding the past review comment about .sig("...") for the vector default: the full nb::sig() approach used here is a more comprehensive solution that also provides complete type annotation for stub generation.

Note: The static analysis warning about from_json being unused is a false positive—it's detecting nlohmann::json's ADL (argument-dependent lookup) function, not code in this file.


64-65: LGTM!

Clean binding for generate_dimacs with appropriate docstring.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@denialhaag denialhaag requested a review from burgholzer January 14, 2026 16:54
Copy link
Member

@burgholzer burgholzer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

besides the couple of code rabbit comments, this looks great! Thanks 🙏🏼

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@bindings/bindings.cpp`:
- Around line 50-69: The lambda takes a non-const optional by reference; change
its signature to accept a const optional holding a reference to a const vector
(use std::optional<std::reference_wrapper<const std::vector<std::string>>>>
const& inputs) so nanobind treats the Python sequence as an immutable reference,
and when passing to checkEquivalence unwrap the reference (use
inputs.value().get()) or pass an empty vector when no value; update the lambda
parameter in the check_equivalence binding and the places that call
inputs.value() accordingly.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b7d6182 and edb00b0.

📒 Files selected for processing (3)
  • bindings/bindings.cpp
  • noxfile.py
  • python/mqt/qusat/pyqusat.pyi
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/dd/register_matrix_dds.cpp:64-109
Timestamp: 2025-12-15T01:54:22.129Z
Learning: In the munich-quantum-toolkit/core repository, after migrating to nanobind, docstrings for Python bindings are now added directly in the C++ binding code (using R"pb(...)pb" syntax) and stub files (.pyi) are auto-generated using the `bindings/generate-stubs.sh` script. This replaces the previous pybind11 approach where docstrings were manually maintained in stub files.
Learnt from: burgholzer
Repo: munich-quantum-toolkit/qcec PR: 817
File: pyproject.toml:81-82
Timestamp: 2026-01-09T17:58:10.350Z
Learning: In the Munich Quantum Toolkit projects using nanobind, setting `wheel.py-api = "cp312"` in `[tool.scikit-build]` enables Stable ABI wheels only for Python 3.12+ (where nanobind supports it), while automatically building regular non-ABI3 wheels for earlier Python versions (3.10, 3.11) and free-threading builds (3.14t). This allows a single configuration to appropriately handle both old and new Python versions without forcing incompatible ABI requirements.
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1355
File: bindings/fomac/fomac.cpp:227-264
Timestamp: 2025-12-07T01:21:27.544Z
Learning: In the munich-quantum-toolkit/core repository, docstrings for Python bindings are added to the corresponding stub files (.pyi) rather than directly in the pybind11 C++ bindings code. This practice may change if the project adopts nanobind with automatic stub generation.
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/fomac/fomac.cpp:111-116
Timestamp: 2025-12-19T00:05:54.428Z
Learning: In the munich-quantum-toolkit/core repository after migrating to nanobind, lifetime management differs from pybind11: `nb::keep_alive<nurse, patient>()` does not exist in nanobind. Instead, use `nb::rv_policy::reference_internal` when binding methods that return objects referencing internal state of the parent object (e.g., Session::getDevices returning Device objects that depend on the Session). This tells nanobind to keep the parent alive while children exist.
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/ir/register_permutation.cpp:153-171
Timestamp: 2025-12-22T01:25:21.609Z
Learning: In the munich-quantum-toolkit/core repository, when using nanobind iterator factory functions like `make_key_iterator` and `make_iterator`, the unqualified form (without explicit `nb::` prefix) is preferred. The clang-tidy configuration suggests removal of explicit namespace qualification, relying on ADL (Argument-Dependent Lookup) to resolve these functions correctly.
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/fomac/fomac.cpp:348-364
Timestamp: 2025-12-21T22:35:08.572Z
Learning: In the munich-quantum-toolkit/core repository's nanobind bindings, use `.sig("...")` on parameter arguments that have vector or container defaults (e.g., `"sites"_a.sig("...") = std::vector<fomac::Session::Device::Site>{}`) to prevent exposing mutable defaults in the Python API, which would be flagged as a code smell by Python linters. This pattern is preferred over removing `.sig("...")` even though it shows `...` in the stub signature.
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1383
File: python/mqt/core/ir/operations.pyi:9-16
Timestamp: 2025-12-15T01:59:17.023Z
Learning: In the munich-quantum-toolkit/core repository, stub files (.pyi) are auto-generated by nanobind's stubgen tool and should not be manually modified for style preferences, as changes would be overwritten during regeneration.
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1246
File: bindings/CMakeLists.txt:0-0
Timestamp: 2025-10-10T08:09:54.528Z
Learning: In the Munich Quantum Toolkit (MQT) Core project, scikit-build-core is configured with `wheel.install-dir = "mqt/core"` in pyproject.toml, which automatically prefixes all CMake `DESTINATION` paths with `mqt/core/` during wheel installation. Therefore, CMake install destinations are relative to the `mqt/core` package namespace, not `site-packages`.
📚 Learning: 2025-12-15T01:59:17.023Z
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1383
File: python/mqt/core/ir/operations.pyi:9-16
Timestamp: 2025-12-15T01:59:17.023Z
Learning: In the munich-quantum-toolkit/core repository, stub files (.pyi) are auto-generated by nanobind's stubgen tool and should not be manually modified for style preferences, as changes would be overwritten during regeneration.

Applied to files:

  • noxfile.py
  • python/mqt/qusat/pyqusat.pyi
📚 Learning: 2025-12-15T01:54:22.129Z
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/dd/register_matrix_dds.cpp:64-109
Timestamp: 2025-12-15T01:54:22.129Z
Learning: In the munich-quantum-toolkit/core repository, after migrating to nanobind, docstrings for Python bindings are now added directly in the C++ binding code (using R"pb(...)pb" syntax) and stub files (.pyi) are auto-generated using the `bindings/generate-stubs.sh` script. This replaces the previous pybind11 approach where docstrings were manually maintained in stub files.

Applied to files:

  • noxfile.py
  • bindings/bindings.cpp
📚 Learning: 2025-12-07T01:21:27.544Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1355
File: bindings/fomac/fomac.cpp:227-264
Timestamp: 2025-12-07T01:21:27.544Z
Learning: In the munich-quantum-toolkit/core repository, docstrings for Python bindings are added to the corresponding stub files (.pyi) rather than directly in the pybind11 C++ bindings code. This practice may change if the project adopts nanobind with automatic stub generation.

Applied to files:

  • noxfile.py
  • bindings/bindings.cpp
📚 Learning: 2025-12-21T22:35:08.572Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/fomac/fomac.cpp:348-364
Timestamp: 2025-12-21T22:35:08.572Z
Learning: In the munich-quantum-toolkit/core repository's nanobind bindings, use `.sig("...")` on parameter arguments that have vector or container defaults (e.g., `"sites"_a.sig("...") = std::vector<fomac::Session::Device::Site>{}`) to prevent exposing mutable defaults in the Python API, which would be flagged as a code smell by Python linters. This pattern is preferred over removing `.sig("...")` even though it shows `...` in the stub signature.

Applied to files:

  • python/mqt/qusat/pyqusat.pyi
  • bindings/bindings.cpp
📚 Learning: 2025-12-22T01:25:21.609Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/ir/register_permutation.cpp:153-171
Timestamp: 2025-12-22T01:25:21.609Z
Learning: In the munich-quantum-toolkit/core repository, when using nanobind iterator factory functions like `make_key_iterator` and `make_iterator`, the unqualified form (without explicit `nb::` prefix) is preferred. The clang-tidy configuration suggests removal of explicit namespace qualification, relying on ADL (Argument-Dependent Lookup) to resolve these functions correctly.

Applied to files:

  • bindings/bindings.cpp
📚 Learning: 2025-12-19T00:05:54.428Z
Learnt from: denialhaag
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/fomac/fomac.cpp:111-116
Timestamp: 2025-12-19T00:05:54.428Z
Learning: In the munich-quantum-toolkit/core repository after migrating to nanobind, lifetime management differs from pybind11: `nb::keep_alive<nurse, patient>()` does not exist in nanobind. Instead, use `nb::rv_policy::reference_internal` when binding methods that return objects referencing internal state of the parent object (e.g., Session::getDevices returning Device objects that depend on the Session). This tells nanobind to keep the parent alive while children exist.

Applied to files:

  • bindings/bindings.cpp
🪛 Cppcheck (2.19.0)
bindings/bindings.cpp

[information] 13-13: Include file

(missingIncludeSystem)


[information] 14-14: Include file

(missingIncludeSystem)


[information] 15-15: Include file

(missingIncludeSystem)


[information] 16-16: Include file

(missingIncludeSystem)


[information] 17-17: Include file

(missingIncludeSystem)


[information] 14-14: Include file

(missingInclude)


[information] 15-15: Include file

(missingInclude)


[information] 17-17: Include file

(missingIncludeSystem)


[information] 18-18: Include file

(missingIncludeSystem)


[information] 19-19: Include file

(missingIncludeSystem)


[information] 20-20: Include file

(missingIncludeSystem)


[information] 21-21: Include file

(missingIncludeSystem)


[information] 12-12: Include file

(missingInclude)


[information] 14-14: Include file

(missingIncludeSystem)


[information] 15-15: Include file

(missingIncludeSystem)


[information] 16-16: Include file

(missingIncludeSystem)


[information] 17-17: Include file

(missingIncludeSystem)


[style] 53-53: The function 'from_json' is never used.

(unusedFunction)


[style] 69-69: The function 'toString' is never used.

(unusedFunction)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: 🇨‌ Test 🏁 (windows-11-arm, msvc, Release) / 🏁 windows-11-arm msvc Release
  • GitHub Check: 🇨‌ Test 🐧 (ubuntu-24.04, gcc, Debug) / 🐧 ubuntu-24.04 gcc Debug
  • GitHub Check: 🇨‌ Test 🐧 (ubuntu-24.04, gcc, Release) / 🐧 ubuntu-24.04 gcc Release
  • GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
  • GitHub Check: 🇨‌ Test 🏁 (windows-2022, msvc, Debug) / 🏁 windows-2022 msvc Debug
  • GitHub Check: 🇨‌ Test 🏁 (windows-2022, msvc, Release) / 🏁 windows-2022 msvc Release
  • GitHub Check: 🇨‌ Test 🐧 (ubuntu-24.04-arm, gcc, Release) / 🐧 ubuntu-24.04-arm gcc Release
  • GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
  • GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
  • GitHub Check: 🇨‌ Test 🍎 (macos-14, clang, Debug) / 🍎 macos-14 clang Debug
  • GitHub Check: 🐍 Test (macos-15-intel) / 🐍 macos-15-intel
  • GitHub Check: 🇨‌ Coverage / 📈 Coverage
🔇 Additional comments (6)
noxfile.py (2)

177-202: LGTM on the stubs session structure.

The session correctly sets up the build environment, generates stubs using nanobind's stubgen, and includes the guard for empty .pyi files. Based on learnings, this approach aligns with the MQT Core repository's pattern where stubs are auto-generated by nanobind's stubgen tool.

One minor observation: the env dictionary is set at line 180 but not passed to the stubgen command at lines 192-202. Verify whether the stubgen command needs the UV_PROJECT_ENVIRONMENT to locate the installed package.


204-221: Proper empty file guard and progressive cleanup.

The guard at lines 206-208 properly handles the case when no .pyi files are generated. The progressive cleanup pattern (allowing exit code 1 for fixing passes, then strict final check) is a sound approach.

python/mqt/qusat/pyqusat.pyi (1)

9-20: Auto-generated stub with improved type signature.

Based on learnings, this stub file is auto-generated by nanobind's stubgen tool and should not be manually modified. The signature change from inputs: list[str] = [] to inputs: Sequence[str] | None = None is an improvement—it avoids the mutable default anti-pattern that Python linters flag.

The type annotations and docstrings correctly reflect the updated C++ bindings.

bindings/bindings.cpp (3)

14-21: Clean nanobind migration for includes and namespaces.

The namespace alias change from py to nb and the updated includes correctly reflect the migration from pybind11 to nanobind. The NOLINT(misc-include-cleaner) comments are appropriate since these STL headers are needed for type conversions even if not directly referenced.


71-72: LGTM on generate_dimacs binding.

Simple and clean binding with appropriate docstring.


44-45: Proper module initialization with nanobind.

The NB_MODULE macro and explicit import of mqt.core.ir correctly set up the module for nanobind. The import ensures the dependent types are available before binding functions that use QuantumComputation.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@codecov
Copy link

codecov bot commented Jan 14, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@denialhaag denialhaag enabled auto-merge (squash) January 14, 2026 23:30
@denialhaag denialhaag merged commit 0413664 into main Jan 14, 2026
27 of 28 checks passed
@denialhaag denialhaag deleted the nanobind branch January 14, 2026 23:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++ minor python Pull requests that update python code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants