Encoding data in superposition

If you want to encode data in a quantum register as a uniform superposition of states, this can be done with an algorithm proposed in a very readable 2001 paper by C.A. Trugenberger.

The algorithm utilizes three registers, a processing register to load the data, a memory register, that eventually stores the target state and a two bit auxiliary register. The main idea of the algorithm is to prepare the target state in a superposition with an auxiliary bit, rotate the state into the memory state to isolate it from the following rewinding of the operations.

The implementation in PennyLane, firstly some initialization…

import pennylane as qml
from pennylane import numpy as np

data = [0,2,5]
reg_size = int(np.ceil(np.log2(np.max(data)+1)))

processing_reg = qml.wires.Wires(range(reg_size))
utility_reg = qml.wires.Wires(range(reg_size, reg_size+2))
memory_reg = qml.wires.Wires(range(reg_size+2, 2*reg_size+2))

all_wires = qml.wires.Wires.all_wires([processing_reg, utility_reg, memory_reg])
n_wires = len(all_wires)

dev = qml.device("default.qubit", wires=all_wires)

Then a small helper function to encode data, into the processing register …

def load_data(n, wires=processing_reg, ctrl_wire=utility_reg[1]):
    
    assert len(wires) == reg_size, f"wires expected a list of size {reg_size}, got {len(wires)}."
    
    n_bin_str = np.binary_repr(n, width=reg_size)
    n_bin = [int(s) for s in n_bin_str]
    
    for i, v in enumerate(n_bin):
        if v:
            qml.PauliX(wires[i])

Eventually the center piece

@qml.qnode(dev)
def circuit():    
    qml.PauliX(wires=utility_reg[1])
    
    for m, value in enumerate(data):
        # initial state |data,01,000>
        load_data(value)
        
        for i in range(reg_size):
            qml.Toffoli(wires=[processing_reg[i], utility_reg[1], memory_reg[i]])
        
        for i in range(reg_size):
            qml.CNOT(wires=[processing_reg[i], memory_reg[i]])
            qml.PauliX(wires=memory_reg[i])
        
        qml.MultiControlledX(control_wires=memory_reg, wires=[utility_reg[0]])
        
        mu = len(data) - m
        angle = np.pi if mu == 1 else 2.*np.arctan(-np.sqrt(1/(mu-1)))
        qml.CRY(angle, wires=utility_reg)
        
        qml.MultiControlledX(control_wires=memory_reg, wires=[utility_reg[0]])
        
        for i in range(reg_size):
            qml.CNOT(wires=[processing_reg[i], memory_reg[i]])
            qml.PauliX(wires=memory_reg[i])
        
        for i in range(reg_size):
            qml.Toffoli(wires=[processing_reg[i], utility_reg[1], memory_reg[i]])
        
        load_data(value)
    
    return qml.probs(wires=memory_reg)

The algorithm creates correct uniform superpositions for different input data-sets.

Retrieving data, will be the next step.

QHack 2023: 4 week prep-challenge, day 19, A reading night

Recently, I was feeling quite comfortable with solving these challenges, to me this is a sign, that I can set the bar a little higher or mix-up my activities to increase my learning. The QHack comprises talks, the coding challenge and an open hackathon. The coding challenges will be of the same format, as my exercises of the last days, the hackathon invites for free-form contributions and offer much bigger rewards. Now, I deem it beneficial to broaden my view and get some inspiration reading up a little bit.

I read an insightful industry and market analysis by BCG, providing forecasts for market sizes and areas for near term applications.

Further, I found a consortium of big German companies, evaluating quantum computing: QUTAC. Some of the researched applications are outlined in this paper. Over the next few days, I want to study this overview more carefully.

QHack 2023: 4 week prep challenge, day 15, SWAP Test

I did another basic exercise on the QML track, but honestly, I was struggling a lot with this one. The hard-earned victories are both satisfying and instructive. But first a quick outline of the exercise.

The challenge was to estimate the inner product of two feature vectors. A distance measure is derived from this quantity and is used to perform a k-NN (k-nearest neighbor) algorithm – in this configuration an artificial use case.

The inner product can be estimated with the SWAP-test.

SWAP test circuit, taken from Wikipedia.

The inner product estimate is calculated from

\[ P(\text{control qubit} = 0) = \frac{1}{2} + \frac{1}{2}\lvert\langle \psi | \phi \rangle\rvert^2 \]

Implementation

In this implementation, each feature vector is normalized, and since each vector has dimension two, a single qubit suffices to encode the data.

from pennylane import numpy as np
import pennylane as qml


