OpenQASM input and output¶
MQT Core accepts OpenQASM as a compiler input and can export structured programs from the QC dialect.
The OpenQASM specification defines the language. This page describes the subset supported by MQT Core.
Import OpenQASM¶
The frontend parses and validates the source before translating it directly to QC. The C++ compiler API accepts strings and files:
auto fromString = mlir::QCProgram::fromQASMString(source);
auto fromFile = mlir::QCProgram::fromQASMFile("program.qasm");
Python provides the corresponding constructors:
from mqt.core.mlir import QCProgram
from_string = QCProgram.from_qasm_str(source)
from_file = QCProgram.from_qasm_file("program.qasm")
mqt-cc recognizes .qasm files automatically. Use --input-format=qasm when
the filename does not identify the format:
mqt-cc program.qasm
mqt-cc --input-format=qasm program.txt
Input support¶
OpenQASM concept |
Support and restrictions |
|---|---|
Versions and includes |
Versionless input and versions 3.0 and 3.1 use the maintained OpenQASM profile. |
Classical types |
Unsized |
Outputs |
Explicit |
Gates |
Language gates, the standard libraries, custom gates, broadcasting, and |
Quantum statements |
Measurement, reset, barrier, logical qubits, and physical qubits are supported. The QC target rejects programs that mix logical allocation with physical qubits. |
Expressions |
Scalar arithmetic, comparisons, Boolean expressions, and the supported math functions are type checked before translation. |
Structured control |
|
Dynamic indexing |
Dynamic qubit and bit indices are supported on input. The generated QC includes bounds checks and structured dispatch where needed. |
Unsupported language areas |
Subroutines, |
Syntax and semantic diagnostics retain source locations and include stacks. Runtime integer preconditions and dynamic-index bounds are represented explicitly in QC. This safety machinery is supported by the normal compiler and QIR paths, but it is intentionally outside the export subset described below.
Bit outputs use memref<nxi1> in QC, including scalar bit as memref<1xi1>.
Other scalar outputs use builtin MLIR scalar types. A scalar qubit lowers to
qc.alloc, while qubit[1] remains a one-element qubit register.
Export OpenQASM¶
The exporter prints validated QC and SCF operations. The translation is failure-atomic: it prepares the complete source before writing to the requested stream.
Use the translation API for a ModuleOp:
#include "mlir/Dialect/QC/Translation/TranslateQCToOpenQASM3.h"
auto source = mlir::qc::translateQCToOpenQASM3(moduleOp);
if (mlir::failed(source)) {
// An MLIR diagnostic describes the unsupported operation.
}
The compiler API returns an owned textual program:
auto qc = mlir::QCProgram::fromQASMFile("input.qasm");
auto direct = qc->toOpenQASM3(); // Export without QCO optimization.
direct->write("direct.qasm");
auto reimported = mlir::runDefaultPipeline(
mlir::CompilerInput{*direct}, mlir::ProgramFormat::QCImport);
auto optimized = mlir::runDefaultPipeline(
mlir::CompilerInput{std::move(*qc)}, mlir::ProgramFormat::OpenQASM3);
Python exposes both forms:
from mqt.core.mlir import OutputFormat, QCProgram, compile_program
qc = QCProgram.from_qasm_file("input.qasm")
direct = qc.to_openqasm3()
print(direct.source)
direct.write("direct.qasm")
optimized = compile_program("input.qasm", output=OutputFormat.OPENQASM3)
optimized.write("optimized.qasm")
The command-line driver writes to standard output unless -o is given:
mqt-cc input.qasm --emit=openqasm3
mqt-cc input.qasm --emit=openqasm3 -o optimized.qasm
The compiler-pipeline path performs target compilation when requested, runs the
QCO optimization pipeline, converts back to QC, and then exports. Calling
to_openqasm3() or
mlir::QCProgram::toOpenQASM3 applies the QC cleanup pipeline but
bypasses that QCO optimization round trip.
Export and round-trip support¶
QC or MLIR concept |
Export support |
|---|---|
Qubits and classical bits |
Logical and physical qubits, scalar allocations, and static rank-one qubit or |
Quantum operations |
Measurement, reset, barrier, deallocation, global phase, and QC unitary operations. The exporter uses standard gates where available; for example, |
Gate modifiers |
Nested |
Scalar values |
|
Structured control |
Result-free |
Results |
Multiple scalar and bit-register outputs using the canonical type and naming rules below. |
The exporter writes an OpenQASM 3.1 version declaration and includes
stdgates.inc. Gates in MQT Core’s compatibility catalog, such as r, rzz,
and ecr, receive definitions under their catalog names. Strict consumers use
those definitions. MQT Core’s default compatibility mode recognizes a definition
with the catalog name and signature and imports calls directly as the
corresponding native QC operation; the definition body is deliberately ignored.
A same-name definition with a mismatched signature is rejected. Strict mode
always analyzes the custom definition normally.
The _mqt_ prefix is reserved for generated composite-modifier gates,
temporaries, and collision-safe identifiers. Existing classical-register
allocation names are reused when valid and distinct from catalog gates; scalar
output names are generated deterministically.
Output types follow a deliberately small canonical mapping:
QC result |
OpenQASM output |
|---|---|
|
|
|
|
Other |
|
|
|
|
|
A lone constant-zero i64 result is treated as the frontend’s status return and
is not emitted. Import and export do not preserve uint, angle spelling,
scalar-versus-one-element bit spelling, or scalar output names. Unsigned
constants therefore normalize to int. Operations whose signedness affects
their meaning, such as unsigned division, comparison, or conversion, are
rejected instead of being approximated. Integer sign extension and truncation
are also rejected because OpenQASM scalar casts have different value semantics.
Emitted scalar casts use standard OpenQASM conversion syntax. The MQT Core frontend does not yet parse that syntax, so cast-containing output is outside the current MQT strict round-trip subset.
Export limitations¶
Export accepts exactly one defined, argument-free function. It rejects calls,
arbitrary CFGs, multi-block SCF regions, dynamic indices or ranges, general
memrefs, unsupported integer widths, packed bit-vector operations, unknown
operations, and non-unitary content inside modifier regions. SCF results,
loop-carried values, nonempty scf.yield, and arith.select are outside the
export subset. Multi-operation modifier bodies must have a target qubit and
cannot capture additional qubits from an enclosing scope.
The exporter does not reconstruct the runtime checks created for dynamic indices or checked integer arithmetic. Surviving assertions, checked-index control flow, or live poison values cause an explicit diagnostic. Programs with static qubit and bit indices and without scalar casts can be exported and parsed again through the strict frontend. Programs that rely on the input safety machinery must continue through another output path such as QIR.
Important
The compiler removes dead code. A circuit that only prepares a state has no observable effect and may be removed by optimization. Measure the relevant qubits and return the results when compiling a program for execution.