import numpy as np
# Importing standard Qiskit libraries
from qiskit import QuantumCircuit, transpile, Aer, IBMQ
from qiskit.tools.jupyter import *
from qiskit.visualization import *
#from ibm_quantum_widgets import *

# Loading your IBM Q account(s)
provider = IBMQ.load_account()

from qiskit import assemble
import numpy as np
import matplotlib.pyplot as plt

n=4
grover_ckt = QuantumCircuit(n+1, n)
marked = [1,0,1,1] # 1101 element is marked (lsb to msb)=>13

def apply_oracle(n,marked,ckt):
    control0 = [i for i in range(n) if not marked[i]]
    ckt.x(control0)
    ckt.mct(list(range(n)),n)
    ckt.x(control0)
    ckt.draw()
    
    


def reflect_uniform(ckt,n):
    ckt.h(list(range(n)))
    ckt.x(list(range(n)))
    ckt.mct(list(range(n)),n)
    ckt.x(list(range(n)))
    ckt.h(list(range(n)))
    ckt.x(n)
    pass
    




grover_ckt.x(n)
grover_ckt.barrier()
grover_ckt.h(list(range(n)))

grover_ckt.draw()


svsim = Aer.get_backend('statevector_simulator') # Tell Qiskit how to simulate our circuit
qobj = assemble(grover_ckt)     # Create a Qobj from the circuit for the simulator to run
result = svsim.run(qobj).result() # Do the simulation and return the result
statevector = result.data()['statevector']
statevector = statevector[:2**n]

marked = [1,0,1,1] # Corresponds to integer 1101 in binary => 13
ket_a = np.zeros(2**n)
ket_a[13] =1
ket_e = (np.ones(2**n) - ket_a)/np.sqrt(2**n -1)



def get_projection(psi,e,a ):
    proj =  [np.real(np.vdot(e,psi)), np.real(np.vdot(a,psi))]
    return proj

def plt_vector(proj, axes =[0.0,1.0,0.0,1.0]):
    x_pos = 0
    y_pos = 0
    x_direct = proj[0]
    y_direct = proj[1]

    # Creating plot
    fig, ax = plt.subplots()
    ax.quiver(x_pos, y_pos, x_direct, y_direct,scale=1.0)

    ax.axis(axes)

    # show plot
    plt.show()

proj = get_projection(statevector, ket_e, ket_a)
plt_vector(proj)

#grover_ckt.append(oracle, list(range(n+1)))
apply_oracle(4,marked,grover_ckt)
grover_ckt.barrier()

grover_ckt.draw()

reflect_uniform(grover_ckt,n)

grover_ckt.barrier()
grover_ckt.draw()

svsim = Aer.get_backend('statevector_simulator') # Tell Qiskit how to simulate our circuit
qobj = assemble(grover_ckt)     # Create a Qobj from the circuit for the simulator to run
result = svsim.run(qobj).result() # Do the simulation and return the result
statevector = result.data()['statevector']

statevector = statevector[:2**n]
proj = get_projection(statevector, ket_e, ket_a)
plt_vector(proj)

apply_oracle(4,marked,grover_ckt)

grover_ckt.barrier()
reflect_uniform(grover_ckt,n)
grover_ckt.barrier()


grover_ckt.draw()

svsim = Aer.get_backend('statevector_simulator') # Tell Qiskit how to simulate our circuit
qobj = assemble(grover_ckt)     # Create a Qobj from the circuit for the simulator to run
result = svsim.run(qobj).result() # Do the simulation and return the result
statevector = result.data()['statevector']

statevector = statevector[:2**n]
proj = get_projection(statevector, ket_e, ket_a)
plt_vector(proj)

apply_oracle(4,marked,grover_ckt)

grover_ckt.barrier()
reflect_uniform(grover_ckt,n)
grover_ckt.barrier()
grover_ckt.draw()


svsim = Aer.get_backend('statevector_simulator') # Tell Qiskit how to simulate our circuit
qobj = assemble(grover_ckt)     # Create a Qobj from the circuit for the simulator to run
result = svsim.run(qobj).result() # Do the simulation and return the result
statevector = result.data()['statevector']

statevector = statevector[:2**n]
proj = get_projection(statevector, ket_e, ket_a)
plt_vector(proj, axes = [-1.0,1.0,-1.0,1.0])

from math import *
N = (math.sqrt(n**2))
theta0 = asin(1/N)
theta0

T = int((((3.14/2)/theta0)-1)/2)
T

n=4
grover_ckt = QuantumCircuit(n+1, n)
marked = [1,0,1,1] # 1101 element is marked (lsb to msb)=>13
grover_ckt.x(n)
grover_ckt.barrier()
grover_ckt.h(list(range(n+1)))
grover_ckt.barrier()

for _ in range(int(np.floor(T))):
    apply_oracle(4,marked,grover_ckt)

    grover_ckt.barrier()
    reflect_uniform(grover_ckt,n)
    grover_ckt.barrier()

for j in range(n):
    grover_ckt.measure(j,j)


grover_ckt.draw()

sim = Aer.get_backend('qasm_simulator') # Tell Qiskit how to simulate our circuit
qobj = assemble(grover_ckt)     # Create a Qobj from the circuit for the simulator to run
result = sim.run(qobj).result() # Do the simulation and return the result

result

counts = result.get_counts(grover_ckt)


plot_histogram(counts)
