Skip to content

✨ Add MQT Core compilation and QIR export - #1027

Draft
simon1hofmann wants to merge 10 commits into
mainfrom
feat/mqt-core-compiler
Draft

simon1hofmann wants to merge 10 commits into
mainfrom
feat/mqt-core-compiler

Conversation

@simon1hofmann

@simon1hofmann simon1hofmann commented Sep 12, 2026 •

Copy link
Copy Markdown
Collaborator

🤖 AI text below 🤖

Description

MQT Bench currently sends all compilation requests through Qiskit. This adds an optional MQT Core compiler selected with compiler="mqt" in Python or --compiler mqt in the CLI. Qiskit remains the default compiler, and both paths return QuantumCircuit objects.

The change adds Core compilation at the independent, native-gate, and mapped levels, QIR export as LLVM text or bitcode, compiler provenance in exports, mirror support, an optional dependency extra, and a dedicated CI test session. The CLI also defaults to optimization level 2 when that option is omitted.

Usage

From a checkout of this PR, first install LLVM/MLIR 23.1 or newer and set MLIR_DIR using Core’s build instructions. The extra builds Core from commit 1a0c32f7cf3e264af9143146bf764fd669aa772d, combining Core main with controlled composites (#2565), generic fixed-parameter targets (#2575), and native trapped-ion gates (#2578). These APIs are not in Core 4.0.0:

python -m pip install -e ".[mqt]"
from mqt.bench import BenchmarkLevel, get_benchmark
from mqt.bench.targets import get_device, get_target_for_gateset

independent = get_benchmark(
    "ghz", BenchmarkLevel.INDEP, 3, compiler="mqt"
)

native = get_benchmark(
    "ghz",
    BenchmarkLevel.NATIVEGATES,
    3,
    target=get_target_for_gateset("ibm_falcon", 3),
    compiler="mqt",
)

mapped = get_benchmark(
    "ghz",
    BenchmarkLevel.MAPPED,
    3,
    target=get_device("iqm_crystal_5"),
    compiler="mqt",
    generate_mirror_circuit=True,
)

The level-specific functions get_benchmark_indep, get_benchmark_native_gates, and get_benchmark_mapped accept the same compiler and options arguments. The algorithm level does not compile.

Bench defaults to compilation seed 10 and four mapping trials. Pass Core’s CompilationOptions to override those defaults:

from mqt.core.mlir import CompilationOptions, MappingOptions

mapped = get_benchmark(
    "ghz", BenchmarkLevel.MAPPED, 3,
    target=get_device("iqm_crystal_5"), compiler="mqt",
    compiler_options=CompilationOptions(
        seed=17, mapping=MappingOptions(trials=8, iterations=2, lookahead=10),
    ),
)

Options also apply to mirror recompilation. Supplied objects replace Bench’s defaults; CompilationOptions() retains Core’s default seeds and CPU-dependent trial count. Timing, statistics, and routing search-memory controls are available through the same object. compiler_options is rejected for Qiskit compilation or the algorithm level. The benchmark-generation seed remains separate.

mqt-bench --compiler mqt --algorithm ghz --num-qubits 3 \
  --level mapped --target iqm_crystal_5 --save

The Core CLI filename contains _mqt_ and omits Qiskit's optimization level. QASM headers and QPY metadata record the compiler version. Qiskit recompilation updates an existing compiler record.

Level Core behavior
INDEP Decompose multi-controlled operations and run the default target-independent optimization pipeline.
NATIVEGATES Compile to the target gate set using all-to-all connectivity and the input circuit width; omit operations wider than the circuit and ignore physical gate placements.
MAPPED Compile to the device width, connectivity, and ordered native-gate placements.

QIR and LLVM output

The same mqt extra enables QIR export for circuits generated with either compiler. QIR uses LLVM IR; the output choices are:

Output format Encoding CLI behavior
qir or llvm LLVM text (.ll) Print to stdout, or write a file with --save.
qir-bitcode LLVM bitcode (.bc) Always write a file and print its path.
mqt-bench --compiler mqt --algorithm ghz --num-qubits 3 \
  --level indep --output-format qir

mqt-bench --compiler mqt --algorithm ghz_dynamic --num-qubits 3 \
  --level indep --output-format qir-bitcode --qir-profile adaptive
from pathlib import Path
from mqt.bench.output import OutputFormat, write_circuit

write_circuit(independent, Path("ghz.ll"), BenchmarkLevel.INDEP, OutputFormat.QIR)
write_circuit(
    independent, Path("ghz.bc"), BenchmarkLevel.INDEP, OutputFormat.QIR_BITCODE,
    qir_profile="base",
)

save_circuit also accepts qir_profile. The default is base; use adaptive for measurement feedback and supported classical control flow. LLVM text records the Bench header and Core exporter version using ; comments. Bitcode contains Core's QIR metadata without the Bench header.

Current limitations

  • Dependencies: The extra pins Core 1a0c32f7cf3e264af9143146bf764fd669aa772d and Qiskit >=2.5,<2.6. Installing Core currently requires a C++20 compiler and LLVM/MLIR 23.1 or newer. Replace the development pin with a release requirement before publishing Bench. Circuit generation and the public circuit representation still use Qiskit. Core imports permutations and array-valued definitions and preserves parameter identity/vector membership. Core handles MCMT definitions and controlled arithmetic composites within its existing decomposition pass, without extra pipeline passes or single-qubit merge calls; Bench passes circuits directly to Core without recursive preprocessing.
  • Targets: The adapter supports the bundled IBM Falcon/Eagle/Heron, IQM, Quantinuum, IonQ, Rigetti, and Clifford+T+rotations gate sets. IonQ uses Core’s native GPI/GPI2/MS/ZZ operations with parameters in turns. Canonical native input definitions survive repeated compilation with their original parameters, including symbolic values; a single native entangler is no longer expanded and resynthesized. Rigetti pulses use fixed RX capabilities, with their target names restored on export. Standard target parameters may be independent free parameters or finite fixed values. Core derives fixed-pulse synthesis from any distinct RX/RY/RZ axis pair with one arbitrary axis; fixed RZ with arbitrary RX or RY is supported too. Relations between target parameters, unsupported instructions, and unavailable synthesis bases produce errors. Core does not provide approximate Clifford+T synthesis, and there is no fallback transpilation. Symbolic sequence export retains the existing limitation in Core #2559.
  • Optimization settings: Leave opt_level at its default of 2. Core uses its own fixed pipeline; this is not an equivalence to Qiskit optimization level 2. Values 0, 1, and 3 are rejected for Core compilation.
  • Layout metadata: Mapped results use physical circuit wires and preserve classical measurement destinations. The pinned Core commit does not include the layout-reporting work in Core #2553; Bench does not attach TranspileLayout metadata or expose initial-placement controls.
  • Control flow and mirrors: Dynamic circuits and structured loops are limited to Core's supported translation and target capabilities; loops may be unrolled to satisfy the target. Mirrors require an invertible circuit after final measurements are removed. The compiled circuit is mirrored across a barrier and, when a target is supplied, compiled again with Core.
  • QIR export: All circuit parameters must be bound. Unsupported profiles or lowering requests raise an export error; failed lowering leaves an existing destination file intact. Export lowers the supplied circuit without another optimization or mapping pipeline, but QIR lowering may decompose gates and assign QIR resource identifiers. The output is not guaranteed to preserve a device-native gate set or physical qubit numbering. Execution requires a runtime that supports the emitted QIS calls, QIR version, and profile capabilities; LLVM text/bitcode is not a standalone executable.
  • Reproducibility: Bench fixes the default seed and mapping trial count. Explicit options may opt back into Core defaults. Fixed settings do not guarantee identical results across Core versions or platforms. The CLI uses Bench defaults; custom compiler options are a Python API feature.

Validation

Tested locally with Python 3.13, Core 1a0c32f7cf3e264af9143146bf764fd669aa772d, and Qiskit 2.5.2 on macOS:

  • Full suite with branch coverage: 389 passed, including native gate round trips, symbolic native inputs, and one-qubit circuits against wider target gate sets.
  • Full repository lint and type checking passed with the new pinned dependency.
  • Tests cover one-qubit inputs against wider targets, repeated numeric and symbolic native-gate compilation, IonQ native names and units, Rigetti pulse aliases, all six arbitrary/fixed rotation-axis combinations, symbolic parameters, global phase, physical placements, and rejected target definitions. Existing controlled-composite, QIR/LLVM, mirror, options, and import regressions remain covered.

Coverage from the full suite:

Module Statement coverage Branch coverage
MQT Core adapter 100% 100%
Circuit export 100% 100%
CLI 93.5% 88.9%
Entire package 99.4% 96.9%

The existing Core CI job runs the optional compiler tests and type checks and uploads its coverage with the other Python reports. Hosted CI for this pin update is pending.

Codex assisted with the implementation, tests, documentation, and this description. The PR remains a draft.

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.
  • 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.
  • I have reviewed my own code changes.

If PR contains AI-assisted content:

  • Any agent that created, edited, or submitted GitHub content was explicitly authorized for that scope, as required by our AI Usage Guidelines.
  • Every agent-authored or agent-edited public text body begins with the visible disclosure 🤖 *AI text below* 🤖 (titles are exempt).
  • I have disclosed AI assistance in the PR description.
  • I confirm that I have personally reviewed and understood all AI-generated content, and accept full responsibility for it.

@codecov

codecov Bot commented Sep 12, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@simon1hofmann simon1hofmann changed the title ✨ Add MQT Core as an optional compiler ✨ Add MQT Core compilation and QIR export Sep 12, 2026
@simon1hofmann simon1hofmann added feature New feature or request python Pull requests that update Python code mqt.bench Issues that affect mqt.bench labels Sep 17, 2026
Assisted-by: GPT-6 via Codex
Assisted-by: GPT-6 via Codex
Assisted-by: GPT-6 via Codex
Include the MQT Core coverage report in the combined CI upload.

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Pin Core main plus the controlled-composite fix and expose CompilationOptions
with a fixed seed and mapping trial count, including mirror recompilation.
Remove circuit preprocessing and parameter remapping now handled by Core.

Configure MLIR for CI source builds and cover option forwarding,
reproducibility, and parameter-vector identity.

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Use the Core fix that handles composite controls within the existing
decomposition pass and avoids extra single-qubit merging.

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

The reusable lint job configures sccache without installing it when stub
checks are disabled, so building Core for ty fails. Run the existing ty hook
in the Core job, which already provides MLIR and builds the dependency.
Require that job whenever either tests or lint are requested.

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Pin the integrated Core changes for native trapped-ion gates and generic
fixed RX/RY/RZ capabilities while preserving controlled-composite support.
Restore Rigetti pulse names during target validation and cover symbolic
parameters, native semantics, and rejected target definitions.

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Use canonical native definitions so Core preserves numeric and symbolic
pulses through repeated compilation. Omit capabilities wider than the
input circuit at the native-gate level. Pin Core with the matching import
and fixed-parameter compatibility fixes.

Assisted-by: GPT-6 via Codex
🤖 *AI text below* 🤖

Pin the rebased integration branch with the fixed-parameter and native-gate CI tests. Keep the dependency lockfile and documented revision aligned.

Assisted-by: GPT-6 via Codex
@simon1hofmann simon1hofmann self-assigned this Sep 24, 2026

This branch has not been deployed

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

Labels

feature New feature or request mqt.bench Issues that affect mqt.bench python Pull requests that update Python code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant