import numpy as np
import math
import qiskit as qiskit
from numpy import sqrt
from random import randint
from qiskit import *
from qiskit import Aer, QuantumCircuit, IBMQ, execute, quantum_info, transpile
from qiskit.visualization import plot_state_city, plot_bloch_multivector
from qiskit.visualization import plot_histogram
from qiskit.tools import job_monitor
from qiskit.providers.fake_provider import FakeOpenPulse2Q, FakeOpenPulse3Q, FakeManilaV2, FakeManila

provider = IBMQ.load_account()
print('se ha ejecutado correctamente')


def SoQ_Grover_0(qc, x_qubit_0,x_qubit_1,y_qubit):
    qc.ccx(x_qubit_0,x_qubit_1,y_qubit)
    return qc

def SoQ_Grover_1(qc, x_qubit_0,x_qubit_1,y_qubit):
    qc.x(x_qubit_0)
    qc.ccx(x_qubit_0,x_qubit_1,y_qubit)
    qc.x(x_qubit_0)
    return qc

def SoQ_Grover_2(qc, x_qubit_0,x_qubit_1,y_qubit):
    qc.x(x_qubit_1)
    qc.ccx(x_qubit_0,x_qubit_1,y_qubit)
    qc.x(x_qubit_1)
    return qc

def SoQ_Grover_3(qc, x_qubit_0,x_qubit_1,y_qubit):
    qc.x(x_qubit_0)
    qc.x(x_qubit_1)
    qc.ccx(x_qubit_0,x_qubit_1,y_qubit)
    qc.x(x_qubit_0)
    qc.x(x_qubit_1)
    return qc

def random_oracle(qc, x_qubit_0,x_qubit_1,y_qubit):
    rand=randint(0,3)
    if rand==3:
    	SoQ_Grover_0(qc, x_qubit_0,x_qubit_1,y_qubit)
    elif rand==2:
    	SoQ_Grover_1(qc, x_qubit_0,x_qubit_1,y_qubit)
    elif rand==1:
    	SoQ_Grover_2(qc, x_qubit_0,x_qubit_1,y_qubit)
    else:
    	SoQ_Grover_3(qc, x_qubit_0,x_qubit_1,y_qubit)         
    return qc 


def Grover_Iteration(qc, x_qubit_0,x_qubit_1):
    qc.h(range(2))
    qc.x(range(2))
    qc.h(x_qubit_1)
    qc.cx(x_qubit_0,x_qubit_1)
    qc.h(x_qubit_1)
    qc.x(range(2))
    qc.h(range(2))
    
    return qc

print('se ha ejecutado correctamente')

#Se añade el circ_measure, y el Grover_Iteration con rangos de registro
x_register=2
y_register=1
measure_register=2
y_position=x_register+y_register-1

#Se define la variable circ
circ = QuantumCircuit(x_register+y_register,measure_register)
#Se describen las compuertas y barreras
circ.x(y_position)
circ.barrier()
circ.h(range(x_register+y_register))
circ.barrier()
random_oracle(circ, 0,1,2)
circ.barrier()
Grover_Iteration(circ, 0,1)
circ.measure(range(x_register),range(measure_register))

circ.draw('mpl')

circ.count_ops() #número de compuertas

circ.depth() #calcula la profundidad del circuito

x_register=2
y_register=1
measure_register=2
y_position=x_register+y_register-1

circ = QuantumCircuit(x_register+y_register,measure_register)

circ.x(y_position)
circ.barrier()
circ.h(range(x_register+y_register))
circ.barrier()
random_oracle(circ, 0,1,2)
circ.barrier()
Grover_Iteration(circ, 0,1)
circ.measure(range(x_register),range(measure_register))
#nota: son las mismas instruccions expresadas a la hora de dar el resultado gráfico arriba, el circuito
#completamente estructurado.
#circ.draw('mpl') EN ESTE CASO NO LO UTILIZAREMOS solo daremos instrucciones al circuito
#circ.draw('mpl')

#simulación Aer Simulator
backend = Aer.get_backend('aer_simulator') #simulador a usar
job = execute(circ, backend, shots=1024) #descripción del trabajo
result = job.result()
counts = result.get_counts(circ)
plot_histogram(counts) #expresa los datos en un histograma

