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

# Building and running flows

> Compose a hybrid program from layers and execute it with haiqu.flow

This guide walks through composing a [hybrid program](/hybrid/overview) from layers and running it
with `haiqu.flow`. If you are new to layers and programs, read the [overview](/hybrid/overview)
first.

<Steps>
  <Step title="Authenticate and start an experiment">
    Log in and initialize an experiment so your circuits and jobs are tracked.

    ```python theme={null}
    from haiqu.sdk import haiqu

    haiqu.login()
    haiqu.init("Hybrid program demo")
    ```

    <Check>
      You're authenticated and ready to build a program. See the
      [`login` reference](/reference/core/login) for API-key options.
    </Check>
  </Step>

  <Step title="Build a program">
    A program is an ordered list of layers. Start with an `InputLayer`, end with a `DeviceLayer`,
    and add processing steps in between.

    ```python theme={null}
    from haiqu.sdk.hybrid import HybridProgram, layers

    program = HybridProgram(layers=[
        layers.InputLayer(),
        layers.DeviceLayer(device_id="aer_simulator"),
    ])
    ```

    <Check>
      The program is validated as you build it — for example, leaving out the `DeviceLayer` raises an
      error.
    </Check>
  </Step>

  <Step title="Run the flow">
    Pass the program and your circuit(s) to `haiqu.flow`.

    ```python theme={null}
    from qiskit import QuantumCircuit

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

    job = haiqu.flow(program, circuits=qc, shots=1000)
    ```

    <Check>
      `haiqu.flow` returns a job handle you can track and collect results from.
    </Check>
  </Step>

  <Step title="Read the results">
    Call `job.result()` to retrieve the output.

    ```python theme={null}
    job.result()
    # [{'00': 0.504, '11': 0.496}]
    ```

    Results come back as a nested list ordered by **circuits → observables → parameters**. With a
    single circuit and no observables, that is a one-element list holding the measurement
    distribution (bitstrings in Qiskit bit-order).

    <Check>
      You've built and run your first hybrid program.
    </Check>
  </Step>
</Steps>

## Worked examples

### Measure observables

Supply `observables` to get expectation values instead of a distribution. A program that takes
observables needs a layer that computes them; the simplest choice is the grouped `EstimatorLayer`,
which evaluates the observables and applies error mitigation. The order of Pauli terms in a string
follows the Qiskit reversed-order convention (for example, `"IZ"` measures qubit 0 in the Z basis).

```python theme={null}
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from haiqu.sdk.hybrid import HybridProgram, layers

program = HybridProgram(layers=[
    layers.InputLayer(),
    layers.EstimatorLayer(),
    layers.DeviceLayer(device_id="aer_simulator"),
])

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

obs = [SparsePauliOp("ZZ"), SparsePauliOp("XY")]
job = haiqu.flow(program, circuits=qc, observables=obs)
job.result()
# [[1.0, 0.018]]
```

Configure the mitigation through the layer's flags — for example
`EstimatorLayer(noise_tailoring=True)`, or `EstimatorLayer(mitigation_enabled=False)` to turn it off.

For full control, compute the observables by hand instead of using the grouped `EstimatorLayer`:
split them with `ObservableSplitLayer` and evaluate them with `QWCComputeLayer`.

```python theme={null}
program = HybridProgram(layers=[
    layers.InputLayer(),
    layers.ObservableSplitLayer(),
    layers.QWCComputeLayer(),
    layers.DeviceLayer(device_id="aer_simulator"),
])
```

<Warning>
  Building a program only validates its structure. A program that passes `observables` but has no
  `EstimatorLayer` (or `ObservableSplitLayer` → `QWCComputeLayer`) is rejected when you call
  `haiqu.flow`, not when you construct it.
</Warning>

### A full pipeline

Compose transpilation, packing, and error mitigation into one program. Each layer applies its step
in order before the circuits reach the device:

```python theme={null}
program = HybridProgram(layers=[
    layers.InputLayer(),
    layers.TranspilationLayer(optimization_level=3),
    layers.PackingLayer(pack_size=2),
    layers.EstimatorLayer(
        mitigation_enabled=True,
        readout_mitigation=True,
        dynamical_decoupling=True,
    ),
    layers.DeviceLayer(device_id="fake_torino"),
])

job = haiqu.flow(program, circuits=qc, observables=obs)
```

### Sweep over parameters

Pass `parameters` to run a parametrized circuit at several values. The extra dimension appears last
in the result ordering:

```python theme={null}
from qiskit import QuantumCircuit
from qiskit.circuit import Parameter
from haiqu.sdk.hybrid import HybridProgram, layers

program = HybridProgram(layers=[
    layers.InputLayer(),
    layers.DeviceLayer(device_id="aer_simulator"),
])

theta = Parameter("θ")
qc = QuantumCircuit(2)
qc.ry(theta, 0)
qc.cx(0, 1)
qc.measure_all()

job = haiqu.flow(program, circuits=qc, parameters=[[0.5], [1.0]])
job.result()
# [[{'00': 0.934, '11': 0.066}, {'00': 0.802, '11': 0.198}]]
```

<Note>
  **Estimate cost before running.** Pass `dry_run=True` to stop just before device execution and
  estimate the QPU cost. `job.result()` is then empty; read `job.estimated_qpu_cost` instead.

  ```python theme={null}
  job = haiqu.flow(program, circuits=qc, dry_run=True)
  job.estimated_qpu_cost
  ```
</Note>

See the [`haiqu.flow` reference](/reference/run/flow) for the full signature and every supported
combination of circuits, parameters, and observables, and the
[hybrid program reference](/reference/run/hybrid_program) for all layers and their parameters.
