Using MQSSCI as a Library¶
The Passes page shows how to run passes through the mqss-opt/mqss-cc command-line
tools. This page is for the case where you don’t want a separate command-line step at all — you want
your own C++ program to compile a circuit directly.
Before You Start¶
Your project needs to link against the MQSSCI library first. That’s a one-time CMake setup step —
see
Integrating the MQSS Quantum Compilation Suite within your project
for how to fetch MQSSCI with FetchContent and link your target against mqss-ci::mqss-ci. The
rest of this page assumes that step is done and focuses purely on the C++ side.
A Minimal Example¶
mqss::mqssci::MQSSCompiler is the simplest way to compile a circuit: one header, one class, one
call. It hides dialect registration, MLIR context setup, and pipeline assembly behind a single
compile call.
#include "MQSSCIInterfaces/MQSSCompiler.h"
mqss::mqssci::MQSSCompiler compiler;
mqss::mqssci::CompilerOptions opts;
opts.optimization_level = mqss::mqssci::OptLevel::O1; // O1, O2, or O3 — selects the preset pipeline
opts.result_format = mqss::mqssci::ResultFormat::OPENQASM2; // or QIR, QIRBASE, QIRADAPTIVE, QIRFULL, IQMJSON
opts.verify = false; // optional; see Verifying the Compiled Output below
std::optional<std::string> qasm =
compiler.compile("path/to/circuit.qke", {"rx", "cz", "rz"}, opts); // Use compileSource() to parse source string
// The input may also be an OpenQASM 2 file/string; see "OpenQASM 2 as Input".
if (!qasm) {
// Compilation failed. MQSSCompiler has already reported a diagnostic through
// MLIR's diagnostic engine — it never throws or aborts the process.
return 1;
}
llvm::outs() << *qasm;
Walking through it:
#include "MQSSCIInterfaces/MQSSCompiler.h"is the only header you need for this path.mqss::mqssci::CompilerOptionsconfigures the run:optimization_levelselects theO1/O2/O3preset pipeline (see Pass Pipelines for what each includes), andresult_formatselects the output format — one of themqss::mqssci::ResultFormatenumerators:OPENQASM2,QIR,QIRBASE,QIRADAPTIVE,QIRFULL, orIQMJSON.The second argument to
compile({"rx", "cz", "rz"}above) is the target’s native-gate set, which the circuit is decomposed into. See Choosing a Native-Gate Set and Backend below for the alternatives.compilereturnsstd::optional<std::string>, not the compiled circuit directly. On success, it holds the compiled output; on failure it’sstd::nullopt, and the reason has already been reported viamlir::emitError— MQSSCI is built without C++ exceptions, so a failed compile never throws or crashes your program, it just returns an empty optional.
Verifying the Compiled Output¶
Set opts.verify = true to have MQSSCompiler check that native-gate decomposition didn’t change
what the circuit computes — the same circuit-equivalence checking described in
Verifying Circuit Correctness, applied automatically during compile/
compileSource rather than as a separate --mqssci-verify flag on mqss-opt.
mqss::mqssci::CompilerOptions opts;
opts.optimization_level = mqss::mqssci::OptLevel::O1;
opts.result_format = mqss::mqssci::ResultFormat::OPENQASM2;
opts.verify = true;
std::optional<std::string> qasm =
compiler.compile("path/to/circuit.qke", {"rx", "cz", "rz"}, opts);
This is a hard gate, not just a warning: if verification finds that decomposition broke equivalence,
compile/compileSource return std::nullopt — same as any other failure — rather than handing
you output that silently computes the wrong thing. The default is opts.verify = false, so turning
it on is opt-in and costs nothing unless you ask for it.
Choosing a Native-Gate Set and Backend¶
MQSSCompiler has no built-in gate sets: you always pass the target’s native-gate set yourself, or
none at all to skip decomposition:
// Decompose into an explicit native-gate set.
compiler.compile("path/to/circuit.qke", {"rx", "cz", "rz"}, opts);
// Skip native-gate decomposition entirely.
compiler.compile("path/to/circuit.qke", opts);
For reference, these are the native-gate sets of some common targets:
Target |
Native-gate set |
|---|---|
IQM |
|
planqc |
|
WMI |
|
The gate mnemonics in a native-gate set are the ones the BasisConversionPass recognizes. See
Supported Gate Mnemonics for the full list of mnemonics, the
Quake operation each one maps to, and their constraints (parameter count, controls, adjointness). A
few device-specific names are accepted as aliases for a mnemonic, e.g. r for prx.
The backend_name argument¶
The full signature also takes a backend_name and a qubit_connectivity map:
compiler.compile("path/to/circuit.qke", "LRZ", {"r", "cz"}, qubit_connectivity, opts);
backend_name only changes anything when it is "LRZ" (case-sensitive); any other value, including
"", compiles exactly the same way.
With "LRZ", OpenQASM2 output uses your own spelling for any aliased gate in the native-gate set.
For example, passing r produces r(...) calls in the output, not phased_rx(...), which is the
name prx is otherwise emitted under. With any other backend name, the output keeps phased_rx.
This renaming only applies to OPENQASM2 output.
qubit_connectivity lists the coupled qubit pairs of a target with restricted connectivity; the
circuit is mapped onto it before decomposition. See MQSSCIInterfaces/MQSSCompiler.h for all four
compile overloads and their compileSource counterparts.
Discovering Supported Input Formats¶
MQSSCompiler::getSupportedInputFormats() is a static method that returns the display name of every
input format MQSSCI’s mqss::mqssci::InputFormat enum defines:
for (std::string_view name : mqss::mqssci::MQSSCompiler::getSupportedInputFormats()) {
llvm::outs() << name << "\n";
}
It returns a std::vector<std::string_view> (currently
{"cudaq-quake", "catalyst-quantum", "OpenQASM2"}) — useful for validating a user-supplied format
name or listing the options in a CLI’s --help output. Note that compile/compileSource
currently only accept the cudaq-quake dialect and OpenQASM 2 (see
OpenQASM 2 as Input), regardless of what this list reports; see the note at the
top of MQSSCompiler.cpp.
Advanced: Building a Custom Pipeline Yourself¶
MQSSCompiler covers the common case: a preset optimization level, a fixed native-gate set, and one
of its supported output formats. If you need to run individual passes, pass pass-specific options
(e.g. CommonGateCancellationPassOptions), or assemble a pipeline MQSSCompiler doesn’t support,
drop down to the same mlir::OpPassManager-based API MQSSCompiler itself is built on:
#include "Passes/Transforms/Dialects.h"
#include "Passes/Transforms/Pipelines.h"
#include "Passes/CodeGen/CodeGenPasses.h"
// 1. Get an MLIRContext with every dialect MQSSCI's passes need already loaded.
std::unique_ptr<mlir::MLIRContext> context = mqss::mqssci::opt::createMQSSContext();
// 2. Parse your MLIR file into a module
auto module = mlir::parseSourceFile<mlir::ModuleOp>("path/to/file", context.get());
if (!module) {
llvm::errs() << "failed to parse MLIR file\n";
}
// 3. Build a pass manager and add passes to the pipeline
mlir::PassManager pm(context.get());
CommonGateCancellationPassOptions CancelOpts;
CommonCommutePassOptions CommuteOpts;
CancelOpts.mode = "CancelGate";
CommuteOpts.mode = "CX-RX";
pm.addPass(CommonGateCancellationPass(CancelOpts));
pm.addPass(CommonCommutePass(CommuteOpts));
// Run the Pass Pipeline
if (mlir::failed(pm.run(*module))) {
llvm::errs() << "Compiler: Pipeline failed\n";
}
// 4. Lower the optimized module to OpenQASM 2
pm.addPass(mqss::mqssci::codegen::QuakeToQASM2Pass());
if (mlir::failed(pm.run(*module))) {
llvm::errs() << "Compiler: Conversion of Quake to QASM2 failed\n";
}
mqss::mqssci::opt::createMQSSContext() is a convenience that registers every MQSSCI dialect and
returns a ready-to-use MLIRContext in one call — equivalent to building a DialectRegistry with
registerMQSSDialects, constructing an MLIRContext from it, and calling
loadAllAvailableDialects() yourself.
If you’d rather capture the OpenQASM output in a string instead of printing it, QuakeToQASM2Pass
also accepts an llvm::raw_ostream&:
std::string qasm;
llvm::raw_string_ostream os(qasm);
pm.addPass(mqss::mqssci::codegen::QuakeToQASM2Pass(os));
For the full list of available passes, their options, and valid option values, see Passes.
Testing the Interface¶
MQSSCompiler has its own testing framework at
tests/unittests/MQSSCIInterfaces/test_Interface.cpp. It builds automatically as part of the normal
build (via scripts/build.sh / make target) — run it with:
make test-interfaces
For what the interfaces testing framework covers and how to extend it, see Testing the Interface in the developer guide.