Skip to main content

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.

The configuration_recovery flag in SKQDOptions controls how the SQD post-processing loop in haiqu.postprocess_skqd handles measurement bitstrings that hardware noise pushed out of the target particle-number sector. This page walks through what the flag does on a small Single-Impurity Anderson Model (SIAM), so you can decide whether to enable it.

The system

We use the same SIAM that ships with the simple SKQD example: four spatial orbitals (one impurity, three bath sites), at half-filling.
from haiqu.sdk.skqd import siam_hamiltonian, get_orbital_rotation, rotate_basis

norb = 4
nelec = (2, 2)  # (n_alpha, n_beta)

h1e_site, h2e_site = siam_hamiltonian(norb=norb, t=1.0, U=10.0, V=1.0, mu=-5.0)

orbital_rotation = get_orbital_rotation(norb)
h1e, h2e = rotate_basis(h1e_site, h2e_site, orbital_rotation.T.conj())
The Krylov circuits that feed postprocess_skqd run on 2 * norb = 8 qubits.

Bitstring layout

Each shot returns 8 bits. We split them into two halves: the left four bits encode beta (spin-down) occupations, the right four encode alpha (spin-up). Within each half, position 0 is the rightmost bit (orbital 0), then orbital 1, orbital 2, orbital 3 going left:
   beta orbitals       alpha orbitals
  ┌─────────────┐    ┌─────────────┐
   b₃ b₂ b₁ b₀         a₃ a₂ a₁ a₀
So the bitstring 00110011 means:
  • Beta: orbitals 0 and 1 occupied (rightmost two bits of the left half).
  • Alpha: orbitals 0 and 1 occupied (rightmost two bits of the right half).
This is one of the dominant ground-state determinants for our SIAM in the momentum basis. (With occupancy 0.5 on orbitals 1 and 2 — see below — there are actually four equally-weighted dominant determinants; we’ll come back to the others in the worked examples.)

Valid configurations

Because nelec = (2, 2), a valid bitstring has exactly two ones in each half. There are (42)2=36\binom{4}{2}^2 = 36 valid configurations out of 28=2562^8 = 256 total. A handful of examples:
BitstringBeta orbitalsAlpha orbitalsValid?
001100110, 10, 1yes
010100110, 20, 1yes
001111000, 12, 3yes
0001001100, 1no (n_beta = 1)
101100110, 1, 30, 1no (n_beta = 3)

What hardware noise does

Read-out noise flips bits independently with some probability per qubit. A perfect shot of 00110011 can land as 00010011 (one beta bit flipped 1→0) or 10110011 (one beta bit flipped 0→1). Both bitstrings are out of the (2, 2) sector. The fraction of out-of-sector shots grows with the number of qubits and the per-qubit error rate. For 8 qubits at 1% per-qubit read-out error, roughly 8% of shots fall outside the sector; for 40 qubits at the same rate, it’s roughly 33%.

Default behavior: postselection

With configuration_recovery=False (the default), each iteration of the SQD loop discards every out-of-sector shot. The bigger your system, the more shots you waste:
from haiqu.sdk.skqd import SKQDOptions

opts = SKQDOptions(configuration_recovery=False)  # default

What configuration recovery does

With configuration_recovery=True, from the second iteration onward the loop repairs out-of-sector bitstrings instead of discarding them, using the orbital occupancies measured at the previous iteration. For our SIAM at U/t=10U/t = 10, the converged orbital occupancies (alpha and beta both) are:
OrbitalOccupancy
00.99
10.50
20.50
30.01
Read this as: orbital 0 is “almost always occupied” in the ground state, orbital 3 is “almost always empty,” and orbitals 1 and 2 each carry half an electron on average across the ground-state distribution. The recovery procedure handles the alpha and beta halves independently. For each half:
  1. Compute a per-bit flip probability for every position. A 0 at a high-occupancy orbital is very likely to be flipped to 1; a 1 at a low-occupancy orbital is very likely to be flipped to 0; bits at orbitals near the expected density (nelec / norb = 0.5) get a small floor probability.
  2. Count how many bits are wrong (e.g., one too few for n_beta = 2).
  3. Pick exactly that many bits to flip, weighted by the flip probabilities from step 1.
The result is guaranteed to land in the (n_alpha, n_beta) sector and is biased toward configurations consistent with the current orbital occupancies. For our SIAM occupancies, the per-orbital flip probabilities work out to:
OrbitalOccupancyp(flip 0→1)p(flip 1→0)
00.990.980.0002
10.500.010.01
20.500.010.01
30.010.00020.98
These are the building blocks for the worked examples below.

Worked example 1: 0001001100110011

Starting bitstring: 00010011. Beta half is 0001, so only beta orbital 0 is occupied — n_beta = 1, target is 2, so one beta 0 needs to flip to 1. The 0-bits in beta are at orbitals 1, 2, 3. Their 0→1 flip probabilities (normalized within the 0-bit set) are:
OrbitalOccupancyp(flip 0→1)Normalized
10.500.010.495
20.500.010.495
30.010.00020.010
Recovery picks orbital 1 or orbital 2 with about equal probability (~49.5% each), and orbital 3 only about 1% of the time. Both 00110011 and 01010011 are equally-weighted dominant ground-state determinants, so either outcome lands in the high-weight part of the subspace; we essentially never recover into the much higher-energy configuration with orbital 3 occupied.

Worked example 2: 1011001100110011

Starting bitstring: 10110011. Beta half is 1011 — three orbitals occupied (0, 1, 3) — so n_beta = 3, one beta 1 needs to flip to 0. The 1-bits in beta are at orbitals 0, 1, 3. Their 1→0 flip probabilities (normalized within the 1-bit set) are:
OrbitalOccupancyp(flip 1→0)Normalized
00.990.00020.0002
10.500.010.010
30.010.980.990
Recovery picks orbital 3 about 99% of the time. The recovered beta is 0011, putting the bitstring back to 00110011 — one of the dominant ground-state determinants. In words: orbital 3 was “supposed to be empty” (occupancy 0.01) but the noisy bitstring had it occupied; recovery confidently turns it off.

Why iteration 1 has no recovery

The procedure needs orbital occupancies, which only become available after the first diagonalization. Iteration 1 always falls back to plain Hamming-weight postselection; recovery kicks in from iteration 2 onward and continues until the loop converges or max_iterations is reached.
This is a self-consistent loop: each iteration’s diagonalization produces occupancies that feed the next iteration’s recovery, which produces a different subspace, which produces refined occupancies, and so on.

When to enable it

Settingconfiguration_recovery
Noiseless simulatorFalse (default)
Hardware run, large system, high noiseTrue
Hardware run, small system, low reject %False is usually fine
The benefit grows with the fraction of out-of-sector samples. For a 40-qubit hardware run, recovery is typically the difference between converging cleanly and failing to. For a 4-qubit toy model on a simulator, it usually does nothing.
opts = SKQDOptions(
    samples_per_batch=100,
    num_batches=5,
    max_iterations=15,
    symmetrize_spin=True,
    configuration_recovery=True,  # enable recovery
    seed=42,
)

References