Introduction

In this post we will be implementing a quantum adder circuit using Qelvin. The circuit is presented in this paper written by L. Ruiz-Perez and J. C. Garcia-Escartin and it takes advantage of the Quantum Fourier Transform (QFT).

Theory

Quantum Fourier Transform

In a $n$ qubit system, the Quantum Fourier Transform acting on an orthonormal basis state $|j\rangle,~n=1,2,…,n$ can be expressed as

\[\begin{equation} |j\rangle\mapsto\frac{1}{\sqrt{2^n}}\sum_{k=0}^{2^n-1}e^{i\frac{2\pi kj}{2^n}}|k\rangle, \end{equation}\]

and the corresponding inverse QFT takes form

\[\begin{equation} |k\rangle\mapsto\frac{1}{\sqrt{2^n}}\sum_{j=0}^{2^n-1}e^{-i\frac{2\pi kj}{2^n}}|j\rangle. \end{equation}\]

QFT adder

Consider integers $0\leq a,b\leq2^n-1$ represented by $n$ qubit states $|a\rangle$ and $|b\rangle$ respectively. The total number of qubits in the adder circuit totals to $2n+1$, $n$ qubits for each state representing a integer and one additional qubit for the carry bit. Thus the whole register the circuit operates on is

\[\begin{equation} |a_1a_2...a_n\rangle|0\rangle|b_1b_2...b_n\rangle, \end{equation}\]

where $a_1a_2…a_n=a_1\cdot2^0+a_2\cdot 2^1+…+a_n\cdot2^n$ and $b_1b_2…b_n=b_1\cdot2^0+b_2\cdot 2^1+…+b_n\cdot2^n$ are the binary representations of $a$ and $b$. Not that the register stores the integers in big-endian format.

The next part focuses on register $|a_1a_2…a_n\rangle|0\rangle$. We first apply a QFT to obtain state (no $SWAP$ gates applied)

\[\begin{equation} \begin{split} |a_1a_2...a_n\rangle|0\rangle\mapsto^{QFT} & \frac{1}{\sqrt{2^{n+1}}}\sum_{k=0}^{2^n}e^{i\frac{2\pi ka}{2^{n+1}}}|k\rangle \newline & = \left(|0\rangle+e^{-i\frac{2\pi a}{2^{n+1}}}|1\rangle\right)\otimes\left(|0\rangle+e^{-i\frac{2\pi a}{2^{n}}}|1\rangle\right)\otimes...\otimes\left(|0\rangle+e^{-i\frac{2\pi a}{2^1}}|1\rangle\right) \newline & = |\phi_{n+1}(a)|\phi_n(a)\rangle...|\phi_1(a)\rangle, \end{split} \end{equation}\]

where $\phi_k(a)=\frac{1}{\sqrt{2}}\left(|0\rangle+e^{i\frac{2\pi a}{2^k}}|1\rangle\right)$. Note that

\[\begin{equation} e^{i\frac{2\pi a}{2^k}}=e^{i2\pi\frac{a_1\cdot2^0+a_2\cdot2^1+...+a_n\cdot2^{n-1}}{2^k}}=e^{i2\pi\left(a_1\cdot2^{-k}+a_2\cdot2^{-k+1}+..+a_k\cdot2^0\right)}=e^{i2\pi0.a_ka_{k-1}...a_1}, \end{equation}\]

where $0.a_ka_{k-1}…a_1$ is the binary fraction. This implies that $\phi_k(a)$ contains the lower $k$ binary digits of $a$. Next we define a controlled rotation gate

\[\begin{equation} R_j=\begin{bmatrix} 1 & 0 & 0 & 0 \newline 0 & 1 & 0 & 0 \newline 0 & 0 & 1 & 0 \newline 0 & 0 & 0 & e^{i\frac{2\pi}{2^j}} \end{bmatrix}. \end{equation}\]

For each $|\phi_k(a)\rangle,~0\lt k\lt n$ we apply $k$ controlled rotation gates where $R_1$ is controlled by $|b_k\rangle$, $R_2$ by $|b_{k-1}\rangle$ and so on. To state $\phi_{n+1}(a)$ we apply $n$ controlled rotation gates such that $R_2$ controlled by $|b_n\rangle$, $R_3$ controlled by $|b_{n-1}\rangle$ and so on.

Let us examine how applying these gates affects $|\phi_k(a)\rangle$. Let $R_j^{b_i}$ be a rotation gate controlled by $b_i$. We get

\[\begin{equation} \begin{split} R_k^{b_1}...R_2^{b_{k-1}}R_1^{b_k}|\phi_k(a)\rangle & = R_k^{b_1}...R_2^{b_{k-1}}\frac{1}{\sqrt{2}}\left(|0\rangle+e^{i2\pi\left(0.a_ka_{k-1}...a_1+b_k\cdot2^{-1}\right)}|1\rangle\right)\newline & . \newline & . \newline & . \newline & = \frac{1}{\sqrt{2}}\left(|0\rangle+e^{i2\pi\left(0.a_ka_{k-1}...a_1+b_k\cdot2^{-1}+b_{k-1}\cdot2^{-2}+...+b_1\cdot2^{-k}\right)}|1\rangle\right)\newline & = \frac{1}{\sqrt{2}}\left(|0\rangle+e^{i2\pi\left(0.a_ka_{k-1}...a_1+0.b_kb_{k-1}...b_1\right)}|1\rangle\right)\newline & = \phi_k(a+b). \end{split} \end{equation}\]

Now by applying the described procedure to every state in the QFT register we end up with state

