Easy Integration with the iqm-qdmi Python Package

To ease the distribution and integration of the IQM QDMI Device library, we have packaged it as a Python module available on PyPI under the name iqm-qdmi. This package provides a convenient way to discover installation paths and metadata that are useful when integrating the IQM QDMI Device into downstream build systems, such as CMake-based projects or Python workflows.

Install From PyPI

Use uv (or your Python package manager of choice) to install the package:

uv pip install iqm-qdmi

Quick Usage

The package itself makes the following variables available for import:

1from iqm.qdmi import __version__, IQM_QDMI_INCLUDE_DIR, IQM_QDMI_CMAKE_DIR, IQM_QDMI_LIBRARY_PATH
2
3print(f"QDMI on IQM version: {__version__}")
4print(f"Include directory: {IQM_QDMI_INCLUDE_DIR}")
5print(f"CMake directory: {IQM_QDMI_CMAKE_DIR}")
6print(f"Library path: {IQM_QDMI_LIBRARY_PATH}")
QDMI on IQM version: 1.4.0
Include directory: /home/runner/work/QDMI-on-IQM/QDMI-on-IQM/.nox/docs/lib/python3.14/site-packages/iqm/qdmi/data/include
CMake directory: /home/runner/work/QDMI-on-IQM/QDMI-on-IQM/.nox/docs/lib/python3.14/site-packages/iqm/qdmi/data/share/cmake
Library path: /home/runner/work/QDMI-on-IQM/QDMI-on-IQM/.nox/docs/lib/python3.14/site-packages/iqm/qdmi/data/lib/libiqm-qdmi-device.so

Command Line Interface

The above values can also be conveniently queried from the command line via the iqm-qdmi entry point.

1!iqm-qdmi --help
usage: iqm-qdmi [-h] [--version | --include_dir | --cmake_dir | --lib_path]

Command line interface for the QDMI on IQM library.

options:
  -h, --help     show this help message and exit
  --version      show program's version number and exit
  --include_dir  Print the path to the iqm-qdmi C/C++ include directory
  --cmake_dir    Print the path to the iqm-qdmi CMake module directory
  --lib_path     Print the path to the iqm-qdmi shared library
1!iqm-qdmi --version
1.4.0
1!iqm-qdmi --include_dir
/home/runner/work/QDMI-on-IQM/QDMI-on-IQM/.nox/docs/lib/python3.14/site-packages/iqm/qdmi/data/include
1!iqm-qdmi --cmake_dir
/home/runner/work/QDMI-on-IQM/QDMI-on-IQM/.nox/docs/lib/python3.14/site-packages/iqm/qdmi/data/share/cmake
1!iqm-qdmi --lib_path
/home/runner/work/QDMI-on-IQM/QDMI-on-IQM/.nox/docs/lib/python3.14/site-packages/iqm/qdmi/data/lib/libiqm-qdmi-device.so

Sampler and Estimator CLI Utilities

If you install the package with the qiskit extra, the following additional command-line scripts are exposed:

  • iqm-sampler (see the sampler entry point module): Samples a serialized QPY circuit on the specified backend.

  • iqm-estimator (see the estimator entry point module): Variational Quantum Eigensolver (VQE) parameter estimation for a serialized ansatz and observable.

iqm-sampler Usage

For example, to execute a QPY circuit file (bell.qpy):

iqm-sampler bell.qpy --shots 128

iqm-estimator Usage

To run a parameter estimation job:

iqm-estimator ansatz.qpy observable.pkl --maxiter 10

The estimator CLI and offloader.estimate use Qiskit’s default precision of 1/64, corresponding to 4,096 shots per measurement circuit. See the primitive options.

Programmatic Offloading with the offloader Module

For workflows running on a Slurm login node (such as Jupyter notebooks on a gateway service), the package exposes the offloader module. It allows you to programmatically submit quantum workloads to the Slurm quantum queue.

The module provides two primary functions:

  • sample(): Serializes the given circuit to QPY, submits a Slurm job using srun iqm-sampler, and parses the base64-encoded pickled result back to a python dictionary of counts.

  • estimate(): Serializes the ansatz and observable, submits a Slurm job using srun iqm-estimator, and parses the base64-encoded pickled VQEResult to return it, matching the semantics of running VQE directly against the regular (non-offloaded) estimator.

