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

# Measuring in different bases

> Measuring your circuits in different Pauli bases and getting counts back

## What it does

By default a circuit with measurements is read out in the computational (Z) basis, so you get
counts of Z-basis bitstrings. `readout_bases` rotates the qubits into a different Pauli basis
just before readout, and still returns **counts** — one distribution per basis, per circuit.

This is different from passing `observables`. Observables give you expectation values, with the
distribution collapsed away. Readout bases keep the full distribution, so you can compute your
own estimators, look at correlations, or do basis-resolved tomography-style analysis.

```python theme={null}
counts = haiqu.run(
    circuit,
    shots=8000,
    device_id="aer_simulator",
    readout_bases=["XX", "ZZ"],
).result_by_basis()[0]

counts["XX"]  # distribution measured in the X basis
counts["ZZ"]  # distribution measured in the Z basis
```

<Note>
  Your circuits keep their own measurements, exactly as for a normal distribution job. Haiqu
  strips the final measurements, inserts the basis rotation, and re-measures.
</Note>

***

## Basis strings and qubit order

A basis is one Pauli character per qubit, using **Qiskit's little-endian convention** — the same
one `observables` uses. The **last** character applies to qubit 0:

```
basis  = "X Z Y Z"
qubit  =  3 2 1 0
```

Only `X`, `Y` and `Z` are allowed. Use `Z` for a qubit you do not want rotated.

<Steps>
  <Step title="Build a circuit whose basis you can predict">
    Put qubit 0 into `|+⟩` and leave qubit 1 in `|0⟩`.

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

    circuit = QuantumCircuit(2)
    circuit.h(0)
    circuit.measure_all()
    ```
  </Step>

  <Step title="Read qubit 0 in X and qubit 1 in Z">
    Because the last character is qubit 0, the basis is `"ZX"` — **not** `"XZ"`.

    ```python theme={null}
    job = haiqu.run(
        circuit,
        shots=8000,
        device_id="aer_simulator",
        readout_bases=["ZX", "XZ"],
    )
    ```
  </Step>

  <Step title="Check the result">
    `|+⟩` is an X eigenstate and `|0⟩` is a Z eigenstate, so `"ZX"` is deterministic. Reading the
    same state in `"XZ"` measures qubit 0 in Z and qubit 1 in X, which is uniformly random.

    ```python theme={null}
    by_basis = job.result_by_basis()[0]

    by_basis["ZX"]  # {'00': 1.0}
    by_basis["XZ"]  # {'00': 0.249, '01': 0.258, '10': 0.246, '11': 0.248}
    ```
  </Step>
</Steps>

<Tip>
  If a basis you expect to be deterministic comes back uniform, you have almost certainly written
  the string in big-endian order. Reverse it.
</Tip>

***

## Reading the results

Results gain one nesting level for the basis axis, in the order you supplied the bases:

| Job                    | Nesting                       |
| ---------------------- | ----------------------------- |
| No parameters          | `[circuit][basis]`            |
| With a parameter sweep | `[circuit][basis][parameter]` |

`result_by_basis()` saves you from indexing by position — it returns one dict per circuit, keyed
by the basis string:

```python theme={null}
job = haiqu.run(
    circuits=[circuit_a, circuit_b],
    shots=8000,
    device_id="aer_simulator",
    readout_bases=["XX", "YY", "ZZ"],
)

for index, by_basis in enumerate(job.result_by_basis()):
    for basis, distribution in by_basis.items():
        print(f"circuit {index}, basis {basis}: {distribution}")
```

The keys are unambiguous because duplicate bases are rejected at submission.

<Note>
  `result_by_basis()` blocks until the job reaches a terminal state, like `result()`. Use
  `retrieve_status()` first if you do not want to block.
</Note>

***

## Cost

Each basis is a separate execution of the circuit, so a job costs `len(readout_bases) * shots`
shots in total. Three bases at 8000 shots is 24000 shots on the device.

What you do **not** pay twice for is compilation: all bases of a circuit share a single
transpilation and a single layout, because the rotation is applied after transpilation. The bases
are therefore directly comparable — they run on the same physical qubits with the same routing.

***

## Combining with other features

<AccordionGroup>
  <Accordion title="Error mitigation">
    `use_mitigation=True` works as usual. Readout error acts on the post-rotation bitstring, which
    is exactly what the mitigation stack models, so no special handling is needed.

    ```python theme={null}
    job = haiqu.run(
        circuit,
        shots=8000,
        device_id="ibm_torino",
        readout_bases=["XX", "ZZ"],
        use_mitigation=True,
    )
    ```
  </Accordion>

  <Accordion title="Circuit packing">
    `use_packing=True` is supported. Packing replicates one circuit across the device, and every
    copy is read out in the same basis, so a basis string still has one character per **circuit**
    qubit — not per packed qubit.

    ```python theme={null}
    job = haiqu.run(
        circuit,
        shots=8000,
        device_id="ibm_torino",
        readout_bases=["ZX"],
        use_packing=True,
        pack_size=2,
    )
    ```
  </Accordion>

  <Accordion title="Parameterized circuits">
    Parameters combine freely with bases. The parameter axis is the inner one, so
    `result()[circuit][basis][parameter]`.

    ```python theme={null}
    job = haiqu.run(
        parameterized_circuit,
        parameters=[[0.0], [3.14159]],
        shots=8000,
        device_id="aer_simulator",
        readout_bases=["X", "Z"],
    )
    ```
  </Accordion>

  <Accordion title="Hybrid programs">
    In a hybrid program, add a `ReadoutBasisLayer` to mark where the rotation happens, and pass
    the bases to `flow()`. Place the layer after the transpilation layer so all bases share one
    transpilation.

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

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

    job = haiqu.flow(
        program=program,
        circuits=circuit,
        shots=8000,
        readout_bases=["XX", "ZZ"],
    )
    ```

    The layer and the bases go together: supplying one without the other is rejected, because a
    layer with no bases would silently return computational-basis counts.
  </Accordion>
</AccordionGroup>

***

## Rules and limits

A submission is rejected if any of these does not hold:

| Rule                               | Why                                                                                                                         |
| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| No `observables` at the same time  | Observables return expectation values, bases return distributions. Submit separate jobs.                                    |
| Characters from `X`, `Y`, `Z` only | Use `Z` for an unrotated qubit; `I` is not accepted, to keep the meaning unambiguous.                                       |
| No duplicate bases                 | Results are keyed by basis, so a repeated basis would be ambiguous.                                                         |
| All bases the same length          | One character per qubit, so every basis covers the same register.                                                           |
| One character per circuit qubit    | Checked against the circuit width, or against the active qubits when the circuit is already transpiled.                     |
| `run_type` is not `StatevectorRun` | A statevector run returns amplitudes, not counts; a basis rotation there is a local change of frame you can apply yourself. |
