MQT Core DD-based Simulator QDMI Device¶
Objective¶
MQT Core provides a QDMI device that is powered by a classical quantum circuit simulator based on decision diagrams (see the documentation of the DD Package). This functionality is exposed through the QDMI interface as a device, which can be used to classically simulate quantum programs.
Capabilities¶
The simulator device accepts OpenQASM 2, OpenQASM 3, and textual or binary QIR programs using the Base or Adaptive Profile. See the OpenQASM support table and QIR Support in the MQT for the supported operations, exact QDMI program formats, and payload contracts.
The device can perform weak simulation for every supported format, i.e., sample
from the distribution produced by the program. It can also perform strong
simulation for OpenQASM and eligible QIR Base or Adaptive Profile programs,
i.e., compute a representation of the full state vector. Set the
QDMI_DEVICE_JOB_PARAMETER_SHOTSNUM parameter to the desired number of shots
for weak simulation or to 0 for strong simulation.
State extraction defers terminal measurements without collapsing the returned state. Measurement-dependent computation, resets and subsequent operations on measured wires are unsupported. Adaptive QIR may still execute classical loops, branches, dynamic allocations and direct helpers; see the QIR extraction contract for result-use and lifetime rules.
For reproducible stochastic execution, set QDMI_DEVICE_JOB_PARAMETER_CUSTOM1
to a positive int seed. The Python API exposes the same parameter as
custom1. If custom1 is absent, the device seeds the random-number generator
from the system. The seed controls OpenQASM and QIR sampling. State extraction
does not use this seed.
Under the hood, the QDMI device imports OpenQASM into the compiler’s QC
representation, lowers it to QCO, and executes it with the QCO DD utilities.
This is the same compiler-backed simulation path exposed by
QCOProgram.
OpenQASM 3 output bits are undefined until written, so direct QDMI jobs with a partially initialized output register fail during import. The Qiskit backend preserves Qiskit’s zero-initialized classical-bit semantics by writing every classical bit before submitting its generated OpenQASM 3 program.
Sampling returns ordered bitstrings through QDMI_JOB_RESULT_SHOTS and their
histogram through QDMI_JOB_RESULT_HIST_KEYS and QDMI_JOB_RESULT_HIST_VALUES.
Both results come from the same samples, including mid-circuit measurements. QIR
Base or Adaptive programs with a static terminal measurement region can sample
one prepared DD; other QIR programs run once per shot. See the
QIR execution contract for eligibility and resource limits.
OpenQASM classical registers use reverse declaration order, with each register
most-significant-bit first. QIR samples follow the program’s recorded outputs.
Compile and execute QIR¶
The compiler can snapshot the DDSIM device as an all-to-all target, compile a program to QIR, and submit the resulting bitcode to the same device:
1from mqt.core.mlir import (
2 CompilerTarget,
3 PayloadFormat,
4 PayloadEncoding,
5 PayloadSpecification,
6 TargetEnvironment,
7 compile_program,
8)
9from mqt.core.qdmi import ProgramFormat
10from mqt.core.qdmi.driver import open_device
11
12bell_qasm = """OPENQASM 3.0;
13include "stdgates.inc";
14qubit[2] q;
15bit[2] result;
16h q[0];
17cx q[0], q[1];
18result = measure q;
19"""
20
21device = open_device("mqt.ddsim.default")
22target = CompilerTarget.from_device(device)
23payload = PayloadSpecification(PayloadFormat("qir", "2.1.0", "base", PayloadEncoding.BINARY))
24program = compile_program(
25 bell_qasm,
26 target_environment=TargetEnvironment(target, payload),
27)
28
29job = device.submit_job(
30 program.to_bitcode(),
31 ProgramFormat.QIR_BASE_MODULE,
32 num_shots=1024,
33 custom1=7,
34)
35job.wait()
36counts = job.get_counts()
37assert sum(counts.values()) == 1024
38assert set(counts) <= {"00", "11"}
39print(counts)
40print("First eight shots:", job.get_shots()[:8])
{'00': 503, '11': 521}
First eight shots: ['11', '00', '00', '11', '00', '11', '00', '11']