#Simulador de prueba FakeManilaV2
backend = FakeManilaV2() #la única diferencia es que ahora establecemos un backend determinado
#para el circuito a simular
x_register=2
y_register=1
measure_register=2
y_position=x_register+y_register-1

circ = QuantumCircuit(x_register+y_register,measure_register)

circ.x(y_position)
circ.barrier()
circ.h(range(x_register+y_register))
circ.barrier()
random_oracle(circ, 0,1,2)
circ.barrier()
Grover_Iteration(circ, 0,1)
circ.measure(range(x_register),range(measure_register))

circ.draw('mpl')


circ.count_ops()

circ.depth()

transpiled_circ = transpile(circ, backend)
transpiled_circ.draw()
#Resultados de transpilación con FakeManilaV2

transpiled_circ.count_ops()

transpiled_circ.depth()

job = backend.run(transpiled_circ) #la variable establece la función de correr el backend = FakeManilaV2
counts = job.result().get_counts()
plot_histogram(counts) #analiza los datos a través del histograma
#Resultados de Simulación con FakeManilaV2

#Simulador de prueba FakeManila
backend = FakeManila() 
x_register=2
y_register=1
measure_register=2
y_position=x_register+y_register-1

circ = QuantumCircuit(x_register+y_register,measure_register)

circ.x(y_position)
circ.barrier()
circ.h(range(x_register+y_register))
circ.barrier()
random_oracle(circ, 0,1,2)
circ.barrier()
Grover_Iteration(circ, 0,1)
circ.measure(range(x_register),range(measure_register))

circ.draw('mpl')


circ.count_ops()

circ.depth()

transpiled_circ = transpile(circ, backend)
transpiled_circ.draw()
#Resultados de transpilación con FakeManila

transpiled_circ.count_ops()

transpiled_circ.depth()

job = backend.run(transpiled_circ) #la variable establece la función de correr el backend = FakeManila
counts = job.result().get_counts()
plot_histogram(counts) #analiza los datos a través del histograma
#Resultados de Simulación con FakeManila

Como observamos, tenemos un poco de ruido.(Con el backend=FakeManila)

#Simulador de prueba FakeOpenPulse2Q
backend = FakeOpenPulse2Q()
x_register=2
y_register=1
measure_register=2
y_position=x_register+y_register-1

circ = QuantumCircuit(x_register+y_register,measure_register)

circ.x(y_position)
circ.barrier()
circ.h(range(x_register+y_register))
circ.barrier()
random_oracle(circ, 0,1,2)
circ.barrier()
Grover_Iteration(circ, 0,1)
circ.measure(range(x_register),range(measure_register))

circ.draw('mpl')


circ.count_ops()

circ.depth()

job = backend.run(circ)
counts = job.result().get_counts()
plot_histogram(counts)
#Resultados de Simulación con FakeOpenPulse2Q

Como observamos, tenemos demasiado ruido en comparación
con las otras simulaciones.(Con el backend=FakeOpenPulse2Q)

#Simulador de prueba FakeOpenPulse3Q
backend = FakeOpenPulse3Q()
x_register=2
y_register=1
measure_register=2
y_position=x_register+y_register-1

circ = QuantumCircuit(x_register+y_register,measure_register)

circ.x(y_position)
circ.barrier()
circ.h(range(x_register+y_register))
circ.barrier()
random_oracle(circ, 0,1,2)
circ.barrier()
Grover_Iteration(circ, 0,1)
circ.measure(range(x_register),range(measure_register))

circ.draw('mpl')


circ.count_ops()

circ.depth()

transpiled_circ = transpile(circ, backend)
transpiled_circ.draw()
#Resultados de transpilación con FakeOpenPulse3Q

transpiled_circ.count_ops()

transpiled_circ.depth()

job = backend.run(transpiled_circ)
counts = job.result().get_counts()
plot_histogram(counts)
#Resultado de Simulación con FakeOpenPulse3Q

En esta simulación, no poseemos ruido.(Con el backend=FakeOpenPulse3Q)