# State preparation and QRAM circuits

PyAQASM comes with a KP-tree implementation that generates real states using RY multiplexors.

The implementation is located in qat.lang.models.KPTree. It implements the Kerenidis-Prakash tree structure to describe an arbitrary tensor (see [KP17] for more details).

This class can be used to either prepare an arbitrary (real) state or to perform calls to a QRAM (with arbitrary dimension, see below).

It is constructed using a numpy array of arbitrary shape $$(k_1, ..., k_n)$$. If the $$k_i$$ are not power of two, the array will be padded with $$0$$. The resulting tree will generate circuits over $$\sum_i \log_2{k'_i}$$ qubits where $$k'_i$$ are the padded dimensions. This information can be retrieved in the .qubits attribute of the object.

import numpy as np
from qat.lang.models import KPTree

data = np.random.random((2, 4, 8))
tree = KPTree(data)
print(tree.qubits)

[1, 2, 3]


Once a KPTree is built, the object can be used to generate QRoutine objects that perform either a QRAM load or a full state preparation.

An input data $$\alpha$$ (once normalized) with $$k$$ axis is understood as the following quantum state:

$|\alpha\rangle = \sum_{i_1,...,i_k}\alpha_{i_1 ... i_k}|i_1\rangle...|i_k\rangle$

The same piece of data $$\alpha$$ can also be understood as an adressable QRAM. In that case, we assume that some of the first $$l$$ registers already carry a non trivial quantum state of shape:

$|\beta\rangle = \sum_{i_1,..,i_l} \beta_{i_1...i_l} |i_1\rangle...|i_l\rangle$

Then one can “load” the remaining $$k-l$$ registers thus producing the following quantum state:

$\sum_{i_1,...,i_l} \frac{\beta_{i_1...i_l}}{|| D_{i_{1}..i_{l}} ||} |i_1\rangle...|i_l\rangle |D_{i_{1}...i_{l}}\rangle$

where $$|D_{i_1 ... i_l}\rangle = \sum_{i_{l+1},...,i_k}\alpha_{i_1 ... i_k}|i_{l+1}\rangle...|i_k\rangle$$

The KPTree class supports all type of QRAM load of this shape. These are accessible through the get_routine() method. This method takes a single optional argument specifying the number of axis to use as address (i.e $$l$$ in the example above).

import numpy as np
from qat.lang.models import KPTree

data = np.random.random((2, 4, 8))
tree = KPTree(data)

# This routine prepares a state corresponding to flatten(data)
rout_0 = tree.get_routine(0)

# This one uses the first qubit as an address
rout_1 = tree.get_routine(1)

# This one uses the first 3 qubits as address (i.e the first 2 axis)
rout_2 = tree.get_routine(2)

class qat.lang.models.KPTree(data)

A class representing a Kerinidis-Prakash tree for a QRAM.

This class can also be used to prepare (classical) distributions.

Parameters

data (numpy.ndarray) – a numpy array of arbitrary shape

Example of usage:

from qat.lang.models import KPTree
from qat.lang.AQASM import *
import numpy as np

data = np.random.random(size=(2, 4))

tree = KPTree(data)

# prepares a state proportional to the data
routine_state_prep = tree.get_routine(0)

# prepare either data or data depending on the state of the first qubit
routine_1 = tree.get_routine(1)

# The resulting routine can be used as a gate over the appropriate number of qubits (here 3)
prog = Program()
qbits = prog.qalloc(3)
routine_state_prep(qbits)
circuit = prog.to_circ()
print("Full state preparation:")
for op in circuit.iterate_simple():
print(op)

prog = Program()
qbits = prog.qalloc(3)
H(qbits)
routine_1(qbits)
circuit = prog.to_circ()
print("QRAM call with 1 qubit address")
for op in circuit.iterate_simple():
print(op)

Full state preparation:
('RY', [1.9599481358565838], )
('RY', [1.164734201894148], )
('CNOT', [], [0, 1])
('RY', [-0.24062886139716544], )
('CNOT', [], [0, 1])
('RY', [1.410368857987665], )
('CNOT', [], [1, 2])
('RY', [0.3567354116708534], )
('CNOT', [], [0, 2])
('RY', [-0.18847912247571447], )
('CNOT', [], [1, 2])
('RY', [0.7177579394158706], )
('CNOT', [], [0, 2])
QRAM call with 1 qubit address
('H', [], )
('RY', [1.164734201894148], )
('CNOT', [], [0, 1])
('RY', [-0.24062886139716544], )
('CNOT', [], [0, 1])
('RY', [1.410368857987665], )
('CNOT', [], [1, 2])
('RY', [0.3567354116708534], )
('CNOT', [], [0, 2])
('RY', [-0.18847912247571447], )
('CNOT', [], [1, 2])
('RY', [0.7177579394158706], )
('CNOT', [], [0, 2])

Returns a QRoutine object implementing a call to a QRAM loading the data in a quantum memory. The resulting QRoutine is composed of a sequence of RY-mutliplexors.