## `eigenkit.core.backends.iqm_backend`

The IQM backend, which runs circuits on IQM quantum computers through iqm-client.

The `iqm-client` package, with its `qiskit` extra, is an optional dependency (`pip install eigenkit[iqm]`). It is imported only when it is needed, so the rest of the library works without it. If it is missing, `compile()` raises an `ImportError` with a clear message.

This backend connects directly to IQM Resonance, or to any IQM Server, instead of using the same hardware through Amazon Braket. You then pay through IQM's own plans instead of an Amazon Web Services (AWS) bill. It is also the only way to reach IQM's fake backends, its calibration sets and its architectures with `MOVE` gates.

IQM has **no estimator primitive**: the IQM service returns histograms of measured bitstrings and nothing else. So this backend works by sampling everywhere. The observables are sorted into groups of qubit-wise commuting terms, one circuit runs for each group, and the expectation values are computed locally from the counts (see the `_sampling` module). `probabilities()` and `sample()` use the same code, and this is what lets the quantum approximate optimisation algorithm (QAOA) read a solution from real hardware.

There are two kinds of target:

- **Resonance or any IQM Server**: pass `url` and a `token` (or set the `IQM_TOKEN` environment variable). You can also pass `quantum_computer` to choose one machine when the server hosts several.
- **Local fake backends**: pass `IQMFakeAdonis()`, `IQMFakeApollo()`, `IQMFakeDeneb()` or a similar fake backend as `backend_instance`. These run on Qiskit Aer with a realistic IQM noise model, with no token and no network access. The tests of this backend run on them.

The default is `gradient_method = "none"`, because hardware is too expensive for parameter-shift gradients: one backward pass takes at least `2 * n_weights` jobs, each with the full number of shots. Train on a simulator, then use the trained weights on the device. `enable_gradients=True` in the configuration turns on parameter-shift gradients on the device. Compiling for a real device then issues a `CostWarning` that gives the number of circuits of one backward pass.

The circuits are transpiled for the device target. The native gates are `r` and `cz`, and also `move` on the Star architectures, which the Qiskit transpiler routes through the plugin that iqm-client provides. `transpile_mode` chooses when this happens:

- `"parametric"`, the default, transpiles once for each measurement group and sets the parameter values at each call. This is cheap, but the transpiler cannot merge rotations whose angles it does not know. For example, an encoding block and a variational block on 2 qubits, measured in one group, gave 6 `r` gates in this mode against 3 with the values set before transpiling.
- `"per_call"` sets the values first and transpiles at every call. The circuits are shallower, so they run with higher fidelity on hardware. The cost is one transpilation per evaluation, which is small next to the time spent in the queue, but not next to a local simulator.

Example

```python
from eigenkit import Circuit, Encoding, Variational, all_z
from eigenkit.configs import IQMConfig

blocks = [Encoding(in_features=4), Variational(depth=2)]

# Offline, against IQM's noise-modelled fake backend.
from iqm.qiskit_iqm import IQMFakeAdonis

circuit = Circuit(
    blocks=blocks,
    observables=all_z(4),
    provider=IQMConfig(backend_instance=IQMFakeAdonis(), shots=2048),
)

# On real hardware, by name.
circuit = Circuit(
    blocks=blocks,
    observables=all_z(4),
    provider=IQMConfig(
        url="https://resonance.iqm.tech/",
        quantum_computer="garnet",
        shots=2048,
    ),
)
```

The backend is registered as `"iqm"` when the `iqm` extra is installed, and its module is imported only when it is first used. A real device uses your IQM quota: the first compilation for a real device issues a `CostWarning`, and `dry_run_info()` reports the size of the circuit and the number of circuits that each call submits, before you submit anything.

### `IQMBackend`

Provider for IQM quantum computers, through iqm-client.

It is registered as `"iqm"` when iqm-client is installed, and its module is imported only when it is first used. A real device uses your IQM quota: the first compilation for a real machine issues a CostWarning, and `dry_run_info()` reports the size of a run and the number of circuits that each call submits, beforehand. Compiling for one of IQM's fake backends is free and gives no warning.

This class is internal: you select the backend with `provider="iqm"` or by passing an IQMConfig, not by creating this class.

#### `capabilities`

What this backend supports.

It supports every gate except state preparation, arbitrary unitaries and `CPHASE`. It samples with a finite number of shots, which is the only thing IQM offers. It has no native batching, because a batch takes one job for each sample. It has no qubit limit of its own, and the number of qubits of the device applies. See Capabilities.

#### `gradient_method`

How this backend computes gradients.

