# 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:

```console
uv pip install iqm-qdmi
```

## Quick Usage

The package itself makes the following variables available for import:

- [`__version__`](api/iqm/qdmi/index.html.md#iqm.qdmi.__version__): installed package version.
- [`IQM_QDMI_INCLUDE_DIR`](api/iqm/qdmi/index.html.md#iqm.qdmi.IQM_QDMI_INCLUDE_DIR): include directory for C/C++
  headers.
- [`IQM_QDMI_CMAKE_DIR`](api/iqm/qdmi/index.html.md#iqm.qdmi.IQM_QDMI_CMAKE_DIR): CMake package directory for
  `find_package` integration.
- [`IQM_QDMI_LIBRARY_PATH`](api/iqm/qdmi/index.html.md#iqm.qdmi.IQM_QDMI_LIBRARY_PATH): full path to the shared library.

```ipython3
from iqm.qdmi import __version__, IQM_QDMI_INCLUDE_DIR, IQM_QDMI_CMAKE_DIR, IQM_QDMI_LIBRARY_PATH

print(f"QDMI on IQM version: {__version__}")
print(f"Include directory: {IQM_QDMI_INCLUDE_DIR}")
print(f"CMake directory: {IQM_QDMI_CMAKE_DIR}")
print(f"Library path: {IQM_QDMI_LIBRARY_PATH}")
```

```myst-ansi
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.

```ipython3
!iqm-qdmi --help
```

```myst-ansi
[1;34musage: [0m[1;35miqm-qdmi[0m [[32m-h[0m] [[36m--version[0m | [36m--include_dir[0m | [36m--cmake_dir[0m | [36m--lib_path[0m]

Command line interface for the QDMI on IQM library.

[1;34moptions:[0m
  [1;32m-h[0m, [1;36m--help[0m     show this help message and exit
  [1;36m--version[0m      show program's version number and exit
  [1;36m--include_dir[0m  Print the path to the iqm-qdmi C/C++ include directory
  [1;36m--cmake_dir[0m    Print the path to the iqm-qdmi CMake module directory
  [1;36m--lib_path[0m     Print the path to the iqm-qdmi shared library
```

```ipython3
!iqm-qdmi --version
```

```myst-ansi
1.4.0
```

```ipython3
!iqm-qdmi --include_dir
```

```myst-ansi
/home/runner/work/QDMI-on-IQM/QDMI-on-IQM/.nox/docs/lib/python3.14/site-packages/iqm/qdmi/data/include
```

```ipython3
!iqm-qdmi --cmake_dir
```

```myst-ansi
/home/runner/work/QDMI-on-IQM/QDMI-on-IQM/.nox/docs/lib/python3.14/site-packages/iqm/qdmi/data/share/cmake
```

```ipython3
!iqm-qdmi --lib_path
```

```myst-ansi
/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`](api/iqm/qdmi/sampler/index.html.md#module-iqm.qdmi.sampler) entry point module):
  Samples a serialized QPY circuit on the specified backend.
- `iqm-estimator` (see the [`estimator`](api/iqm/qdmi/estimator/index.html.md#module-iqm.qdmi.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`):

```console
iqm-sampler bell.qpy --shots 128
```

### `iqm-estimator` Usage

To run a parameter estimation job:

```console
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](qiskit.html.md#sampler-and-estimator-primitives).

## 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`](api/iqm/qdmi/offloader/index.html.md#module-iqm.qdmi.offloader) module.
It allows you to programmatically submit quantum workloads to the Slurm quantum
queue.

The module provides two primary functions:

- [`sample()`](api/iqm/qdmi/offloader/index.html.md#iqm.qdmi.offloader.sample): Serializes the circuit to QPY and
  submits a Slurm job using `srun iqm-sampler`. The returned sampler result is
  converted to joint counts across all classical registers in Qiskit’s bit
  order.
- [`estimate()`](api/iqm/qdmi/offloader/index.html.md#iqm.qdmi.offloader.estimate): Serializes the ansatz and observable,
  submits a Slurm job using `srun iqm-estimator`, and returns the complete
  `VQEResult` produced by the worker.

Both worker CLIs reserve stdout for one base64-encoded pickle containing the
native Qiskit result; diagnostics belong on stderr. The offloader is intended
for controlled Slurm deployments with trusted workers and serialized inputs. Use
compatible Python and dependency environments on the submitting and worker
nodes. Result loading uses standard pickle without a restricted unpickler.

Both functions support `local=True` to execute in the submitting process instead
of Slurm, and `simulator=True` to select the simulator in either mode.

### 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](spank_plugin.html.md) and the
   [Administrator Guide](admin_guide.html.md).

```python
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](spank_plugin.html.md#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](spank_plugin.html.md) 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](spank_plugin.html.md#limiting-concurrent-access-with-slurm-licenses)
docs – and `licenses` is how a caller requests it. Only used when
`local=False`.

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

### Programmatic Sampling Example

```python
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. MQT Core discovers the installed device manifest without
importing provider code or loading the device library. Open its stable ID
[`IQM_QDMI_DEVICE_ID`](api/iqm/qdmi/index.html.md#iqm.qdmi.IQM_QDMI_DEVICE_ID) through the MQT Core QDMI driver:

```python
from mqt.core.qdmi.builtin_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:

```python
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:

```python
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:

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

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

## Temporary development dependencies

This repository currently pins QDMI #509 and MQT Core #2373 commits to exercise
native multi-program jobs and installed device discovery. Replace both pins with
suitable releases and regenerate `uv.lock` before publishing. Remove the
temporary LLVM/MLIR setup from Python CI and Linux wheel-test containers once
Core wheels are available for these APIs. Native-only device builds do not
require LLVM/MLIR.
