qat.lang.models.KPTree

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[0] or data[1] 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[0])
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.7576111591686434], [0])
('RY', [1.6226617303308588], [1])
('CNOT', [], [0, 1])
('RY', [1.1624510557137304], [1])
('CNOT', [], [0, 1])
('RY', [1.166125542802753], [2])
('CNOT', [], [1, 2])
('RY', [0.10817932206821218], [2])
('CNOT', [], [0, 2])
('RY', [-0.574878454473902], [2])
('CNOT', [], [1, 2])
('RY', [0.31591633534675684], [2])
('CNOT', [], [0, 2])
QRAM call with 1 qubit address
('H', [], [0])
('RY', [1.6226617303308588], [1])
('CNOT', [], [0, 1])
('RY', [1.1624510557137304], [1])
('CNOT', [], [0, 1])
('RY', [1.166125542802753], [2])
('CNOT', [], [1, 2])
('RY', [0.10817932206821218], [2])
('CNOT', [], [0, 2])
('RY', [-0.574878454473902], [2])
('CNOT', [], [1, 2])
('RY', [0.31591633534675684], [2])
('CNOT', [], [0, 2])
get_routine(addresses_count=0)

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.

The routine always acts on sum(log(data.shape)) qubits. The addresses_count parameter allows to control how many initial axis of the data will be considered as addresses in case of a QRAM application.

Parameters

addresses_count (int) – the number of axis to use as address. Defaults to 0 (i.e the routine will prepare a state proportional to the flattened data).

References

KP17

Iordanis Kerenidis and Anupam Prakash. Quantum recommendation systems. In 8th Innovations in Theoretical Computer Science Conference (ITCS 2017). Schloss Dagstuhl-Leibniz-Zentrum fuer Informatik, 2017.