It is `"none"`, unless the configuration sets `enable_gradients`, and then it is `"parameter_shift"`. A parameter-shift backward pass costs at least `2 * n_weights` jobs, each with the full number of shots, so gradients are refused unless you turn them on.

#### `__init__(config=None, name='iqm')`

Build the backend from a device configuration.

Parameters:

| Name     | Type        | Description                                                                                                     | Default |
| -------- | ----------- | --------------------------------------------------------------------------------------------------------------- | ------- |
| `config` | `IQMConfig` | The device settings. None builds the default configuration, which reads the server address from IQM_SERVER_URL. | `None`  |
| `name`   | `str`       | The registry name, which error messages use.                                                                    | `'iqm'` |

#### `compile(circuit, observables)`

Prepare `circuit` for repeated evaluation of `observables`.

It is called once for each `Circuit`, and the handle is reused for every call. So the expensive work happens here: planning the measurement groups and, in `"parametric"` mode, transpiling one circuit for each group.

Parameters:

| Name          | Type                   | Description                               | Default    |
| ------------- | ---------------------- | ----------------------------------------- | ---------- |
| `circuit`     | `CircuitSpec`          | The circuit to prepare.                   | *required* |
| `observables` | `Sequence[Observable]` | The observables that the handle measures. | *required* |

Returns:

| Type          | Description            |
| ------------- | ---------------------- |
| `IQMCompiled` | An IQMCompiled handle. |

### `IQMCompiled`

Bases: `CompiledCircuit`

A circuit prepared for an IQM quantum computer.

This class is internal: you get it from IQMBackend.compile(), and you never create it directly. The measurement plan and the parametric circuit are built once. `run()` and `submit()` set the parameter values, and in `"per_call"` mode they also transpile.

#### `__init__(circuit, observables, config, *, warn=True)`

Prepare the circuit for repeated evaluation.

`IQMBackend.compile()` builds it, and there is no reason to create one directly.

Parameters:

| Name          | Type                   | Description                                                                                                                               | Default    |
| ------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| `circuit`     | `CircuitSpec`          | The circuit to prepare.                                                                                                                   | *required* |
| `observables` | `Sequence[Observable]` | The observables that this handle measures.                                                                                                | *required* |
| `config`      | `IQMConfig`            | The device settings.                                                                                                                      | *required* |
| `warn`        | `bool`                 | Whether to issue the cost warnings for a real device. It is False only for the shifted copies that compile_shifted builds. Default: True. | `True`     |

#### `compile_shifted(circuit)`

Compile a copy of this circuit with one gate angle shifted.

The chain-rule gradient compiles one copy for each shifted gate. Each copy reuses the backend of this handle, without asking the server for it again, and is transpiled with the seed of this handle, so every term of the gradient comes from the same qubits. The compilation of this handle already issued the cost warnings, so the copy issues none.

Parameters:

| Name      | Type          | Description          | Default    |
| --------- | ------------- | -------------------- | ---------- |
| `circuit` | `CircuitSpec` | The shifted circuit. | *required* |

Returns:

| Type            | Description                        |
| --------------- | ---------------------------------- |
| `'IQMCompiled'` | An IQMCompiled handle for circuit. |

#### `counts(weights, inputs)`

Measure one sample in the computational basis and return the histogram.

It runs one job of `shots` shots, measured in the `Z` basis with no change of basis.

Parameters:

| Name      | Type      | Description                     | Default    |
| --------- | --------- | ------------------------------- | ---------- |
| `weights` | `ndarray` | A 1-D parameter vector.         | *required* |
| `inputs`  | `ndarray` | A 1-D vector of input features. | *required* |

Returns:

| Name     | Type             | Description                                                                               |
| -------- | ---------------- | ----------------------------------------------------------------------------------------- |
| `counts` | `dict[str, int]` | The number of shots for each measured bitstring. The leftmost bit of each key is qubit 0. |

#### `dry_run_info()`

Report the size of a run, before anything is submitted.

The report gives the target backend and whether it is one of IQM's local fake backends, the number of shots, the transpile mode, the optimisation level, the `seed_transpiler` setting, the numbers of logical and physical qubits, the number of observables, and the number of measurement groups that the observables need, which is the number of circuits that one call submits. In `"parametric"` mode, it also gives the gate counts and the depth of the transpiled circuit for each measurement basis. `"per_call"` mode transpiles at call time, so there is no stored circuit to report.

Returns:

| Name   | Type   | Description                      |
| ------ | ------ | -------------------------------- |
| `info` | `dict` | The values above, keyed by name. |

#### `probabilities(weights, inputs)`

Return the measurement distribution over all `2**n_qubits` basis states.

