Add Python bindings for MQT MLIR compiler collection#1770
Conversation
Exposes the full QASM → QC → QCO (→ QIR) compiler pipeline from Python with a composable, stage-by-stage API: load_qasm(qasm) → QC-dialect MLIR string qc_to_qco(qc_ir) → QCO-dialect MLIR string compile(qasm, **opts) → final IR string (or stage dict) compile_with_record(...) → typed CompilationResult dataclass Key design decisions: - Each pipeline stage is independently callable, matching the issue's requested loadQASM / translateToQC / transformToQCO decomposition. - compile_with_record() returns a typed dataclass (CompilationResult) rather than a plain dict, giving IDE completion on all 10 stage fields. - MQTContext subclasses mlir.ir.Context and pre-registers all MQT dialects, so callers can parse and manipulate IR directly without manual setup. - compile() accepts capture_intermediates=True to return a raw dict of stage snapshots when callers need programmatic access. - Python bindings activate automatically when BUILD_MQT_CORE_BINDINGS and BUILD_MQT_CORE_MLIR are both on; no separate cmake flag required. New files: mlir/include/mlir/CAPI/Dialects.h — C API dialect registration mlir/lib/CAPI/Dialects.cpp — implementation mlir/lib/CAPI/CMakeLists.txt — CAPI library target mlir/python/MQTCoreMLIRModule.cpp — nanobind extension mlir/python/CMakeLists.txt — Python module build config mlir/python/mqt/core/mlir/__init__.py — public API mlir/python/mqt/core/mlir/_pipeline.py — CompilationResult, MQTContext test/python/mlir/test_mlir_bindings.py — 30 pytest tests
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…zation, add docstrings
…N unset, guard mlir imports - mlir/CMakeLists.txt: downgrade FATAL_ERROR to STATUS + skip when MLIR_ENABLE_BINDINGS_PYTHON is not set, so builds without Python-enabled MLIR toolchains still configure cleanly - test/python/mlir/test_mlir_bindings.py: add pytest.importorskip guards so the test module is skipped gracefully when mlir is not installed - pyproject.toml: exclude test/python/mlir/** from ty type-checking and allow E402 in those files since importorskip must precede the imports
|
Hi @denialhaag , I've had my first attempt at the implementation. Would be great to get your feedback! |
There was a problem hiding this comment.
Thanks for this first draft for adding Python bindings to mqt-cc, @alyabouzaid! 🙂
This PR will require some more input from you before it is merge-ready. You can find some more details in the comments below.
In addition to the comments below, please ensure the tests you are adding are also run in our CI. For this to work, BUILD_MQT_CORE_MLIR will have to be enabled here:
Lines 132 to 136 in 946116d
ci.yml:
setup-mlir: true
llvm-version: 22.1.0There was a problem hiding this comment.
It would be nice to structure this more like https://github.com/llvm/llvm-project/tree/main/mlir/include/mlir/CAPI.
There was a problem hiding this comment.
It would be nice to structure this more like https://github.com/llvm/llvm-project/tree/main/mlir/lib/CAPI.
There was a problem hiding this comment.
The bindings should live in https://github.com/munich-quantum-toolkit/core/tree/main/bindings.
There was a problem hiding this comment.
It would be nice to extend this. All dialects, passes, and conversions should be accessible. It would also be nice to provide a compile_program() function that accepts an OpenQASM program and pushes it through the entire pipeline (see the steps in CompilerPipeline.cpp).
| if(NOT MLIR_ENABLE_BINDINGS_PYTHON) | ||
| message(STATUS "MQT MLIR Python bindings skipped: MLIR was not built with " | ||
| "MLIR_ENABLE_BINDINGS_PYTHON=ON.") |
There was a problem hiding this comment.
We should enable MLIR_ENABLE_BINDINGS_PYTHON by default if BUILD_MQT_CORE_MLIR and BUILD_MQT_CORE_BINDINGS are both enabled.
There was a problem hiding this comment.
It would be nice to condense the tests a bit and to make them more assertive. Per functionality and circuit, a single test that checks for the actual output would be nice.
Closes #1693
Summary
Exposes the full QASM to QC to QCO (optionally QIR) compiler pipeline from Python with a composable, stage-by-stage API:
Design
loadQASM / translateToQC / transformToQCOdecomposition described in the issue.compile_with_record()returns a typedCompilationResultdataclass with a field for each of the 10CompilationRecordstages, giving IDE completion and avoiding stringly-typed dict access.MQTContextsubclassesmlir.ir.Contextand pre-registers all MQT dialects, so callers can parse and manipulate IR directly without manual setup.compile()also acceptscapture_intermediates=Truefor programmatic access to stage snapshots as a plain dict.BUILD_MQT_CORE_MLIRandBUILD_MQT_CORE_BINDINGSare on.New files
mlir/include/mlir/CAPI/Dialects.hmqtRegisterAllDialects,mqtRegisterAllPassesmlir/lib/CAPI/Dialects.cppmlir/lib/CAPI/CMakeLists.txtMQTMLIRCoreDialectsCAPIlibrary targetmlir/python/MQTCoreMLIRModule.cppload_qasmandcompilemlir/python/CMakeLists.txtmlir/python/mqt/core/mlir/__init__.pymlir/python/mqt/core/mlir/_pipeline.pyMQTContext,CompilationResult,qc_to_qco,compile_with_recordtest/python/mlir/test_mlir_bindings.pyTest plan
test/python/mlir/test_mlir_bindings.pypasses withBUILD_MQT_CORE_MLIR=ONandBUILD_MQT_CORE_BINDINGS=ONload_qasmreturns valid QC dialect IR for Bell and single-qubit circuitsqc_to_qcochained fromload_qasmproduces QCO dialect IRcompilereturns final IR string;capture_intermediates=Truereturns all 11 keyscompile_with_recordreturns aCompilationResultwith all fields populatedMQTContextworks as a plainmlir.ir.Contextcontext manager