from qiskit import QuantumCircuit,  execute, Aer, BasicAer
from qiskit.visualization import plot_histogram
from qiskit.quantum_info import Statevector, state_fidelity
import numpy as np
pi = np.pi
from qiskit.visualization import plot_state_qsphere, plot_histogram, plot_bloch_multivector

# For n qubit system 

n = 4

N = 2**n

w = np.exp(2*pi*(1j)/N)

# Initiliaing the qft matrix and assigning it values

qft = np.zeros((N,N),dtype = 'complex_')

for i in range(N):
    for j in range(N):
        qft[i][j] = pow(w,(i*j))/n

print(qft)

# Initializing the input state
in_state = np.zeros(N,dtype = 'complex_')

# State |0010> in vector form has third element non zero
in_state[2] = 1j /pow(2, 0.5)

# State |0011> in vector form has fourth element non zero
in_state[3] = 1 /pow(2, 0.5)

#print(in_state)


# Output state

out_state = np.matmul(qft, in_state)

# Making a state_vector 
matrix_output = Statevector(out_state, dims=(2, 2, 2, 2))

print(out_state)

# Checking if output state is normalized

norm = pow(np.matmul(out_state.conjugate(), out_state), 0.5)
print("Norm = ", norm)


n=4
qftn=QuantumCircuit(n,n,name='QFT')


for i in range(n-1,-1,-1):
    # Puttign each qubit in unifrom superposition
    qftn.h(i)
    
    # Adding the relevent rotation gate
    p=0
    for j in range(i):
        p+=1
        qftn.cp(pi/(2**(p)),i-j-1,i) 
    qftn.barrier()

# Swapping the output qubits
for i in range(int(n/2)):
    qftn.swap(i,n-1-i)

qftn.draw('mpl')
        

# Perparing the inout state for the quntum circuit
sv1 = Statevector.from_label('0011')
sv2 = Statevector.from_label('0010')

sv = (sv1 + 1j* sv2)/pow(2,0.5) 
# Representing the input state on the block sphere
plot_bloch_multivector(sv)


# Evolovinf the state and representing the output state on the block sphere
circuit_output = sv.evolve(qftn)
plot_bloch_multivector(circuit_output)

# Verifying that the circuit and matrix give the same results
#print(matrix_output)
#print(circuit_output)

fidelity = state_fidelity(matrix_output, circuit_output)

print("Fidelity =", fidelity)

# The QFT inverse circuit
n=4
iqftn=QuantumCircuit(n,n,name='IQFT')

for i in range(int(n/2)):
    iqftn.swap(i,n-1-i)

iqftn.barrier()

for i in range(n):
    p=i+1
    for j in range(i):
        p-=1
        iqftn.cp(-pi/(2**(p)),j,i)
    iqftn.h(i)
    iqftn.barrier()

iqftn.draw('mpl')