The distribution is estimated from the shot frequencies, and it is not the exact one: this is hardware, and the basis states that no shot measured get a probability of exactly `0`. It is limited to `_sampling.MAX_DENSE_QUBITS` qubits. For wider circuits, read `sample()` directly.

Parameters:

| Name      | Type      | Description                     | Default    |
| --------- | --------- | ------------------------------- | ---------- |
| `weights` | `ndarray` | A 1-D parameter vector.         | *required* |
| `inputs`  | `ndarray` | A 1-D vector of input features. | *required* |

Returns:

| Name            | Type      | Description                                                   |
| --------------- | --------- | ------------------------------------------------------------- |
| `probabilities` | `ndarray` | An array of shape (2\*\*n_qubits,) whose entries add up to 1. |

#### `resources(measured=True)`

Report the resources that the transpiled circuit uses on this device.

The two-qubit gate counts include `move` on Star devices, because every `CZ` there goes through the resonator. In `"per_call"` mode, the report describes the parametric circuit, and the circuits with the values set can merge a few more rotations.

Parameters:

| Name       | Type   | Description                                                                           | Default |
| ---------- | ------ | ------------------------------------------------------------------------------------- | ------- |
| `measured` | `bool` | It is accepted for consistency with the Qiskit Runtime backend, and it has no effect. | `True`  |

Returns:

| Type   | Description                                                                   |
| ------ | ----------------------------------------------------------------------------- |
| `dict` | A dictionary with the same keys as resources() of the Qiskit Runtime backend. |

#### `retrieve_zero_state_probabilities(job_id)`

Reconnect to a job that `submit_zero_state_probabilities()` submitted.

Parameters:

| Name     | Type  | Description                    | Default    |
| -------- | ----- | ------------------------------ | ---------- |
| `job_id` | `str` | The identifier of the IQM job. | *required* |

Returns:

| Type                   | Description                                            |
| ---------------------- | ------------------------------------------------------ |
| `ProbabilityJobHandle` | A handle whose result() is the array of probabilities. |

Raises:

| Type                  | Description                                                                                               |
| --------------------- | --------------------------------------------------------------------------------------------------------- |
| `NotImplementedError` | For a local fake backend, which keeps no history of its jobs, or for a backend that cannot retrieve jobs. |

#### `run(weights, inputs)`

Evaluate the circuit and return one expectation value per observable.

It follows the `CompiledCircuit` contract: the result has shape `(n_obs,)` when `weights` and `inputs` are both 1-D, and `(B, n_obs)` otherwise. Each sample costs one job of `n_measurement_groups` circuits. The values are estimated from shots, so they carry the shot noise of `shots`.

Parameters:

| Name      | Type      | Description                                                                 | Default    |
| --------- | --------- | --------------------------------------------------------------------------- | ---------- |
| `weights` | `ndarray` | The parameter vector: 1-D for one sample, or 2-D with a leading batch axis. | *required* |
| `inputs`  | `ndarray` | The input features, with the same 1-D or 2-D shape rule as weights.         | *required* |

Returns:

| Type      | Description                                                              |
| --------- | ------------------------------------------------------------------------ |
| `ndarray` | One expectation value for each observable (for each sample, in a batch). |

#### `sample(weights, inputs)`

Return the same histogram as `counts()`.

This is an alias of counts, under the name that `Circuit.sample()` looks for.

Parameters:

| Name      | Type      | Description                     | Default    |
| --------- | --------- | ------------------------------- | ---------- |
| `weights` | `ndarray` | A 1-D parameter vector.         | *required* |
| `inputs`  | `ndarray` | A 1-D vector of input features. | *required* |

Returns:

| Name     | Type             | Description                                      |
| -------- | ---------------- | ------------------------------------------------ |
| `counts` | `dict[str, int]` | The number of shots for each measured bitstring. |

#### `submit(weights, inputs)`

Submit the circuit without waiting, and return a job handle.

All the measurement groups go into one job, so `handle.result()` waits once and returns the whole vector of expectation values.

Parameters:

| Name      | Type      | Description                     | Default    |
| --------- | --------- | ------------------------------- | ---------- |
| `weights` | `ndarray` | A 1-D parameter vector.         | *required* |
| `inputs`  | `ndarray` | A 1-D vector of input features. | *required* |

Returns:

| Name     | Type        | Description                     |
| -------- | ----------- | ------------------------------- |
| `handle` | `JobHandle` | A handle for the submitted job. |

#### `submit_zero_state_probabilities(weights, inputs)`

Submit the all-zeros probabilities of the rows of `inputs`, without waiting.

