Alias sampling¶
Alias sampling is a technique for sampling from a discretized probability distribution that we can use in order to implement arbitrary state preparation.
For our state prep to work, we need a few magic ingredients:
- A uniform superposition preparation subroutine
- A QROM data loader
- Some utility functions to build the alias and threshold values.
All of these, along with the Qubrick that builds the final circuit, are provided in Workbench Algorithms. For interested parties, the technique is an implementation of the circuit in Fig. 11 in Google's encoding electronic spectra paper. ⧉
The alias sampler¶
Most users should not need to access the alias sampler utilities directly. They are shown here in case anyone is interested.
%load_ext autoreload
%autoreload 2
from psiqworkbench import Qubits, QPU
from workbench_algorithms import AliasSampling, SelectNaive, SwapUp, DataLookupClean, USP
from workbench_algorithms.utils import generate_alias_table
import numpy as np
# the state we want to prepare (currently only positive, real values)
inputs = [1 / 16, 2 / 16, 0 / 16, 5 / 16, 4 / 16, 4/ 16]
# how many bits of precision do we want for our state prep
bits_of_precision = 3
Let's get the alias and threshold values. The alias values correspond to the index the value at the top of the column originated from. The threshold values correspond to the index at which the blocks transition from one index to another. In this case, our inputs should yield:
alias = [4, 5, 3, 5, 5, 5]
threshold = [3, 6, 0, 7, 7, 0]
alias, threshold = generate_alias_table(inputs, bits_of_precision)
print(np.array(alias), threshold)
[3 4 5 4 4 3] [np.uint64(3), np.uint64(6), np.uint64(0), np.uint64(6), np.uint64(0), np.uint64(4)]
If we change the inputs to [1/4, 3/8, 1/8, 1/4], we should get:
alias = [0, 1, 1, 3]
threshold = [0, 0, 4, 0]
inputs = [1/4, 3/8, 1/8, 1/4]
alias, threshold = generate_alias_table(inputs, bits_of_precision)
print(alias, threshold)
[np.uint64(1), np.uint64(1), np.uint64(0), np.uint64(3)] [np.uint64(4), np.uint64(0), np.uint64(4), np.uint64(0)]
Let's see if the AliasSampling Qubrick does what it's supposed to:
# set up our system parameters
inputs = [1 / 16, 2 / 16, 0 / 16, 5 / 16, 4 / 16, 4/ 16]
bits_of_precision = 3
# set up our number of qubits
len_inputs = int(np.ceil(np.log2(len(inputs))))
num_prep = len_inputs
num_alias = len_inputs
num_keep = bits_of_precision
num_coin = bits_of_precision
num_comp_anc = bits_of_precision
num_qrom = (num_alias + num_keep)
num_qubits = num_prep + num_qrom + num_coin + num_comp_anc
# set up our QPU
qc = QPU()
qc.reset(num_qubits)
prep = Qubits(len_inputs, 'prep', qc)
# set up our qubricks
usp = USP()
select = SelectNaive()
swap_up = SwapUp()
qrom = DataLookupClean(select, swap_up)
alias_sampling_prep = AliasSampling(qrom=qrom, usp=usp)
# run and draw the subroutine
alias_sampling_prep.compute(prep, inputs, bits_of_precision, lambda_val=1)
qc.draw()
We can then test to see that it implements the desired state (note that currently we need to pull out the probabilities by hand since the state is entangled with some garbage qubits).
probs = []
for i in range(len(inputs)):
probs.append(prep.peek_read_probability(i))
print(probs)
[0.062499999999999986, 0.12499999999999997, 0.0, 0.31249999999999994, 0.24999999999999992, 0.24999999999999994]