def distance(A, B):
    """Function that returns the distance between two vectors.

    Args:
        - A (list[int]): person's information: [age, minutes spent watching TV].
        - B (list[int]): person's information: [age, minutes spent watching TV].

    Returns:
        - (float): distance between the two feature vectors.
    """

    def prep_state():
        """Function that encodes the two 2D feature vectors into the amplitudes of one qubit each."""
        
        tan_thetaA = A[0]/A[1]
        thetaA = np.arctan(tan_thetaA)
        tan_thetaB = B[0]/B[1]
        thetaB = np.arctan(tan_thetaB)
        
        qml.RY(2.*thetaA, wires=0)
        qml.RY(2.*thetaB, wires=1)
    
    
    dev = qml.device("default.qubit", wires=3)
    @qml.qnode(dev)
    def circuit():
        """Quantum circuit that encodes two 2D-feature vectors and performs a SWAP test.
        
        Returns:
            - ([float, float]): probabilities of states measured on the control qubit.
        """
        prep_state()
        
        qml.Hadamard(wires=2)
        qml.CSWAP(wires=range(2,-1,-1))
        qml.Hadamard(wires=2)
        
        return qml.probs(2)

    # print(qml.draw(circuit)())
    
    tmp = circuit()[0] # .5 + .5<A,B>^2
    A_times_B = np.sqrt(2.*tmp-1.)
    
    return np.sqrt(2*(1.-A_times_B))

The provided tests check out.

Lessons learned

I will seize this opportunity to reflect upon my solution strategies. The problem statement was a little less concrete than the ones of previous exercises. In the starter code two hints were pointing at the SWAP Test and the PennyLane amplitude encoding functionality. After an initial implementation failed, I did some more research and found a SWAP-test implementation on a PennyLane forum. This was more confusing then helpful, I believe the core confusion being how the normalization is done in this encoding strategy.

Anyways, the clean bottom up solution, along with some debugging print-outs eventually worked out.

QHack 2023: 4 week prep challenge, day 13, A Quantum Algorithm

After having a rest day yesterday, today, I’m continuing with the most advanced exercise on the algorithms track. The task is an extension to the Deutsch-Jozsa algorithm. You are given four individual functions, of which either all four are equally balanced/constant or two are balanced and two are constant. The algorithm has to decide which case is materialized, using no more than 8 qubits.

Depiction of the oracle, taken from here.

Now, I think it is easiest to evaluate the functions one by one, and sum up the result bits using the QFT-adder logic from a previous exercise and distinguish the cases depending on the resulting count.

Optimizing for gate count

If you want to minimize the gate count, you can perform each function once, writing the results of each function to individual qubits. Eventually, you can make controlled increments on a two qubit register (mod 4), where each wire serves as a control. You need 8 qubits in total

  • two input qubits
  • four qubits to store the function results
  • two qubits for the counter

You have three possible cases:

  • Four balanced functions, no increments are performed, the counter register reads |00>
  • Four constant functions, four increments are performed, the counter register overflows and eventually reads |00>
  • Two balanced functions and two constant functions, the counter register reads |10>

Optimizing for qubit number

If you want to minimize the number of qubits needed, I came up with a similar solution that uses only four qubits:

  • two input qubits
  • one control qubit
  • one qubit for the counter

With the known two possible result cases, you can opt to make half increments, instead of full increments, this reduces the counter register size by one qubit.

After performing the controlled counting, you can undo the oracle operation by applying the inverse and therefore freeing up the control qubit for further use.

Here is my code, doing just that …

def deutsch_jozsa(fs):
    """Function that determines whether four given functions are all of the same type or not.

    Args:
        - fs (list(function)): A list of 4 quantum functions. Each of them will accept a 'wires' parameter.
        The first two wires refer to the input and the third to the output of the function.

    Returns:
        - (str) : "4 same" or "2 and 2"
    """

    dev = qml.device("default.qubit", wires=4, shots=1)
    inp_wires = range(2)
    
    def controlled_half_increment(crtl_wire, wire):
        """Quantum function capable of performing a half increment on a single qubit (i.e. mod 1)

        Args:
            - ctrl_wire (int): wire to control the operation
            - wire (int): wire on which the function will be executed on.
        """

        wires = [wire, ]
        qml.QFT(wires=wires)

        qml.CRZ(np.pi/2, wires=[crtl_wire, wires[0]])

        qml.adjoint(qml.QFT)(wires=wires)
    
    @qml.qnode(dev)
    def circuit():
        """Implements the modified Deutsch Jozsa algorithm."""
        
        qml.broadcast(qml.Hadamard, wires=inp_wires, pattern="single")

        for f in fs:
            f(range(3))
            controlled_half_increment(2,3)
            qml.adjoint(f)(range(3))
        
        qml.broadcast(qml.Hadamard, wires=inp_wires, pattern="single") # this is 

        return qml.sample(wires=[3,]) # 1 .. 2 and 2, 0 .. 4 the same

    sample = circuit()
    
    four_same = "4 same"
    two_and_two = "2 and 2"
    
    return four_same if sample == 0 else two_and_two

