%matplotlib inline

from qiskit import *
from qiskit.visualization import *
from qiskit.tools.monitor import *

n = 4 # Number of qubits that we are going to use in the oracle

q   = QuantumRegister(n, 'q') # The oracle qubits
out = QuantumRegister(1, 'out') # Qubit for the oracle output
c   = ClassicalRegister(n, 'c') # Classical bits needed for the result of the measurement

circ_init = QuantumCircuit(q,out,c) # Initial part of the circuit

for i in range(n):    
    circ_init.h(q[i]) # We apply H to all the oracle qubits
    
circ_init.x(out)  # We apply X and H to the output qubit
circ_init.h(out)

circ_init.barrier() # Visual barrier to separate the parts of the circuit

circ_init.draw(output='mpl')

circ_end = QuantumCircuit(q,out,c)

circ_end.barrier() # Visual barrier to separate the parts of the circuit

for i in range(n):    
    circ_end.h(q[i]) # We apply H to all the oracle qubits
    
circ_end.measure(q,c)

circ_end.draw(output='mpl')

# Oracle for a boolean function that always returns 1 
    
const = QuantumCircuit(q,out,c)
const.cx(q[0],out)
const.x(q[0])
const.cx(q[0],out)
const.x(q[0])

const.draw(output='mpl')

# Oracle for a boolean function that returns 1 for half of the inputs
    
bal = QuantumCircuit(q,out,c)
bal.cx(q[0],out)

bal.draw(output='mpl')

circ_const = circ_init + const + circ_end
circ_const.draw(output='mpl')

backend = Aer.get_backend('qasm_simulator')
job = execute(circ_const, backend, shots=10)
counts = job.result().get_counts()
print(counts)

circ_bal = circ_init + bal + circ_end
circ_bal.draw(output='mpl')

job = execute(circ_bal, backend, shots=10)
counts = job.result().get_counts()
print(counts)

from qiskit.providers.ibmq import least_busy

provider = IBMQ.load_account()
# We choose the least busy device
backend = least_busy(provider.backends(operational = True, simulator=False, status_msg='active',
                                       filters=lambda x: x.configuration().n_qubits >= n+1)) 
print("We are using...",backend)
print("It has",backend.status().pending_jobs,"pending jobs")
# We send both circuits at a time
circuits = [circ_const,circ_bal]
job_exp = execute(circuits, backend=backend)
job_monitor(job_exp)

result_exp = job_exp.result()
counts_const = result_exp.get_counts(circ_const)
print("Results for the circuit with the constant function")
print(counts_const)
print()
counts_equi = result_exp.get_counts(circ_bal)
print("Results for the circuit with the balanced function")
print(counts_equi)