%matplotlib inline
# Importing standard Qiskit libraries and configuring account
from qiskit import *
from qiskit.visualization import *
import numpy as np

# Create a quantum register with 3 qubits
q3 = QuantumRegister(3,'q')
# Create a classical register with 3 qubits
c3 = ClassicalRegister(3,'c')

# Create the quantum circuit with the measurement in one go.
qc_ex2 = QuantumCircuit(q3,c3,name="ex1")

qc_ex2.ry(2*np.pi/3,0)
qc_ex2.cx(0,1)
qc_ex2.cx(1,2)
qc_ex2.barrier()
qc_ex2.measure(q3,c3)
qc_ex2.draw(output='mpl')# Now, add a Toffoli gate
qc_toff = QuantumCircuit(q3,c3,name="ex1")

backend_q = Aer.get_backend('qasm_simulator')

# Execute the circuit on the qasm simulator.
job_ex2 = execute(qc_ex2, backend_q, shots=4096)

# Grab the results from the job.
result_ex2 = job_ex2.result()
plot_histogram(result_ex2.get_counts(qc_ex2))

# Create a quantum register with 3 qubits
q3 = QuantumRegister(3,'q')
# Create a classical register with 3 qubits
c3 = ClassicalRegister(3,'c')

# Create the quantum circuit without a Toffoli gate
qc_toff = QuantumCircuit(q3,c3,name="ex1")

qc_toff.ry(2*np.pi/3,0)
qc_toff.h(1)
qc_toff.h(2)
qc_toff.barrier()
qc_toff.measure(q3,c3)
qc_toff.draw(output='mpl')

# Execute the circuit on the qasm simulator.
job_toff = execute(qc_toff, backend_q, shots=4096)

# Grab the results from the job.
result_toff = job_toff.result()
plot_histogram(result_toff.get_counts(qc_toff))

# Now, add a Toffoli gate
qc_toff = QuantumCircuit(q3,c3,name="ex1")

qc_toff.ry(2*np.pi/3,0)
qc_toff.h(1)
qc_toff.h(2)
qc_toff.ccx(1,2,0)
qc_toff.barrier()
qc_toff.measure(q3,c3)
qc_toff.draw(output='mpl')

# Execute the circuit on the qasm simulator.
job_toff = execute(qc_toff, backend_q, shots=4096)

# Grab the results from the job.
result_toff = job_toff.result()
plot_histogram(result_toff.get_counts(qc_toff))

# IBMQ.disable_account()
provider = IBMQ.enable_account('TOKEN')

provider = IBMQ.get_provider(hub='ibm-q-research')

provider.backends()

from qiskit.tools.monitor import backend_overview, backend_monitor, job_monitor
from qiskit.tools.visualization import plot_gate_map, plot_error_map

# Retrieve IBM Quantum device information
backend_overview()

# Let's get two quantum devices as an example
backend_qx2 = provider.get_backend('ibmqx2')
backend_vigo = provider.get_backend('ibmq_vigo')

backend_monitor(backend_qx2)

plot_error_map(backend_qx2)

backend_monitor(backend_vigo)

plot_error_map(backend_vigo)

# Create a 5-qubit GHZ state (i.e. (|00000> + |11111>)/sqrt(2))
q5 = QuantumRegister(5,'q')
c5 = ClassicalRegister(5,'c')
ghz5= QuantumCircuit(q5,c5)

ghz5.h(0)
for i in range(1,5):
    ghz5.cx(0,i)

ghz5.barrier()
ghz5.measure(q5,c5)
ghz5.draw(output='mpl')

# Run the 5-qubit GHZ experiment on a 5-qubit device (try vigo)
job_exp1 = execute(ghz5, backend=backend_vigo, shots=4096)
job_monitor(job_exp1)

# Grab experimental results
result_vigo = job_exp1.result()
counts_vigo = result_vigo.get_counts(ghz5)

# Let's also try the same experiment on the 15-qubit device.
job_exp2 = execute(ghz5, backend=provider.get_backend('ibmq_16_melbourne'), shots=4096)
job_monitor(job_exp2)

# Grab experimental results
result_mel = job_exp2.result()
counts_mel = result_mel.get_counts(ghz5)

# Now, compare to theory by running it on qasm_simulator
job_qasm = execute(ghz5,backend=backend_q)
result_qasm = job_qasm.result()
counts_qasm = result_qasm.get_counts(ghz5)

# Plot both experimental and ideal results
plot_histogram([counts_qasm,counts_vigo,counts_mel],
               color=['black','green','blue'],
               legend=['QASM','Vigo','Melbourne'],figsize = [20,8])

%matplotlib inline
# Importing standard Qiskit libraries and configuring account
from qiskit import *
from qiskit.visualization import *
import numpy as np

# Define a function that takes a QuantumCircuit (qc)
# a qubit index (index) and a message string (msg)
def encoding(qc, index, msg):
    if msg == 0:
        pass    # To send 00 we do nothing
    elif msg == 1:
        qc.x(index) # To send 10 we apply an X-gate
    elif msg == 2:
        qc.z(index) # To send 01 we apply a Z-gate
    elif msg == 3:
        qc.z(index) # To send 11, we apply a Z-gate
        qc.x(index) # followed by an X-gate
    else:
        print("Invalid Message. Sending '00'.")