\[\begin{equation} |\phi_{n+1}(a+b)\rangle|\phi_n(a+b)\rangle...\phi_1(a+b)\rangle, \end{equation}\]

and by applying the inverse QFT on the register we end up with state $|a+b\rangle$.

Implementation

Say we want to add integers $a=1=10_2$ and $b=3=11_2$ (big-endian notation). We initialize a state $|10\rangle|0\rangle|11\rangle$.

import numpy as np
from qelvin import QRegister, QCircuit

# number of qubits representing b
N_b = 2
# number of qubits representing a + one qubit representing the carry bit
N_fft = 3
N = N_b + N_fft

psi = QRegister(N, 0b10011) # |a|0|b|=|10|0|11|
circ = QCircuit(psi)

Next we perform QFT on $|a\rangle|0\rangle$.

for j in range(N_b, N):
    for k in range(N_b, j):
        circ.cp(np.pi/float(2**(j-k)), j, k)
    circ.h(j)

print(circ)
circ.run()

Below is the circuit produced by this snippet. H is the Hadamard gate and $R_j$ is represented by P(2.0*pi/2**j).

                                                                           
q0-------------------------------------------------------------------------
                                                                           
                                                                           
                                                                           
                                                                           
q1-------------------------------------------------------------------------
                                                                           
                                                                           
      ---------  ---------             ---------                           
     |         ||         |           |         |                          
q2---|    H    ||P(+1.571)|-----------|P(+0.785)|--------------------------
     |         ||         |           |         |                          
      ---------  ---------             ---------                           
                     |      ---------      |      ---------                
                     |     |         |     |     |         |               
q3-------------------o-----|    H    |-----|-----|P(+1.571)|---------------
                           |         |     |     |         |               
                            ---------      |      ---------                
                                           |          |      ---------     
                                           |          |     |         |    
q4-----------------------------------------o----------o-----|    H    |----
                                                            |         |    
                                                             ---------     

After this we apply the controlled rotation gates.

for j in range(0, N_b+1):
    for k in range(0, N_b):
        theta = 2.0*np.pi*2**(-(k+1+N_b-j+1-N_b))
        circ.cp(theta, k, N_b+j)

print(circ)
circ.run()

Below is the circuit produced by this snippet.

q0--------o---------------------o---------------------o--------------------
          |                     |                     |                    
          |                     |                     |                    
          |                     |                     |                    
          |                     |                     |                    
q1--------|----------o----------|----------o----------|----------o---------
          |          |          |          |          |          |         
          |          |          |          |          |          |         
      ---------  ---------      |          |          |          |         
     |         ||         |     |          |          |          |         
q2---|P(+1.571)||P(+0.785)|-----|----------|----------|----------|---------
     |         ||         |     |          |          |          |         
      ---------  ---------      |          |          |          |         
                            ---------  ---------      |          |         
                           |         ||         |     |          |         
q3-------------------------|P(+3.142)||P(+1.571)|-----|----------|---------
                           |         ||         |     |          |         
                            ---------  ---------      |          |         
                                                  ---------  ---------     
                                                 |         ||         |    
q4-----------------------------------------------|P(+0.000)||P(+3.142)|----
                                                 |         ||         |    
                                                  ---------  ---------     

Finally we apply the inverse QFT on state $|\phi(a+b)\rangle$.

for j in reversed(range(N_b, N)):
    circ.h(j)
    for k in reversed(range(N_b, j)):
        circ.cp(-np.pi/float(2**(j-k)), j, k)

print(circ)
circ.run()

Below is the circuit produced by this snippet.

                                                                           
q0-------------------------------------------------------------------------
                                                                           
                                                                           
                                                                           
                                                                           
q1-------------------------------------------------------------------------
                                                                           
                                                                           
                            ---------             ---------  ---------     
                           |         |           |         ||         |    
q2-------------------------|P(-0.785)|-----------|P(-1.571)||    H    |----
                           |         |           |         ||         |    
                            ---------             ---------  ---------     
                 ---------      |      ---------      |                    
                |         |     |     |         |     |                    
q3--------------|P(-1.571)|-----|-----|    H    |-----o--------------------
                |         |     |     |         |                          
                 ---------      |      ---------                           
      ---------      |          |                                          
     |         |     |          |                                          
q4---|    H    |-----o----------o------------------------------------------
     |         |                                                           
      ---------                                                            

Let us now print the final state.

psi_out = circ.state()

print("Final state:")
print(psi_out)

(real, imag) = psi_out[0b00111]
prob = real*real + imag*imag
print(f"Probability for state |a+b|b|=|001|11|: {prob}")

Printed state is presented below.

Final state:
[ 0.000+0.000*j  0.000+0.000*j  0.000+0.000*j  -0.000+0.000*j  0.000+0.000*j 
 0.000+0.000*j  0.000+0.000*j  1.000-0.000*j  0.000+0.000*j  0.000+0.000*j 
 0.000+0.000*j  -0.000+0.000*j  0.000+0.000*j  0.000+0.000*j  0.000+0.000*j 
 0.000+0.000*j  0.000+0.000*j  0.000+0.000*j  0.000+0.000*j  -0.000+0.000*j 
 0.000+0.000*j  0.000+0.000*j  0.000+0.000*j  0.000+0.000*j  0.000+0.000*j 
 0.000+0.000*j  0.000+0.000*j  -0.000+0.000*j  0.000+0.000*j  0.000+0.000*j 
 0.000+0.000*j  0.000+0.000*j ]
Probability for state |a+b|b|=|001|11|: 1.0000000000000004

And there we have it! We have succesfully computed the sum $a+b=1+3=4=001_2$.