Skip to content

Add Python bindings for MQT MLIR compiler collection#1770

Open
alyabouzaid wants to merge 9 commits into
munich-quantum-toolkit:mainfrom
alyabouzaid:feat/mlir-python-bindings-v2
Open

Add Python bindings for MQT MLIR compiler collection#1770
alyabouzaid wants to merge 9 commits into
munich-quantum-toolkit:mainfrom
alyabouzaid:feat/mlir-python-bindings-v2

Conversation

@alyabouzaid
Copy link
Copy Markdown

Closes #1693

Summary

Exposes the full QASM to QC to QCO (optionally QIR) compiler pipeline from Python with a composable, stage-by-stage API:

from mqt.core.mlir import load_qasm, qc_to_qco, compile, compile_with_record

# Individual stages
qc_ir  = load_qasm(qasm_string)   # QASM -> QC dialect MLIR
qco_ir = qc_to_qco(qc_ir)        # QC dialect -> QCO dialect

# Full pipeline
result = compile(qasm_string)

# Full pipeline with all intermediate snapshots
rec = compile_with_record(qasm_string)
print(rec.after_qco_conversion)   # typed field, not a dict key

Design

  • Each pipeline stage is independently callable, matching the loadQASM / translateToQC / transformToQCO decomposition described in the issue.
  • compile_with_record() returns a typed CompilationResult dataclass with a field for each of the 10 CompilationRecord stages, giving IDE completion and avoiding stringly-typed dict access.
  • MQTContext subclasses mlir.ir.Context and pre-registers all MQT dialects, so callers can parse and manipulate IR directly without manual setup.
  • compile() also accepts capture_intermediates=True for programmatic access to stage snapshots as a plain dict.
  • No separate CMake option: the bindings activate automatically when both BUILD_MQT_CORE_MLIR and BUILD_MQT_CORE_BINDINGS are on.

New files

File Purpose
mlir/include/mlir/CAPI/Dialects.h C API header: mqtRegisterAllDialects, mqtRegisterAllPasses
mlir/lib/CAPI/Dialects.cpp Registers QC, QCO, QTensor, Arith, Func, MemRef, SCF dialects
mlir/lib/CAPI/CMakeLists.txt MQTMLIRCoreDialectsCAPI library target
mlir/python/MQTCoreMLIRModule.cpp nanobind extension with load_qasm and compile
mlir/python/CMakeLists.txt Extension build config and CAPI aggregation
mlir/python/mqt/core/mlir/__init__.py Public API surface
mlir/python/mqt/core/mlir/_pipeline.py MQTContext, CompilationResult, qc_to_qco, compile_with_record
test/python/mlir/test_mlir_bindings.py 30 pytest tests covering all public functions

Test plan

  • test/python/mlir/test_mlir_bindings.py passes with BUILD_MQT_CORE_MLIR=ON and BUILD_MQT_CORE_BINDINGS=ON
  • load_qasm returns valid QC dialect IR for Bell and single-qubit circuits
  • qc_to_qco chained from load_qasm produces QCO dialect IR
  • compile returns final IR string; capture_intermediates=True returns all 11 keys
  • compile_with_record returns a CompilationResult with all fields populated
  • MQTContext works as a plain mlir.ir.Context context manager

Aly Abouzaid and others added 2 commits June 5, 2026 16:50
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
Copy link
Copy Markdown

codecov Bot commented Jun 6, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Aly Abouzaid and others added 7 commits June 5, 2026 23:12
…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
@alyabouzaid
Copy link
Copy Markdown
Author

Hi @denialhaag , I've had my first attempt at the implementation. Would be great to get your feedback!

Copy link
Copy Markdown
Member

@denialhaag denialhaag left a comment

Choose a reason for hiding this comment

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

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:

core/pyproject.toml

Lines 132 to 136 in 946116d

[tool.scikit-build.cmake.define]
BUILD_MQT_CORE_BINDINGS = "ON"
BUILD_MQT_CORE_TESTS = "OFF"
BUILD_MQT_CORE_SHARED_LIBS = "ON"
BUILD_MQT_CORE_MLIR = "OFF"
Furthermore, it will be necessary to add the following to the Python jobs in ci.yml:

setup-mlir: true
llvm-version: 22.1.0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would be nice to structure this more like https://github.com/llvm/llvm-project/tree/main/mlir/include/mlir/CAPI.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would be nice to structure this more like https://github.com/llvm/llvm-project/tree/main/mlir/lib/CAPI.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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).

Comment thread mlir/CMakeLists.txt
Comment on lines +46 to +48
if(NOT MLIR_ENABLE_BINDINGS_PYTHON)
message(STATUS "MQT MLIR Python bindings skipped: MLIR was not built with "
"MLIR_ENABLE_BINDINGS_PYTHON=ON.")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should enable MLIR_ENABLE_BINDINGS_PYTHON by default if BUILD_MQT_CORE_MLIR and BUILD_MQT_CORE_BINDINGS are both enabled.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨ Enable Python Bindings for the MQT Compiler Collection

2 participants