Uniform state preparation¶
Download Notebook - state_preparation.ipynb
uniform_state(L) prepares equal amplitudes on the first \(L\) basis states:
The same interface works whether or not \(L\) is a power of two. This notebook compares \(L=4\) and \(L=6\), checks their statevectors, and shows the resulting probabilities.
Run from a source checkout with the development dependencies installed. The repository default is little endian.
1. Four states: Hadamards are enough¶
For \(L=4\), two qubits span exactly the required states:
The factory chooses the Hadamard implementation. Start qreg at zero and pass it to the returned function.
prepare_four = uniform_state(4)
@guppy
def four_states() -> None:
qreg = qarray(2)
prepare_four(qreg)
state_output("result_state", qreg)
discard_array(qreg)
result = four_states.emulator(2).with_seed(42).run()
state = Quest.extract_states_dict(result.results[0].entries)["result_state"]
amplitudes_four = switch_endianness(state.get_single_state())
assert_allclose_ignorephase(amplitudes_four, np.ones(4) / 2)
print("All four amplitudes match 1/2, up to global phase.")
All four amplitudes match 1/2, up to global phase.
2. Six states: exclude unused addresses¶
For \(L=6\), three qubits provide eight addresses, but the last two must have zero amplitude:
The factory uses a comparator to identify valid addresses and amplitude amplification to prepare the state. The optional cnx_box and comparator_box arguments select the underlying implementations; omitting them uses these same defaults.
L = 6
n = int(np.ceil(np.log2(L)))
prepare_six = uniform_state(
L, cnx_box=cnx, comparator_box=comparator_ripple_cuccaro,
)
@guppy
def six_states() -> None:
qreg = qarray(n)
prepare_six(qreg)
state_output("result_state", qreg)
discard_array(qreg)
# Reserve simulator qubits for the comparator and amplification workspace.
result = six_states.emulator(2 * n + 5).with_seed(42).run()
state = Quest.extract_states_dict(result.results[0].entries)["result_state"]
amplitudes_six = switch_endianness(state.get_single_state())
expected = np.zeros(2**n)
expected[:L] = 1 / np.sqrt(L)
assert_allclose_ignorephase(amplitudes_six, expected)
print("The first six amplitudes match 1/sqrt(6); the remaining two are zero.")
The first six amplitudes match 1/sqrt(6); the remaining two are zero.
Compare the probabilities¶
The assertions check the complex amplitudes, including their relative phases. The bars show their squared magnitudes, \(\Pr(j)=|\langle j|u_L\rangle|^2\). These are exact simulator probabilities, rather than finite-shot estimates.
fig, axes = plt.subplots(1, 2, figsize=(9, 3), layout="constrained")
for axis, (length, amplitudes) in zip(axes, [(4, amplitudes_four), (6, amplitudes_six)]):
axis.bar(np.arange(len(amplitudes)), np.abs(amplitudes)**2)
axis.set(title=f"Uniform over {length} states", xlabel="Basis-state index",
xticks=np.arange(len(amplitudes)), ylim=(0, 0.3))
axes[0].set_ylabel("Probability")
plt.show()
To reverse a preparation, build its inverse with uniform_state(L, dagger=True). Additional workspace is internal to the preparation routine; the calling circuit keeps the same qreg interface.