Testing the algorithm

The starter code includes a scheme for generating the functions. First, I tested the code, by passing the provided test data to the algorithm – but to me it looked like the results were published wrong. I continued by passing the same function four times – checks out, and then passing combinations of two functions – and ended up observing both cases: equal situations and two and two situations. I took it for a successful test.

QHack 2023: 4 week prep challenge, day 12, Quantum Counting

I continued on the quantum algorithms track, with the quantum counting exercise. The number of solutions to a search problem should be estimated using Grover search and Quantum Phase Estimation.

I’ve found a very concise and clear video on Grover’s algorithm and the accompanying basic example code.

The coding itself boiled down to some basic numpy matrix wrangling, and implementing the proposed algorithm. A more detailed description of the solution scheme can be found here.

Since I had a little more time, I also read up on QFT arithmetics. I wasn’t aware of the tutorial yesterday, it would have been very helpful though!

QHack 2023: 4 week prep challenge, day 11, A Quantum Adder (QFT)

Today, I’m continuing with the next exercise from the algorithms track. The task is to build a number adder using the Fourier space.

Firstly, a Fourier transform is applied on the initial state, i.e. a state |0> or |1> in the computational basis is flipped into the X-Y-plane. The qubits are then rotated around the Z-axis, with an angle according to the number to add. Eventually, the state is transformed back from the Fourier space.

The transformation rule for the QFT reads

\[ QFT|x\rangle = \frac{1}{\sqrt{M}} \sum^{M-1}_{k=0} \exp(\frac{2 \pi i}{M} x k) |k\rangle \]

If you now want to add to a state in the Fourier space, you have to rotate the qubits accordingly. It is important to notice, that a rotation only effects the |1> portion, but not the |0> portion of a qubit’s state.

Having that in mind, it was relatively straight forward to come up with the solution…

def qfunc_adder(m, wires):
    """Quantum function capable of adding m units to a basic state given as input.

    Args:
        - m (int): units to add.
        - wires (list(int)): list of wires in which the function will be executed on.
    """

    qml.QFT(wires=wires)

    for w, _ in enumerate(wires):
        angle = m*np.pi/(2**w)
        qml.RZ(angle, wires=w)

    qml.QFT(wires=wires).inv()

… the tests check out, success!

Through the year with Quantum Computing

The most important quantum computing learning events for beginners and intermediates.

The most important quantum computing learning events for beginners and intermediates.

I missed some learning opportunities in my first few months into quantum computing, I wrote this post, to help you do better than me!

As a beginner myself, I am continuously searching for learning opportunities and for opportunities to connect with like-minded people. Outside academia, Quantum Computing is a relatively young technology. Tools and resources are continuously created to help a broader audience to get into the field.

There is a range of easily accessible offerings from various Quantum Computing providers and organizations. I personally found many tutorials and their respective problem statements very abstract, and difficult to relate to. So I looked out for more interactive learning formats.

Online hackathons (e.g. by IBM Quantum or by Pennylane) are ideal for hands-on learning of algorithms, frameworks and key-concepts. They are often very well curated, (apart from some bare basics) provide all the information needed to complete the challenges, but require you to study the provided materials closely. Furthermore, they might come with a message board, to connect with people in your area.

That being said, my personal experience with the hackathons was, that they are oftentimes announced at short notice, and it’s easy to miss out on them. However, you can join on the spot, no preparation strictly required (some basics of the target framework are highly recommended though).

More advanced people may look out to join a dedicated mentorship program, like the one by OQSF, or for offerings that require applicants to demonstrate a lot more work for the community, like the Qiskit Advocate program. These advanced programs need much more preparation and planning.

I attached a small overview of offerings I found, and when they took place in the past. Application deadlines to restricted offerings precede the event some time – so be careful with these.

Calendar overview of past quantum computing events, 2020-2022.

I enjoyed doing hackathons a lot, and with this overview you too should be able to anticipate them. If you still miss one – don’t worry, the resources are usually published with some delay on github, so you can enjoy the contents out of competition. There’s always a next event around the corner, so keep at it!