Both functions support a local=True argument for running simulation/hardware compilation locally (useful for debugging) and a simulator=True argument when submitting Slurm jobs to target simulated devices instead of real QPU hardware.

Selecting the Slurm Partition

Both functions submit their srun jobs to the partition gating the nodes that expose the quantum computer as a Slurm GRES resource. Its name is a per-site choice, resolved in this order:

  1. The partition keyword argument.

  2. The IQM_SLURM_PARTITION environment variable, which lets an administrator set the site’s name once for every user. An empty value counts as unset.

  3. quantum, the name used throughout the SPANK plugin documentation and the Administrator Guide.

counts = sample(qc, shots=512, partition="qc-nodes")

Slurm’s own SLURM_PARTITION has no effect here, because the resolved name is always passed as an explicit --partition, which takes precedence over it.

Important

Renaming the partition is not enough on its own. The SPANK plugin only runs on the partitions its partitions= option lists, so that list has to carry the new name too — see Configuration. Otherwise the plugin silently skips the job and never injects IQM_BASE_URL, IQM_QC_ID, or IQM_QC_ALIAS.

Sizing the Slurm Allocation

Both functions accept a nodes keyword argument, forwarded as --nodes and defaulting to a single node. The worker itself always runs as one task (--ntasks=1), so nodes only sizes the allocation for a site whose partition demands more than one node; it does not distribute or parallelize the workload.

It has no environment fallback: the node count is a per-job resource request rather than a site-wide constant.

Shared Jobs Directory

When submitting Slurm workloads, a shared filesystem directory is required to serialize inputs (QPY/pickle files) for execution on the compute nodes. The directory path is resolved as follows:

  1. Honoring the IQM_JOBS_DIR environment variable, if set.

  2. Defaulting to a hidden .qdmi_jobs folder under the user’s home directory (~/.qdmi_jobs).

Ensure that the jobs directory is located on a shared cluster filesystem accessible by both the login node and all Slurm compute nodes.

Selecting a Quantum Computer per Job

Both functions accept optional qc_id and qc_alias keyword arguments to pin a specific quantum computer for a single job, without changing the process’s default backend configuration. When set, they are passed as --iqm-qc-id and --iqm-qc-alias options on the srun command itself, which the QDMI-on-IQM SPANK plugin resolves into the job’s IQM_QC_ID and IQM_QC_ALIAS environment variables. Only used when local=False.

Requesting a Slurm License

Both functions accept an optional licenses keyword argument, forwarded verbatim as a --licenses option on the srun command (Slurm’s own name[:count][,name[:count]...] syntax). This is unrelated to QC selection: a site administrator can configure a Slurm license per QC to cap concurrent jobs against it – see the SPANK plugin’s Limiting Concurrent Access with Slurm Licenses docs – and licenses is how a caller requests it. Only used when local=False.

counts = sample(qc, shots=512, simulator=True, qc_alias="emerald", licenses="iqm_qc_emerald:1")

Programmatic Sampling Example

from iqm.qdmi.offloader import sample
from qiskit import QuantumCircuit

qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
qc.measure_all()

# programmatically offload via Slurm
counts = sample(qc, shots=512, simulator=True)
print("Counts:", counts)

Querying the Device Directly

The Qiskit backend covers circuit execution, but a QDMI device also answers questions about itself. Open a session with MQT Core’s driver to reach them. Constructing an IQMBackend registers the device under the stable ID IQM_QDMI_DEVICE_ID, after which open_device resolves it:

from mqt.core.qdmi.driver import open_device

from iqm.qdmi import IQM_QDMI_DEVICE_ID

device = open_device(IQM_QDMI_DEVICE_ID, token="…", custom2="emerald")

print(device.status())
print(device.supported_program_formats())

Queue Length and Queue Position

The device reports how busy the quantum computer is, so a client can decide whether to submit now or wait:

waiting = device.queue_length()  # jobs waiting, excluding those executing

queue_length() returns None when the IQM API does not supply a trustworthy value. A queued job reports how many jobs are ahead of it; querying it refreshes the job’s status first:

ahead = job.queue_position  # None once the job is no longer queued

Retrieving an Existing Job

A job outlives the session that submitted it. Given its ID, a later session can pick it up again to poll, wait, cancel, or fetch results:

job = device.retrieve_job_by_id("d3416f0a-…")
print(job.check())

A retrieved job cannot be resubmitted, and its parameters cannot be changed.