Skip to content

Postselection

Measuring a qubit gives a random outcome. Postselection keeps only the runs in which the outcome is the one you chose, and throws the others away: the state becomes the branch that matches, renormalised to length one. It is a tool for analysing circuits and building heralded protocols; on real hardware it costs repeated runs, since the discarded outcomes still happen.

Step 1. Postselect one half of a Bell pair

import numpy as np
import dense_evolution as de

qasm = """OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
h q[0];
cx q[0], q[1];
"""
circ = de.QASMParser().parse(qasm)
sim = de.DenseSVSimulator(2)
sim.run_circuit_jit(circ)
sv0 = sim.get_statevector()
sv1, p = de.postselect(sv0, 2, 0, '+')
print(np.round(np.asarray(sv1), 4), round(p, 4))
[0.5+0.j 0.5+0.j 0.5+0.j 0.5+0.j] 0.5

h and cx build the Bell state (|00> + |11>)/√2. postselect(sv0, 2, 0, '+') keeps the branch where qubit 0 is found in |+>. Because the two qubits are entangled, qubit 1 is forced into |+> as well: all four amplitudes become 0.5, the state |+>|+>. The second number, 0.5, is how often that outcome occurs, so half the runs would be kept.

The target state can be '0', '1', '+', '-', '+i' or '-i'.


Details

Definition. Postselection is the operation in the definition of PostBQP: the computation is conditioned on a measurement outcome with nonzero probability (Aaronson, Quantum computing, postselection, and probabilistic polynomial-time, quant-ph/0412187, Definition 1). An outcome with zero probability raises ValueError.

How it is applied. postselect multiplies the state by the projector |psi><psi| on the chosen qubit and divides by the square root of the probability. It is not unitary, so it acts on the statevector directly and returns (state, probability).

Checked in the tests:

  • '0' and '1' against keeping only the matching amplitudes;
  • '+', '-', '+i', '-i' against the basis-change circuit (h, or sdg then h), postselection on |0> or |1>, and the inverse change;
  • postselecting a qubit in the middle of a circuit against copying it with cx into a fresh ancilla and postselecting the ancilla at the end (Aaronson, Sect. 3).

postselect

Postselection: keep only the branch of the state in which a qubit is found in a chosen state, then renormalise.

This is the operation in the definition of PostBQP (Aaronson, quant-ph/0412187, Definition 1): the computation is conditioned on a measurement outcome that has nonzero probability. It is not unitary, so it is applied to the statevector directly, as the projector |psi><psi| on the qubit followed by renormalisation, and the success probability is returned with the new state.

postselect

postselect(sv, n_qubits, qubit, state='1')

Project qubit onto state and renormalise.

state is one of '0', '1', '+', '-', '+i', '-i'. Returns (new_sv, probability), where probability is the chance of that outcome before postselection. Raises ValueError if the probability is zero, since postselection is only defined for outcomes that can occur (Aaronson, Definition 1, condition (i)).

Source code in dense_evolution/circuits/postselect.py
def postselect(sv, n_qubits, qubit, state='1'):
    """
    Project `qubit` onto `state` and renormalise.

    `state` is one of '0', '1', '+', '-', '+i', '-i'. Returns
    `(new_sv, probability)`, where `probability` is the chance of that outcome
    before postselection. Raises ValueError if the probability is zero, since
    postselection is only defined for outcomes that can occur (Aaronson,
    Definition 1, condition (i)).
    """
    if state not in POSTSELECT_STATES:
        raise ValueError(f"state must be one of {list(POSTSELECT_STATES)}, got {state!r}")
    if not 0 <= qubit < n_qubits:
        raise ValueError(f"qubit index out of range for {n_qubits} qubits")
    if HAS_JAX:
        ensure_x64()
    psi = xp.asarray(POSTSELECT_STATES[state])
    t = xp.asarray(sv).reshape(2 ** qubit, 2, 2 ** (n_qubits - qubit - 1))
    overlap = xp.einsum('j,ijk->ik', xp.conj(psi), t)
    probability = float(xp.sum(xp.abs(overlap) ** 2))
    if probability <= 1e-15:
        raise ValueError(f"outcome {state!r} on qubit {qubit} has zero probability")
    projected = xp.einsum('j,ik->ijk', psi, overlap).reshape(-1)
    return projected / np.sqrt(probability), probability