Parity with measurement and feed-forward¶
Download Notebook - parity_laqcc.ipynb
parity_laqcccombines several input qubits into one parity target. It XORs the input parity into the target:
An odd number of input ones flips the target; an even number leaves it unchanged. The target can start in either state.
The operation also works coherently on superpositions. Internal ancilla measurements drive corrections rather than reading out the input parity.
Run from a source checkout with development dependencies installed. The repository default is little endian.
1. Choose the implementation¶
parity_sequentialuses controlled gates without extra qubits.For four or more inputs,
parity_laqccuses two ancilla registers and classical feed-forward to arrange the quantum gates in constant-depth layers. This trades extra qubits and measurement feedback for quantum depth; it does not imply constant execution time on every device.With fewer than four inputs, it falls back to the sequential circuit. Both implementations use the same call:
parity(target, qreg).Use the built-in helper to size the simulator:
n_inputs = 4
total_qubits = parity_laqcc_total_qubits(n_inputs)
print(f"{n_inputs} inputs + 1 target + {total_qubits - n_inputs - 1} ancillas = {total_qubits} qubits")
4 inputs + 1 target + 2 ancillas = 7 qubits
2. Check even and odd parity¶
Prepare a computational-basis input, apply parity, and measure both registers.
For \(|1,0,1,1\rangle\), the input parity is \(1\oplus0\oplus1\oplus1=1\). The target flips from \(0\) to \(1\), or from \(1\) to \(0\).
Compile once and reuse the emulator for each input. The table also checks that the input bits are preserved.
@guppy
def basis_example(bits: array[bool, 4], target_bit: bool) -> None:
qreg = qarray(n_inputs)
target = qubit()
for i in range(n_inputs):
if bits[i]:
x(qreg[i])
if target_bit:
x(target)
parity_laqcc(target, qreg)
output("inputs", collect_measurements(measure_array(qreg)))
output("target", measure(target).read())
emulator = basis_example.emulator(total_qubits).with_seed(42)
rows = []
for bits in [[True, False, True, True], [True, False, True, False]]:
for target_bit in [False, True]:
shot = emulator.run(bits=bits, target_bit=target_bit).collated_shots()[0]
expected = target_bit ^ (sum(bits) % 2 == 1)
assert shot["inputs"][0] == bits
assert shot["target"][0] == expected
rows.append({
"Input bits": ", ".join(str(int(bit)) for bit in bits),
"Parity": "Odd" if sum(bits) % 2 else "Even",
"Initial target": int(target_bit), "Final target": int(shot["target"][0]),
})
print(pd.DataFrame(rows).to_string(index=False, float_format=lambda value: f"{value:.4f}"))
Input bits Parity Initial target Final target
1, 0, 1, 1 Odd 0 1
1, 0, 1, 1 Odd 1 0
1, 0, 1, 0 Even 0 0
1, 0, 1, 0 Even 1 1
3. Check coherence across measurement branches¶
Start with \(|+\rangle^{\otimes4}|0\rangle\). Parity entangles the target with the inputs:
Apply
parity_sequentialafterward to undo that operation. If the LAQCC corrections preserve coherence, the inputs return to \(|+\rangle^{\otimes4}\) and the target returns to \(|0\rangle\).Force each of the four internal measurement branches with
QuantumReplay. Statevector checks verify relative phases as well as probabilities; a basis-state truth table alone would miss dephasing.
@guppy
def coherence_example() -> None:
qreg = qarray(n_inputs)
target = qubit()
transversal(h, qreg)
parity_laqcc(target, qreg)
parity_sequential(target, qreg)
state_output("inputs", qreg)
output("target", measure(target).read())
discard_array(qreg)
branches = [list(bits) for bits in itertools.product([False, True], repeat=2)]
replay = QuantumReplay(
simulator=Quest(), measurements=branches, resume_with_measurement=True,
)
result = coherence_example.emulator(total_qubits).with_simulator(replay).with_shots(4).run()
expected_state = np.ones(2**n_inputs) / np.sqrt(2**n_inputs)
rows = []
for branch, shot, measured in zip(branches, result.results, result.collated_shots()):
actual = Quest.extract_states_dict(shot)["inputs"].get_single_state()
assert_allclose_ignorephase(actual, expected_state)
assert not measured["target"][0]
rows.append({
"Ancilla outcomes": ", ".join(str(int(bit)) for bit in branch),
"Restored input fidelity": abs(np.vdot(expected_state, actual))**2,
"Final target": int(measured["target"][0]),
})
print(pd.DataFrame(rows).to_string(index=False, float_format=lambda value: f"{value:.8f}"))
Ancilla outcomes Restored input fidelity Final target
0, 0 1.00000000 0
0, 1 1.00000000 0
1, 0 1.00000000 0
1, 1 1.00000000 0
All replayed branches restore the coherent input after uncomputation. The forced outcomes test corrections; they do not estimate branch probabilities.
Parity collects information from several inputs into one target. Fanout applies one control to several targets; the two operations serve different roles.