# Do not forget to Import Qiskit 

from qiskit.visualization import * 
from qiskit import * 

S_simulator=Aer.backends(name='statevector_simulator')[0] 
M_simulator=Aer.backends(name='qasm_simulator')[0] 

from math import pi


# lets make a fucntions called rotate we do this in python by using def and 
# then the function name along with the variables we want our function to take and a colon

def rotations(circuit, n): 
    
    # if we went through the whole circuit, we do not have to do anymore work
    # just return the circuit we made
    if n == 0: 
        return circuit 
    
    
    # circuit is the QuantumCircuit we want to use the qft on, 
    # n is the number of qubits in the circuit 
    n-=1 # 4th qubit is in the n=3 spot on the circuit
        # mth qubit is on the n=m-1 spot on the circuit
        # also allows us to reduce down to n = 0 and to stop doing the rotations
        
    circuit.h(n) # Apply Hadamard gate to the qubit we are currently working on
    
    for qubit in range(n): 
        
        # now use controlled phase gate to rotate 
        # also divide the rotation angle by 2 each time 
        
        circuit.cp(pi/2**(n-qubit), qubit, n) # circuit.cp() takes three arguments 
                                            # (theta, target qubit, control qubit) 
            
        #so the first time through in this loop the dummy  variable 
        # "qubit" is 0 , so we will do rotation of of pi/2**(n-0) on the 0th qubit using the
        # nth qubit as a control qubit. And so on and so forth. 
        
        
    rotations(circuit, n) # do the same thing to the next qubit     


qc = QuantumCircuit(5,5)
rotations(qc,5)
qc.draw(output='mpl')

def swap_qubits(circuit,n): 
    
    # only need to loop through half of the circuit, since we swap two qubits at a time
    
    for qubit in range(n//2): # // floor function helps us round down when we have odd qubits 
        circuit.swap(qubit, n-qubit-1)
    return circuit 

def qft(circuit, n): 
    
    rotations(circuit, n)
    swap_qubits(circuit,n)
    return circuit

qc=QuantumCircuit(5,5)
qft(qc,5)
qc.draw(output='mpl')

# Note see how the 3rd qubit does not get swapped since it would swap with itself

# Lets take some state x and put it in the QFT and see what we get out 

x=10 

from math import log 
def find_number_of_qubits(x): 
    
    powers_of_two=0
    exponent=0
    while powers_of_two<x:
        powers_of_two=2**exponent
        exponent+=1
    
    number_of_qubits = int(log(powers_of_two,2))
    return number_of_qubits

n = find_number_of_qubits(x)  

encoding = bin(x) 
# encoding has an extra '0b' that just says hey i am a binary number we can chop it off 
# by replacing it with nothing 

print('State we need to encode to try out qft: ', encoding.replace('0b', ''))

qc = QuantumCircuit(n,n) # number of qubits calculated before 

qc.x(0)
qc.x(2)

qc.barrier() # seperate steps with a barrier. 

print('state before QFT: ', execute(qc, S_simulator).result().get_statevector())

qft(qc, n)

print('state after QFT: ', execute(qc, S_simulator).result().get_statevector())


# Our output is a little messy lets grab the state and clean it up

state = execute(qc, S_simulator).result().get_statevector()

# we use formatting %f to print trimmed version of the real and complex parts of each amplitude 

# this is cool to pick up but do not worry too much about this 

real=[]
imag=[]
for i in state: 
    
    print('%f' % i.real, '%f' % i.imag , 'j')
    
# We see that we get a weird repeating signal or wave of values for a constant state 
    real.append(i.real)
    imag.append(i.imag)
    
from matplotlib import pyplot as plt 

plt.plot(range(len(state)), real)
plt.plot(range(len(state)), imag)

plt.show()

# What would we get if we take an input where we have an even superposition of all states. 
from math import sqrt 
n=3 

a= sqrt(1/(2**n)) # amplitude 

state=[a,a,a,a,a,a,a,a]

qc=QuantumCircuit(3)

qc.initialize(state, [0,1,2]) 

qc.barrier() # seperate steps with a barrier. 

print('state before QFT: ', execute(qc, S_simulator).result().get_statevector())

qft(qc, n)

print('state after QFT: ', execute(qc, S_simulator).result().get_statevector())




# Our output is a little messy lets grab the state and clean it up

state = execute(qc, S_simulator).result().get_statevector()

# we use formatting %f to print trimmed version of the real and complex parts of each amplitude 

# this is cool to pick up but do not worry too much about this 

real=[]
imag=[]
for i in state: 
    
    print('%f' % i.real, '%f' % i.imag , 'j')
    
# We see that we get a weird repeating signal or wave of values for a constant state 
    real.append(i.real)
    imag.append(i.imag)
    
from matplotlib import pyplot as plt 

plt.plot(range(len(state)), real)
plt.plot(range(len(state)), imag)

plt.show()