> ## Documentation Index
> Fetch the complete documentation index at: https://docs.haiqu.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Run

#### Haiqu.run(circuits, parameters=None, shots=1000, observables=None, readout\_bases=None, device=None, device\_id=None, options=None, use\_mitigation=False, use\_packing=False, pack\_size=None, auto\_quality=False, job\_name=None, job\_description=None, dry\_run=False)

Run quantum circuits on the selected backend.

For the behaviour before 1.7.0, which returned a `RunJobModel`, use `Legacy.run()` (`haiqu.legacy.run`).

This flexible method supports multiple execution scenarios, with different combinations of circuits, parameters, and
observables. When multiple values are provided for any of them, the results are returned as nested lists with up to 3
layers, ordered by circuits, then observables, and finally parameters.

#### NOTE

Every `device_id` — including `"aer_simulator"` — dispatches to the Haiqu cloud; there is no
local execution path, and [`login()`](../index.md#haiqu.sdk.quantum_haiqu.Haiqu.login) must have succeeded first. Jobs submitted before
[`init()`](../index.md#haiqu.sdk.quantum_haiqu.Haiqu.init) is called are logged under a shared default experiment; call
`haiqu.init("my experiment")` first so runs stay organized on the dashboard.

* **Parameters:**
  * **circuits** (*QuantumCircuit* *|* *list* \*\[\**QuantumCircuit* *]*  *|* *CircuitModel* *|* *list* \*\[\**CircuitModel* *]*) -- The quantum circuit(s) to execute. Can be a single circuit or a list of circuits.
  * **parameters** (*list* *|* *None*) --

    The parameters for the circuits. Can be a single set of parameters or nested lists of parameter sets. For
    multiple circuits, must be a list where each element corresponds to parameters for that circuit. Defaults
    to `None`, in which case the circuits must not have any parameters.

    Each inner list must have length `circuit.num_parameters`. Values are bound positionally in the
    order of Qiskit's `QuantumCircuit.parameters`. When parameters are created one-by-one, that order
    is typically alphabetical by name (e.g. `theta10` before `theta2`),
    independent of gate addition order; `ParameterVector` circuits use vector
    index order instead (e.g. `theta[0]`, `theta[1]`). Inspect
    `list(circuit.parameters)` for the exact order.
  * **shots** (*int*) -- The number of shots for each circuit execution. Defaults to 1000.
  * **observables** (*SparsePauliOp* *|* *list* \*\[\**SparsePauliOp* *]*  *|* *list* \*\[\**list* \*\[\**SparsePauliOp* *]* *]*  *|* *None*) --

    The observable(s) to measure. The order of Pauli terms in a single string follows the Qiskit
    reversed-order convention (e.g., `"IZ"` measures qubit 0 in the Z basis). Defaults to `None`,
    in which case the circuits must include their own measurements.

    Accepted shapes:

    * **Single circuit:** a single `SparsePauliOp`, the nested form
      `[[op1, op2, ...]]`, or a bare list `[op1, op2, ...]`.
    * **Multiple circuits:** a list of length `num_circuits`, where each element is independently
      either a single `SparsePauliOp` (one observable on that circuit) or a list of
      `SparsePauliOp` (multiple observables on that circuit). Mixing is allowed —
      `[[op1, op2], op3]` for two circuits is valid.

    The fully-nested form is the unambiguous canonical shape and is recommended when the same code
    path handles both single and multi-circuit submissions.
  * **readout\_bases** (*list* \*\[\**str* *]*  *|* *None*) --

    Per-qubit Pauli bases to read the circuits out in, e.g.
    `["XXXX", "ZZZZ", "XZYZ"]`. Each string follows the same Qiskit reversed-order convention as
    `observables` (`basis[-1]` applies to qubit 0) and needs one character per qubit, drawn from
    `X`, `Y` and `Z` (use `Z` for a qubit that should not be rotated). The circuits keep their
    own measurements; this rotates the readout before them.

    One distribution is returned per basis per circuit, so `result()` gains a basis axis in the
    same position observables occupy: `[circuit][basis]`, or `[circuit][basis][parameter]` with a
    parameter sweep. The bases keep the order they were passed in, so
    `dict(zip(readout_bases, job.result()[i]))` keys circuit `i`'s distributions by basis
    string.

    Mutually exclusive with `observables`. Combines with `use_packing`: each packed copy of
    the circuit is read out in the same basis, so a basis string still has one character per
    circuit qubit. Note that each basis is a separate execution, so the cost is
    `len(readout_bases) * shots`. Defaults to `None`, which reads out in the computational
    basis as before.
  * **device** (*DeviceModel* *|* *None*) -- The device to run the circuits on. If specified, `device_id` is ignored.
    Devices from [`get_device()`](../index.md#haiqu.sdk.quantum_haiqu.Haiqu.get_device) with `use_fractional_gates=True` use fractional gates.
  * **device\_id** (*str* *|* *None*) -- The ID of the device to run the circuits on. Defaults to `None`.
  * **options** (*dict* *|* *None*) --

    Options to pass to the device. Supports an optional
    `"error_mitigation_options"` key with a dictionary of boolean flags to control
    individual error mitigation components when `use_mitigation=True`. Supported keys:

    * `"dynamical_decoupling"` (bool): Toggle dynamical decoupling. Defaults to `True`.
    * `"readout_mitigation"` (bool): Toggle readout error mitigation. Defaults to `False`.
    * `"noise_tailoring"` (bool): Toggle noise tailoring via Pauli twirling. Defaults to `True`.
    * `"advanced_mitigation"` (bool): Toggle advanced mitigation. Defaults to `True`.

    To use MPS simulation on `aer_simulator`, pass `{"method": "matrix_product_state"}`.

    An optional `"noise_model"` key runs a noisy simulation. It is only supported when
    `device_id="aer_simulator"` or `device_id="ionq_simulator"`, and the accepted value
    differs by backend:

    * `aer_simulator`: a `dict` or a `qiskit_aer.noise.NoiseModel` object (serialized
      automatically before submission).
    * `ionq_simulator`: a `str` identifier naming a characterized IonQ device
      (e.g. `"aria-1"`).

    Passing `None` (or omitting the key) runs a noiseless simulation.

    **Simulator qubit limits** (enforced server-side):

    * Statevector (`aer_simulator` default): up to **24 qubits**.
    * MPS (`aer_simulator` with `method="matrix_product_state"`, including with fake
      devices): no strict qubit limit.
    * Noisy simulation without MPS (fake devices such as `fake_kyiv`, or
      `aer_simulator` with a `noise_model`): up to **12 qubits**.

    See [the run reference](https://docs.haiqu.ai/reference/run/run.md) for full details.
  * **use\_mitigation** (*bool*) -- Whether to use error mitigation techniques. Defaults to `False`.
    When enabled, Pauli twirling is applied and readout twirling is not. Override either
    through `options["error_mitigation_options"]`, whose keys are `noise_tailoring`
    (Pauli twirling), `readout_mitigation` (readout twirling), `advanced_mitigation`,
    `dynamical_decoupling` and `readout_mitigation_options`. Twirling and dynamical
    decoupling are skipped on simulators and fake devices, where there is no hardware
    noise to tailor.
  * **use\_packing** (*bool*) -- Whether to use circuit packing for efficient device utilization. Defaults to `False`.
    **Warning:** Experimental — packing replicates circuits on unused device qubits
    to run multiple copies in parallel, which may increase errors for deeper input circuits.
    For example, a 4-qubit circuit with pack\_size=2 and 1000 shots runs two copies
    in parallel with 500 shots each, yielding 1000 shots of results while only paying
    for 500 shot executions on the QPU — a 2x cost saving.
  * **pack\_size** (*int* *|* *None*) -- Number of circuit copies to pack onto the device. Must be >= 2.
    Only valid when `use_packing=True`. If `None` (default), the backend will
    pack into at most 2/3 of the device qubits.
  * **auto\_quality** (*bool*) -- Whether to assess how closely the results match an ideal, noiseless
    simulation of the same circuits. Defaults to `False`.
    Assessment simulates every input circuit exactly, so it applies only to circuits
    small enough for a statevector reference (24 qubits); larger circuits are skipped
    individually and reported in `job.info["quality_assessment"]`. It is unavailable
    for runs with `readout_bases`, because the basis axis leaves no single reference
    distribution to compare against. The assessment runs after the device results are
    collected, so it adds wall-clock time to the job but no QPU cost.
  * **job\_name** (*str* *|* *None*) -- The name for the job. If `None` (default), a name will be automatically generated.
  * **job\_description** (*str* *|* *None*) -- The description for the job.
  * **dry\_run** (*bool*) -- Whether to stop just prior to backend execution for QPU cost estimation. Defaults to `False`.
    When `True`, the job result will be empty since execution on the device is skipped.
    The estimated QPU cost is then available via `job.estimated_qpu_cost`.
* **Returns:**
  The Hybrid job that will execute the circuits.
  : Call `job.result()` to retrieve the execution results as a nested list ordered by
  *circuits → observables → parameters*:
  * Without observables: list of measurement distributions (`dict[str, float]`), one per
    circuit, in Qiskit bit-order.
  * With observables, no parameter sweep: 2D list of expectation values, indexed `[circuit][observable]`.
  * With observables and a parameter sweep: 3D list of expectation values, indexed
    `[circuit][observable][parameter]`.
  * With readout bases: the basis axis takes the observable position — `[circuit][basis]`,
    or `[circuit][basis][parameter]` with a parameter sweep — in the order the bases were
    passed.
  **Dry runs** (`dry_run=True`): `result()` is empty. Use `job.estimated_qpu_cost` for
  the QPU cost estimate and `job.pre_device_pipeline_time` for the classical time spent
  before the device layer. `job.time` is `0` because the device phase does not run.

  **Full runs** (`dry_run=False`): `job.qpu_cost` gives the actual cost and `job.info`
  exposes auxiliary metadata (`job.info["uncertainty"]` when observables are supplied,
  `job.info["qpu_cost"]`). Use `job.progress()` to stream logs, or poll `job.status`
  to avoid blocking.

  In a notebook, a link to the job on the Dashboard is also displayed when
  `haiqu.verbosity` is `"high"`.
* **Return type:**
  HybridJobModel

- **Raises:**
  **ValueError** -- If IBM Quantum rejects the IBM Quantum token and instance for a run on an IBM device, or
  cannot be reached to check them. Dry runs do not check them.

#### Examples

Single circuit, no parameters, no observables:

```python theme={null}
>>> from qiskit import QuantumCircuit
>>> qc = QuantumCircuit(2)
>>> qc.h(0)
>>> qc.cx(0, 1)
>>> qc.measure_all()
>>> job = haiqu.run(circuits=qc, device_id="aer_simulator")
>>> job.result()  # Returns: [dist_c1] (bitstrings in Qiskit convention)
[{'00': 0.504, '11': 0.496}]
```

Single circuit, multiple parameters, no observables:

```python theme={null}
>>> from qiskit import QuantumCircuit
>>> from qiskit.circuit import Parameter
>>> theta = Parameter('θ')
>>> qc = QuantumCircuit(2)
>>> qc.ry(theta, 0)
>>> qc.cx(0, 1)
>>> qc.measure_all()
>>> job = haiqu.run(
...     circuits=qc,
...     parameters=[[0.5], [1.0]],
...     device_id="aer_simulator",
... )
>>> job.result()  # Returns: [[dist_c1_p1, dist_c1_p2]]
[[{'00': 0.934, '11': 0.066}, {'00': 0.802, '11': 0.198}]]
```

Single circuit, no parameters, multiple observables:

```python theme={null}
>>> from qiskit import QuantumCircuit
>>> from qiskit.quantum_info import SparsePauliOp
>>> qc = QuantumCircuit(2)
>>> qc.h(0)
>>> qc.cx(0, 1)
>>> obs = [SparsePauliOp("ZZ"), SparsePauliOp("XY")]
>>> job = haiqu.run(
...     circuits=qc,
...     observables=obs,
...     device_id="aer_simulator",
... )
>>> job.result()  # Returns: [[exp_c1_obs1, exp_c1_obs2]]
[[1.0, 0.018000000000000016]]
```

Single circuit, multiple parameters, multiple observables:

```python theme={null}
>>> from qiskit import QuantumCircuit
>>> from qiskit.circuit import Parameter
>>> from qiskit.quantum_info import SparsePauliOp
>>> theta = Parameter('θ')
>>> qc = QuantumCircuit(2)
>>> qc.ry(theta, 0)
>>> qc.cx(0, 1)
>>> params = [[0.5], [1.0]]
>>> obs = [SparsePauliOp("ZZ"), SparsePauliOp("XX")]
>>> job = haiqu.run(
...     circuits=qc,
...     parameters=params,
...     observables=obs,
...     device_id="aer_simulator",
... )
>>> job.result()  # Returns: [[[exp_c1_obs1_p1, exp_c1_obs1_p2], [exp_c1_obs2_p1, exp_c1_obs2_p2]]]
[[[1.0, 1.0], [0.49, 0.846]]]
```

Multiple circuits, no parameters, no observables:

```python theme={null}
>>> from qiskit import QuantumCircuit
>>> qc1 = QuantumCircuit(2)
>>> qc1.h(0)
>>> qc1.cx(0, 1)
>>> qc1.measure_all()
>>> qc2 = QuantumCircuit(2)
>>> qc2.x(0)
>>> qc2.cx(0, 1)
>>> qc2.measure_all()
>>> circuits = [qc1, qc2]
>>> job = haiqu.run(circuits=circuits, device_id="aer_simulator")
>>> job.result()  # Returns: [dist_c1, dist_c2]
[{'11': 0.524, '00': 0.476}, {'11': 1.0}]
```

Multiple circuits, multiple parameters, no observables:

```python theme={null}
>>> from qiskit import QuantumCircuit
>>> from qiskit.circuit import Parameter
>>> theta = Parameter('θ')
>>> qc1 = QuantumCircuit(2)
>>> qc1.ry(theta, 0)
>>> qc1.cx(0, 1)
>>> qc1.measure_all()
>>> qc2 = QuantumCircuit(2)
>>> qc2.rx(theta, 0)
>>> qc2.cz(0, 1)
>>> qc2.measure_all()
>>> circuits = [qc1, qc2]
>>> params = [[[0.5], [1.0]], [[0.3], [0.7]]]  # Parameters for each circuit
>>> job = haiqu.run(
...     circuits=circuits,
...     parameters=params,
...     device_id="aer_simulator",
... )
>>> job.result()  # Returns: [[dist_c1_p1, dist_c1_p2], [dist_c2_p1, dist_c2_p2]]
[[{'00': 0.955, '11': 0.045}, {'00': 0.783, '11': 0.217}],
 [{'00': 0.982, '01': 0.018}, {'00': 0.882, '01': 0.118}]]
```

Multiple circuits, no parameters, multiple observables:

```python theme={null}
>>> from qiskit import QuantumCircuit
>>> from qiskit.quantum_info import SparsePauliOp
>>> qc1 = QuantumCircuit(2)
>>> qc1.h(0)
>>> qc1.cx(0, 1)
>>> qc2 = QuantumCircuit(2)
>>> qc2.x(0)
>>> qc2.cx(0, 1)
>>> circuits = [qc1, qc2]
>>> obs = [[SparsePauliOp("ZZ"), SparsePauliOp("XX")],
...        [SparsePauliOp("YY"), SparsePauliOp("ZX")]]  # Observables for each circuit
>>> job = haiqu.run(
...     circuits=circuits,
...     observables=obs,
...     device_id="aer_simulator",
... )
>>> job.result()  # Returns: [[exp_c1_obs1, exp_c1_obs2], [exp_c2_obs1, exp_c2_obs2]]
[[1.0, 1.0], [0.0, -0.0020000000000000018]]
```

Multiple circuits, multiple parameters, multiple observables:

```python theme={null}
>>> from qiskit import QuantumCircuit
>>> from qiskit.circuit import Parameter
>>> from qiskit.quantum_info import SparsePauliOp
>>> theta = Parameter('θ')
>>> qc1 = QuantumCircuit(2)
>>> qc1.ry(theta, 0)
>>> qc1.cx(0, 1)
>>> qc2 = QuantumCircuit(2)
>>> qc2.rx(theta, 0)
>>> qc2.cz(0, 1)
>>> circuits = [qc1, qc2]
>>> params = [[[0.5], [1.0]], [[0.3], [0.7]]]  # Parameters for each circuit
>>> obs = [[SparsePauliOp("ZZ"), SparsePauliOp("XX")],
...        [SparsePauliOp("YY"), SparsePauliOp("ZX")]]  # Observables for each circuit
>>> job = haiqu.run(
...     circuits=circuits,
...     parameters=params,
...     observables=obs,
...     device_id="aer_simulator",
... )
>>> job.result()
# Returns: [
#     [[exp_c1_obs1_p1, exp_c1_obs1_p2], [exp_c1_obs2_p1, exp_c1_obs2_p2]],
#     [[exp_c2_obs1_p1, exp_c2_obs1_p2], [exp_c2_obs2_p1, exp_c2_obs2_p2]],
# ]
[[[1.0, 1.0], [0.482, 0.8280000000000001]],
 [[-0.016000000000000014, 0.003999999999999963],
  [-0.02400000000000002, 0.008000000000000007]]]
```

Example of using Matrix Product State (MPS) simulator for larger scale experiments:

```python theme={null}
>>> from qiskit.circuit.random import random_circuit
>>> circuit = random_circuit(num_qubits=20, depth=3, max_operands=3, seed=2025, measure=True)
>>> job = haiqu.run(circuit, device_id="aer_simulator", options={
...                 "method": "matrix_product_state",  # set method to MPS
...                 "matrix_product_state_max_bond_dimension": 16,  # preferably, limit the bonds
...               })
```

Scoring a noisy run against an ideal simulation of the same circuit, to see what
mitigation bought you:

```python theme={null}
>>> from qiskit import QuantumCircuit
>>> qc = QuantumCircuit(2)
>>> qc.h(0)
>>> qc.cx(0, 1)
>>> qc.measure_all()
>>> noisy = haiqu.run(qc, device_id="fake_kyiv", shots=4096, auto_quality=True)
>>> noisy.result()
[{'00': 0.5017, '01': 0.0071, '10': 0.0046, '11': 0.4866}]
>>> noisy.quality  # Hellinger fidelity against the ideal Bell state
0.9882
>>> mitigated = haiqu.run(
...     qc, device_id="fake_kyiv", shots=4096, use_mitigation=True, auto_quality=True
... )
>>> mitigated.result()
[{'00': 0.4967, '01': 0.0041, '10': 0.0024, '11': 0.4969}]
>>> mitigated.quality
0.9936
>>> mitigated.info["quality_assessment"]  # per-circuit breakdown
{'per_circuit': {'circ-abcdefab-cdef-abcd-efab-cdefabcdefab': 0.9936}}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.