def decoding(qc, a, b):
    qc.cx(a,b)
    qc.h(a)

def qc_sdc(msg):

    # Create the quantum circuit with 2 qubits
    qc = QuantumCircuit(2)

    # First, an entangled pair is created between Alice and Bob
    # Bob has the first qubit, Alice has the second qubit.
    qc.h(0)
    qc.cx(0,1)

    qc.barrier()

    # Next, Bob encodes his message onto qubit 0.
    encoding(qc, 0, msg)
    qc.barrier()
    # Bob then sends his qubit to Alice.

    # After recieving qubit 0, Alice applies the recovery protocol:
    decoding(qc, 0, 1)

    # Finally, Alice measures her qubits to read Bob's message
    qc.measure_all()
    
    return qc

backend_qasm = Aer.get_backend('qasm_simulator')

rand_msg = np.random.randint(4)
qc = qc_sdc(rand_msg)
job_sim = execute(qc, backend_qasm, shots=1024)
sim_result = job_sim.result()

measurement_result = sim_result.get_counts(qc)
print(measurement_result)
plot_histogram(measurement_result)

print("The random message was: %s" % rand_msg)
qc.draw(output='mpl')

from qiskit.tools.monitor import backend_overview, backend_monitor, job_monitor
from qiskit.tools.visualization import plot_gate_map, plot_error_map

# Retrieve IBM Quantum device information
backend_overview()

# Run the superdense coding experiment on a 5-qubit device (try london)
job_sdc = execute(qc, backend=provider.get_backend('ibmq_london'), shots=4096)
job_monitor(job_sdc)

experiment_result = job_sdc.result().get_counts(qc)
print("The random message was: %s" % rand_msg)
plot_histogram(experiment_result)

def create_bell_pair(qc, a, b):
    # Creates a bell pair in qc using qubits a & b
    qc.h(a) # Put qubit a into state |+>
    qc.cx(a,b) # CNOT with a as control and b as target

qr = QuantumRegister(3)    # Protocol uses 3 qubits
cr1 = ClassicalRegister(1) # and 2 classical bits
cr2 = ClassicalRegister(1) # in 2 different registers
teleportation = QuantumCircuit(qr, cr1, cr2)

## STEP 1
# Entangle qubits q1 and q2
create_bell_pair(teleportation, 1, 2)
teleportation.barrier()

# And view the circuit so far:
teleportation.draw(output='mpl')

## STEP 2
# Bob performs his gates
teleportation.cx(0,1)
teleportation.h(0)
teleportation.barrier()

# And view the circuit so far:
teleportation.draw(output='mpl')

## STEP 3
# Bob measures his part
teleportation.measure(0,0)
teleportation.measure(1,1)

# And view the circuit so far:
teleportation.draw(output='mpl')

# This function takes a QuantumCircuit (qc), qubit index
# and ClassicalRegisters (cr1 & cr2) to decide which gates to apply
def alice_recover(qc, index, cr1, cr2):
    # Here we use c_if to control our gates with a classical
    # bit instead of a qubit
    qc.z(index).c_if(cr1, 1) # Apply gates if the registers 
    qc.x(index).c_if(cr2, 1) # are in the state '1'

## STEP 4
# Alice perform recovery
alice_recover(teleportation, 2, cr1, cr2)

# And view the circuit so far:
teleportation.draw(output='mpl')

def random_init(qc,r1,r2,index):
    
    ## STEP 0
    # Bob prepares a quantum state to teleport
    # by applying a random rotation around y and z
    qc.ry(r1,index)
    qc.rz(r2,index)
    qc.barrier()

def quantum_teleportation(qc):
    
    ## STEP 1
    # Entangle qubits q1 and q2
    create_bell_pair(qc, 1, 2)
    qc.barrier()

    ## STEP 2
    # Bob performs his gates
    qc.cx(0,1)
    qc.h(0)
    qc.barrier()

    ## STEP 3
    # Bob measures his part
    qc.measure(0,0)
    qc.measure(1,1)

    ## STEP 4
    # Alice perform recovery
    alice_recover(qc, 2, cr1, cr2)

qr = QuantumRegister(3)    # Protocol uses 3 qubits
cr1 = ClassicalRegister(1) # and 2 classical bits
cr2 = ClassicalRegister(1) # in 2 different registers
qc_teleportation = QuantumCircuit(qr, cr1, cr2)
qc_ref = QuantumCircuit(qr)

r1 = np.random.random()*np.pi
r2 = np.random.random()*2*np.pi

random_init(qc_ref, r1, r2, 0)

random_init(qc_teleportation, r1, r2, 0)

quantum_teleportation(qc_teleportation)

qc_teleportation.draw(output='mpl')

backend_sv = BasicAer.get_backend('statevector_simulator')

in_vector = execute(qc_ref, backend_sv).result().get_statevector()
out_vector = execute(qc_teleportation, backend_sv).result().get_statevector()

plot_bloch_multivector(in_vector)

plot_bloch_multivector(out_vector)

