Skip to main content

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

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:
Only X, Y and Z are allowed. Use Z for a qubit you do not want rotated.
1

Build a circuit whose basis you can predict

Put qubit 0 into |+⟩ and leave qubit 1 in |0⟩.
2

Read qubit 0 in X and qubit 1 in Z

Because the last character is qubit 0, the basis is "ZX"not "XZ".
3

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

Reading the results

Results gain one nesting level for the basis axis, in the order you supplied the bases: result_by_basis() saves you from indexing by position — it returns one dict per circuit, keyed by the basis string:
The keys are unambiguous because duplicate bases are rejected at submission.
result_by_basis() blocks until the job reaches a terminal state, like result(). Use retrieve_status() first if you do not want to block.

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

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.
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.
Parameters combine freely with bases. The parameter axis is the inner one, so result()[circuit][basis][parameter].
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.
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.

Rules and limits

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