Running End-to-End Quantum Workloads on IQM Hardware via QDMI-on-IQM¶
Welcome to the end-to-end tutorial. This guide walks you step by step through
driving real quantum workloads on IQM systems using QDMI-on-IQM and the packaged
IQMBackend.
Whether you want to estimate molecular ground-state energies with QSCI or benchmark hardware with MQT Bench, the example scripts in this repository provide a practical starting point. This tutorial focuses on two application areas:
Quantum chemistry: using QSCI and Qiskit Nature to estimate the ground-state energy of an H2 molecule.
Benchmarking: running MQT Bench programs such as GHZ states, Deutsch-Jozsa, QFT, graph states, W states, Grover, or Quantum Phase Estimation.
Important
The example scripts live in the QDMI-on-IQM repository and are not shipped with
the distribution of the iqm-qdmi Python package on PyPI.
Hence, you need to download or clone the repository to access them. You can do
so by running the following command in your terminal:
git clone https://github.com/iqm-finland/QDMI-on-IQM.git
Configure Your Environment¶
The IQM-backed path relies on a small environment-variable contract to authenticate and route your jobs. Before running any of the examples, make sure the following variables are set as needed:
IQM_BASE_URL: The endpoint of the IQM server you are targeting (e.g.,https://resonance.iqm.techfor IQM Resonance).IQM_TOKEN: Your authentication token.IQM_QC_ALIAS: Optional explicit selection of the target quantum computer.
For the full set of authentication options available when configuring C++ sessions directly, see Authentication Methods in the Usage Guide.
You can either run the full suite of examples using the dedicated nox session
or individually execute the scripts from the command line.
# Run the entire suite
uvx nox -s examples
# Run specific examples
./examples/qsci_h2.py --shots 256 --maxiter 5 --cutoff 4
./examples/mqt_bench.py --benchmark ghz --shots 128
Quantum Chemistry¶
Our first workload is a quantum chemistry application that estimates the
ground-state energy of a hydrogen molecule. The script examples/qsci_h2.py
follows a Quantum-Selected Configuration Interaction-style workflow: a
hybrid quantum-classical approach that combines variational optimization,
circuit sampling, and classical diagonalization in a reduced subspace. To build
the chemistry problem, it leans on Qiskit Nature to map an
electronic-structure model to qubit operators.
By running examples/qsci_h2.py, you will:
Build an electronic-structure problem for an H2 molecule.
Map the physical system to qubits with Qiskit Nature.
Optimize a UCCSD ansatz against an IQM backend.
Sample the circuit to gather bitstrings.
Reconstruct the energy estimate.
The script prints an execution trace along the way, showing the progress of the demonstration. QSCI is a great first end-to-end workload beyond a simple toy circuit.
Note
The QSCI example depends on PySCF for classical chemistry calculations, and PySCF is not supported on Windows.
MQT Bench Programs¶
To understand how the backend behaves on standard programs, we move on to
MQT Bench. MQT Bench is an open-source benchmark suite that
collects representative quantum algorithms across several abstraction levels. In
this repository, the benchmark scripts show how to generate those programs,
transpile them for the selected target, execute them through
QDMISampler, and inspect the
resulting bitstring distributions.
The examples/mqt_bench.py entrypoint currently covers the following
algorithms:
ghz: Prepares a GHZ state, a highly entangled state that serves as a strong baseline test for multi-qubit entanglement fidelity.dj: Implements the Deutsch-Jozsa algorithm, one of the earliest examples of a quantum algorithm with an exponential query advantage over its classical counterpart.qft: Computes the Quantum Fourier Transform, a central building block used in algorithms such as phase estimation and Shor’s algorithm.graphstate: Generates graph states, an important family of entangled states that also serve as key resources for measurement-based quantum computing.wstate: Creates a W state, a multipartite entangled state that retains pairwise entanglement even if one qubit is lost.grover: Implements Grover’s algorithm, a quantum search algorithm that provides a quadratic speedup for unstructured search problems.qpe: Implements the Quantum Phase Estimation algorithm, a fundamental algorithm that estimates the eigenvalues of a unitary operator and underpins many quantum algorithms, including Shor’s factoring algorithm.
The benchmark entrypoint exposes a compact, consistent CLI:
--benchmark: Selects the benchmark family to run.--backend: Selectsiqmfor hardware runs orsimfor simulator runs.--shots: Controls how many samples are collected from the executed circuit.--num-qubits: Adjusts the problem size for the benchmark families that support it.
Code Example: Preparing and Sampling from a GHZ State¶
The following snippet shows the benchmark runner in full. The same architecture covers every supported benchmark family; only the selected benchmark and validation logic differ.
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "iqm-qdmi[qiskit]",
# "mqt-bench>=2.2.2",
# ]
# [tool.uv.sources]
# iqm-qdmi = { path = ".." }
# ///
"""Run an MQT Bench workload using the QDMI-on-IQM stack."""
from __future__ import annotations
import argparse
import logging
import sys
from dataclasses import dataclass
from typing import TYPE_CHECKING
import numpy as np
from mqt.bench import BenchmarkLevel, get_benchmark
from mqt.core.plugins.qiskit.provider import QDMIProvider
from mqt.core.plugins.qiskit.sampler import QDMISampler
from qiskit.quantum_info import hellinger_fidelity
from iqm.qdmi.qiskit import IQMBackend
if TYPE_CHECKING:
from mqt.core.plugins.qiskit.backend import QDMIBackend
log = logging.getLogger(__name__)
@dataclass(frozen=True)
class BenchmarkConfig:
"""Configuration for one benchmark family."""
benchmark: str
title: str
default_shots: int
default_qubits: int
result_register: str
description: str
BENCHMARKS: dict[str, BenchmarkConfig] = {
"ghz": BenchmarkConfig(
benchmark="ghz",
title="GHZ",
default_shots=1024,
default_qubits=3,
result_register="meas",
description="GHZ state preparation for multi-qubit entanglement checks.",
),
"dj": BenchmarkConfig(
benchmark="dj",
title="Deutsch-Jozsa",
default_shots=1024,
default_qubits=4,
result_register="c",
description="Deutsch-Jozsa oracle sampling.",
),
"qft": BenchmarkConfig(
benchmark="qft",
title="QFT",
default_shots=1024,
default_qubits=3,
result_register="meas",
description="Quantum Fourier Transform sampling.",
),
"graphstate": BenchmarkConfig(
benchmark="graphstate",
title="Graph State",
default_shots=1024,
default_qubits=4,
result_register="meas",
description="Graph-state preparation and sampling.",
),
"wstate": BenchmarkConfig(
benchmark="wstate",
title="W State",
default_shots=1024,
default_qubits=3,
result_register="meas",
description="W-state sampling.",
),
"grover": BenchmarkConfig(
benchmark="grover",
title="Grover",
default_shots=8192,
default_qubits=7,
result_register="meas",
description="Grover search sampling.",
),
"qpe": BenchmarkConfig(
benchmark="qpeexact",
title="Quantum Phase Estimation",
default_shots=8192,
default_qubits=5,
result_register="c",
description="Quantum Phase Estimation sampling.",
),
}
def _build_backend(backend_name: str) -> QDMIBackend:
if backend_name == "iqm":
return IQMBackend()
return QDMIProvider().get_backend("MQT Core DDSIM QDMI Device")
def _describe_result(key: str, counts: dict[str, int], num_qubits: int, shots: int) -> str:
"""Summarize the observed result distribution for the selected benchmark.
Returns:
A short human-readable summary for the selected benchmark family.
"""
if key == "ghz":
expected = {"0" * num_qubits: shots // 2, "1" * num_qubits: shots - shots // 2}
return f"GHZ fidelity={hellinger_fidelity(counts, expected):.7f}"
if key == "dj":
expected = {"1" * (num_qubits - 1): shots}
return f"Deutsch-Jozsa fidelity={hellinger_fidelity(counts, expected):.7f}"
if key == "qft":
expected = {format(i, f"0{num_qubits}b"): shots / (2**num_qubits) for i in range(2**num_qubits)}
return f"QFT fidelity={hellinger_fidelity(counts, expected):.7f}"
if key == "graphstate":
expected = {format(i, f"0{num_qubits}b"): shots / (2**num_qubits) for i in range(2**num_qubits)}
return f"Graph-state fidelity={hellinger_fidelity(counts, expected):.7f}"
if key == "wstate":
expected = {
f"{1 << (num_qubits - index - 1):0{num_qubits}b}": shots / num_qubits for index in range(num_qubits)
}
return f"W-state fidelity={hellinger_fidelity(counts, expected):.7f}"
if key == "grover":
actual_qubits = num_qubits - 1
r = int(np.pi / 4 * np.sqrt(2**actual_qubits))
theta = 2 * np.arcsin(1 / np.sqrt(2**actual_qubits))
success_prob = float(np.sin((r + 0.5) * theta) ** 2)
expected: dict[str, float] = {"1" * num_qubits: success_prob * shots}
if success_prob < 1:
remaining_prob = 1 - success_prob
remaining_prob_per_bitstring = remaining_prob * shots / (2**actual_qubits - 1)
for index in range(2**actual_qubits):
bitstring = format(index, f"0{actual_qubits}b")
if bitstring != "1" * actual_qubits:
expected["1" + bitstring] = remaining_prob_per_bitstring
return f"Grover fidelity={hellinger_fidelity(counts, expected):.7f}"
ideal_bitstring = max(counts, key=counts.__getitem__)
expected = {ideal_bitstring: sum(counts.values())}
return f"QPE fidelity={hellinger_fidelity(counts, expected):.7f}"
def main() -> None:
"""Run one of the MQT Bench showcase circuits."""
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
datefmt="%Y-%m-%dT%H:%M:%S",
)
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--benchmark", choices=tuple(BENCHMARKS), default="ghz")
parser.add_argument("--backend", choices=("iqm", "sim"), default="iqm")
parser.add_argument("--shots", type=int, default=None)
parser.add_argument("--num-qubits", type=int, default=None)
args = parser.parse_args()
config = BENCHMARKS[args.benchmark]
shots = args.shots or config.default_shots
num_qubits = args.num_qubits or config.default_qubits
log.info(
"Starting %s example (backend=%s, qubits=%d, shots=%d)",
config.title,
args.backend,
num_qubits,
shots,
)
log.info("Initialising '%s' backend...", args.backend)
backend = _build_backend(args.backend)
log.info("Backend ready: '%s' | %d qubits", backend.name, backend.num_qubits)
if backend.num_qubits < num_qubits:
sys.exit(
f"Selected backend exposes {backend.num_qubits} qubits, but the {config.title} example needs {num_qubits}."
)
log.info("Building and mapping %s benchmark circuit (n=%d)...", config.title, num_qubits)
circuit = get_benchmark(
benchmark=config.benchmark,
level=BenchmarkLevel.MAPPED,
circuit_size=num_qubits,
target=backend.target,
)
log.info("Circuit ready: %d qubits, %d gates, depth %d", circuit.num_qubits, circuit.size(), circuit.depth())
log.info("Submitting job to '%s' (%d shots)...", backend.name, shots)
sampler = QDMISampler(backend, default_shots=shots)
job = sampler.run([(circuit,)])
counts: dict[str, int] = job.result()[0].data[config.result_register].get_counts()
total_shots = sum(counts.values())
log.info("Job completed. Collected %d shots across %d distinct bitstrings.", total_shots, len(counts))
log.info("Measured counts: %s", sorted(counts.items()))
log.info("Validation summary: %s", _describe_result(args.benchmark, counts, num_qubits, shots))
log.info("Done.")
if __name__ == "__main__":
main()
Execution on a simulator backend should yield a near-perfect distribution of the expected bitstrings (all 0s and all 1s for the GHZ state):
1!../examples/mqt_bench.py --benchmark ghz --backend sim --shots 8192 --num-qubits 20
2026-07-21T09:46:19 [INFO] Starting GHZ example (backend=sim, qubits=20, shots=8192)
2026-07-21T09:46:19 [INFO] Initialising 'sim' backend...
2026-07-21T09:46:19 [INFO] Backend ready: 'MQT Core DDSIM QDMI Device' | 65535 qubits
2026-07-21T09:46:19 [INFO] Building and mapping GHZ benchmark circuit (n=20)...
2026-07-21T09:46:20 [INFO] Pass: ContainsInstruction - 0.01049 (ms)
2026-07-21T09:46:20 [INFO] Pass: UnitarySynthesis - 0.00691 (ms)
2026-07-21T09:46:20 [INFO] Pass: HighLevelSynthesis - 0.00644 (ms)
2026-07-21T09:46:20 [INFO] Pass: BasisTranslator - 0.03624 (ms)
2026-07-21T09:46:20 [INFO] Pass: ElidePermutations - 0.01240 (ms)
2026-07-21T09:46:20 [INFO] Pass: RemoveDiagonalGatesBeforeMeasure - 0.01502 (ms)
2026-07-21T09:46:20 [INFO] Pass: RemoveIdentityEquivalent - 0.00739 (ms)
2026-07-21T09:46:20 [INFO] Pass: InverseCancellation - 0.01645 (ms)
2026-07-21T09:46:20 [INFO] Pass: ContractIdleWiresInControlFlow - 0.00310 (ms)
2026-07-21T09:46:20 [INFO] Pass: CommutativeCancellation - 0.22125 (ms)
2026-07-21T09:46:20 [INFO] Pass: ConsolidateBlocks - 0.14281 (ms)
2026-07-21T09:46:20 [INFO] Pass: Split2QUnitaries - 0.00381 (ms)
2026-07-21T09:46:20 [INFO] Pass: UnitarySynthesis - 0.00644 (ms)
2026-07-21T09:46:20 [INFO] Pass: HighLevelSynthesis - 0.00858 (ms)
2026-07-21T09:46:20 [INFO] Pass: BasisTranslator - 0.02646 (ms)
2026-07-21T09:46:20 [INFO] Pass: TwoQubitPeepholeOptimization - 9.84192 (ms)
2026-07-21T09:46:20 [INFO] Pass: Size - 0.01001 (ms)
2026-07-21T09:46:20 [INFO] Pass: Depth - 0.01788 (ms)
2026-07-21T09:46:20 [INFO] Pass: FixedPoint - 0.01359 (ms)
2026-07-21T09:46:20 [INFO] Pass: FixedPoint - 0.00572 (ms)
2026-07-21T09:46:20 [INFO] Pass: RemoveIdentityEquivalent - 0.01073 (ms)
2026-07-21T09:46:20 [INFO] Pass: Optimize1qGatesDecomposition - 0.05007 (ms)
2026-07-21T09:46:20 [INFO] Pass: CommutativeCancellation - 0.09608 (ms)
2026-07-21T09:46:20 [INFO] Pass: ContractIdleWiresInControlFlow - 0.00381 (ms)
2026-07-21T09:46:20 [INFO] Pass: GatesInBasis - 0.01121 (ms)
2026-07-21T09:46:20 [INFO] Pass: Size - 0.00405 (ms)
2026-07-21T09:46:20 [INFO] Pass: Depth - 0.01001 (ms)
2026-07-21T09:46:20 [INFO] Pass: FixedPoint - 0.00548 (ms)
2026-07-21T09:46:20 [INFO] Pass: FixedPoint - 0.00525 (ms)
2026-07-21T09:46:20 [INFO] Pass: ContainsInstruction - 0.00882 (ms)
2026-07-21T09:46:20 [INFO] Total Transpile Time - 291.25834 (ms)
2026-07-21T09:46:20 [INFO] Circuit ready: 20 qubits, 40 gates, depth 21
2026-07-21T09:46:20 [INFO] Submitting job to 'MQT Core DDSIM QDMI Device' (8192 shots)...
2026-07-21T09:46:20 [INFO] Job completed. Collected 8192 shots across 2 distinct bitstrings.
2026-07-21T09:46:20 [INFO] Measured counts: [('00000000000000000000', 3987), ('11111111111111111111', 4205)]
2026-07-21T09:46:20 [INFO] Validation summary: GHZ fidelity=0.9998229
2026-07-21T09:46:20 [INFO] Done.
Now try running the same script with --backend iqm to see how the distribution
looks on real hardware. Remember to set the required environment variables for
authentication before running the script.