The rows go into one IQM job of `B` circuits. In `"parametric"` mode, the circuit measured in the `Z` basis is transpiled once, and each row only sets the values. In `"per_call"` mode, every row is transpiled.

Parameters:

| Name      | Type      | Description                                | Default    |
| --------- | --------- | ------------------------------------------ | ---------- |
| `weights` | `ndarray` | A 1-D parameter vector.                    | *required* |
| `inputs`  | `ndarray` | A batch of inputs, of shape (B, n_inputs). | *required* |

Returns:

| Type                   | Description                                                           |
| ---------------------- | --------------------------------------------------------------------- |
| `ProbabilityJobHandle` | A handle whose result() is the array of probabilities, of shape (B,). |

#### `transpiled_circuit(measured=True)`

Return the circuit measured in `Z` and transpiled for this device.

The parameters are not set. Use it to draw or inspect the circuit.

Parameters:

| Name       | Type   | Description                                                                                                                         | Default |
| ---------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `measured` | `bool` | It is accepted for consistency with the Qiskit Runtime backend, and it has no effect, because IQM always submits measured circuits. | `True`  |

Returns:

| Type               | Description                         |
| ------------------ | ----------------------------------- |
| `'QuantumCircuit'` | A transpiled qiskit.QuantumCircuit. |

#### `zero_state_probabilities(weights, inputs)`

Return the all-zeros probability of every row of `inputs`, from one job.

Parameters:

| Name      | Type      | Description                                | Default    |
| --------- | --------- | ------------------------------------------ | ---------- |
| `weights` | `ndarray` | A 1-D parameter vector.                    | *required* |
| `inputs`  | `ndarray` | A batch of inputs, of shape (B, n_inputs). | *required* |

Returns:

| Type      | Description                                                |
| --------- | ---------------------------------------------------------- |
| `ndarray` | The probabilities estimated from the shots, of shape (B,). |

#### `zero_state_probability(weights, inputs)`

Return the all-zeros probability of one sample, estimated from the shots.

Parameters:

| Name      | Type      | Description                     | Default    |
| --------- | --------- | ------------------------------- | ---------- |
| `weights` | `ndarray` | A 1-D parameter vector.         | *required* |
| `inputs`  | `ndarray` | A 1-D vector of input features. | *required* |

Returns:

| Type    | Description      |
| ------- | ---------------- |
| `float` | The probability. |

### `IQMConfig`

Bases: `SecretFieldsMixin`

Device configuration for the IQM backend.

There are two ways to choose a target:

- `url`, and optionally `quantum_computer`, opens an `IQMProvider` for an IQM Server. This needs a token, from `token` or from the `IQM_TOKEN` environment variable.
- `backend_instance` uses any IQM `BackendV2` directly: a fake backend for offline work, or a backend that you configured yourself. It takes precedence over `url`, and it needs no token and no network access.

Parameters:

| Name                   | Type            | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | Default                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| ---------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `url`                  | \`str           | None\`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | The address of the IQM Server, for example "https://resonance.iqm.tech/". It is ignored when backend_instance is set. With None, the backend reads the address from the IQM_SERVER_URL environment variable when the circuit is compiled.                                                                                                                                                                                                                                                                                                                                                                                     |
| `quantum_computer`     | \`str           | None\`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | The machine to use when the server hosts more than one, for example "garnet". With None, the server's default is used.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `backend_instance`     | `Any`           | An IQM backend that is already configured. It takes precedence over url.                                                                                                                                                                                                                                                                                                                                                                                                                             | `None`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `shots`                | `int`           | The number of shots for each circuit run. It must be positive, because hardware cannot compute exact values.                                                                                                                                                                                                                                                                                                                                                                                         | `1024`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `optimization_level`   | `int`           | The optimisation level passed to qiskit.transpile, from 0 to 3.                                                                                                                                                                                                                                                                                                                                                                                                                                      | `1`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `seed_transpiler`      | \`int           | None\`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | The seed of the random steps of the transpiler, mainly the SabreLayout and SabreSwap passes, which choose the layout and the routing. With None, the default, a new seed is drawn for every compilation, so a circuit that needs routing compiles to a different number of gates on every run. Within one compilation, all the transpilations share the seed, including those of the shifted copies that a hardware gradient compiles. Set it whenever two compilations must be comparable. Trying a few seeds and keeping the cheapest result is a cheap and fair way to choose a layout, because compiling submits nothing. |
| `transpile_mode`       | `TranspileMode` | "parametric" transpiles once and sets the parameter values at each call. "per_call" sets the values first and transpiles at every call, which gives shallower circuits but costs one transpilation per evaluation.                                                                                                                                                                                                                                                                                   | `'parametric'`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `group_commuting`      | `bool`          | Whether to put Pauli terms that commute qubit by qubit into shared measurement circuits. With False, one circuit runs for each term: there are more jobs, but each term gets all the shots.                                                                                                                                                                                                                                                                                                          | `True`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `token`                | \`str           | None\`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | The access token for the IQM Server. It is passed on only when it is set. With None, iqm-client handles authentication on its own, from IQM_TOKEN and from its own tokens file. Do not set both token and IQM_TOKEN, because iqm-client rejects a token given in two ways at once. This field is secret: repr() never shows it, and it is never saved in .eigenkit bundles, pickles or joblib checkpoints. After a load it is None, so set IQM_TOKEN.                                                                                                                                                                         |
| `calibration_set_id`   | `Any`           | The calibration set to use, as a UUID. With None, the server's default is used.                                                                                                                                                                                                                                                                                                                                                                                                                      | `None`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `use_metrics`          | `bool`          | Whether to ask the server for the error rates of each qubit and each coupler, and load them into the InstructionProperties of the target. With False (the default of iqm-client itself), the target has no error rates, so the Qiskit layout pass does not see the noise. It then cannot prefer the better qubits, and a comparison with a backend that does have calibration data is not like for like. It costs one extra request at compile time, and it is ignored when backend_instance is set. | `False`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `provider_kwargs`      | `dict`          | Extra keyword arguments passed to IQMProvider, for example tokens_file.                                                                                                                                                                                                                                                                                                                                                                                                                              | `dict()`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `poll_timeout_seconds` | `int`           | How many seconds result() waits before giving up.                                                                                                                                                                                                                                                                                                                                                                                                                                                    | `3600`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `enable_gradients`     | `bool`          | Whether to allow parameter-shift gradients on the device, for PyTorch .backward(), jacobian() and gradient-based optimisers. It is off by default, because one backward pass runs the circuits of a forward call at least 2 * n_weights times. When it is set, compiling for a real device issues a CostWarning that gives this count.                                                                                                                                                               | `False`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |

#### `__post_init__()`

Reject the settings that can never work, when the configuration is created.

If they were left to the first submission, the error would appear after the transpilation and the cost warning, which on a paid device is the wrong time to find out. Only the settings that are wrong on their own are checked here. Whether a *target* can be found depends on the environment at compile time, so `_resolve_backend` checks that.

#### `make_backend()`

Build the backend that this configuration describes.

The provider layer calls it when a configuration is passed as `provider=`. There is no reason to call it directly.

Returns:

| Type           | Description                            |
| -------------- | -------------------------------------- |
| `'IQMBackend'` | The IQMBackend for this configuration. |

### `IQMJobHandle`

A handle to a submitted IQM job.

`compiled.submit(weights, inputs)` returns one of these handles. Call `result()` to wait until the job finishes and get the array of expectation values, or `status()` to check the job without waiting.

#### `__init__(job, groups, n_observables, n_qubits, poll_timeout_seconds)`

Wrap a submitted IQM job in a job handle.

`IQMCompiled.submit()` builds it. It is not meant to be created directly.

Parameters:

| Name                   | Type                           | Description                                                                          | Default    |
| ---------------------- | ------------------------------ | ------------------------------------------------------------------------------------ | ---------- |
| `job`                  | `JobV1`                        | The job returned by the IQM backend.                                                 | *required* |
| `groups`               | `tuple[MeasurementGroup, ...]` | The measurement plan that the submitted circuits follow, in the order of submission. | *required* |
| `n_observables`        | `int`                          | The number of expectation values to compute.                                         | *required* |
| `n_qubits`             | `int`                          | The number of qubits of the circuit, used to read the keys of the histograms.        | *required* |
| `poll_timeout_seconds` | `int`                          | How many seconds result() waits before giving up.                                    | *required* |

#### `cancel()`

Ask IQM to cancel the job. It returns without waiting.

#### `counts()`

Wait until the job finishes, then return the raw histograms.

There is one histogram for each measurement group, and qubit `0` is the leftmost bit of each key. These are the counts that the device returned. result computes the expectation values of the observables from them.

#### `job_id()`

Return the identifier of the IQM job, to look it up in Resonance or through iqm-client.

#### `result()`

Wait until the job finishes, then return one expectation value for each observable, in the order of the observables.

#### `status()`

Return the state of the job, as the job object reports it, without waiting.

The state is a string such as `"QUEUED"`, `"RUNNING"`, `"DONE"`, `"CANCELLED"` or `"ERROR"`.
