%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 2 qubits
q = QuantumRegister(2,'q')
# Form a quantum circuit
# Note that the circuit name is optional
qc = QuantumCircuit(q,name="first_qc")
# Display the quantum circuit
qc.draw()

# Add a Hadamard gate on qubit 0, putting this in superposition.
qc.h(0)
# Add a CX (CNOT) gate on control qubit 0 
# and target qubit 1 to create an entangled state.
qc.cx(0, 1)
qc.draw()

# Create a classical register with 2 bits
c = ClassicalRegister(2,'c')

meas = QuantumCircuit(q,c,name="first_m")
meas.barrier(q)
meas.measure(q, c)

meas.draw()

# Quantum circuits can be added with + operations
# Add two pre-defined circuits
qc_all=qc+meas
qc_all.draw()

# Draw the quantum circuit in a different (slightly better) format
qc_all.draw(output='mpl')

# Create the quantum circuit with the measurement in one go.
qc_all = QuantumCircuit(q,c,name="2q_all")
qc_all.h(0)
qc_all.cx(0,1)
qc_all.barrier()
qc_all.measure(0,0)
qc_all.measure(1,1)
qc_all.draw(output='mpl')

# Use Aer's qasm_simulator
backend_q = Aer.get_backend('qasm_simulator')

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

job_sim1.status()

# Grab the results from the job.
result_sim1 = job_sim1.result()
result_sim1

result_sim1.get_counts(qc_all)

plot_histogram(result_sim1.get_counts(qc_all))

# Use Aer's statevector_simulator
backend_sv = Aer.get_backend('statevector_simulator')

# Execute the circuit on the statevector simulator.
# It is important to note that the measurement has been excluded
job_sim2 = execute(qc, backend_sv)

# Grab the results from the job.
result_sim2 = job_sim2.result()
# Output the entire result
result_sim2

# See output state as a vector
outputstate = result_sim2.get_statevector(qc, decimals=5)
print(outputstate)

# Visualize density matrix
plot_state_city(outputstate)

# Create the quantum circuit with the measurement in one go.
qc_3 = QuantumCircuit(3,3,name="qc_bloch")
qc_3.x(1)
qc_3.h(2)
qc_3.barrier()
qc_3.draw(output='mpl')

# Execute the circuit on the statevector simulator.
# It is important to note that the measurement has been excluded
job_sim_bloch = execute(qc_3, backend_sv)

# Grab the results from the job.
result_sim_bloch = job_sim_bloch.result()

# See output state as a vector
output_bloch = result_sim_bloch.get_statevector(qc_3, decimals=5)

# Draw on the Bloch sphere
plot_bloch_multivector(output_bloch)

# Use Aer's unitary_simulator
backend_u = Aer.get_backend('unitary_simulator')

# Execute the circuit on the unitary simulator.
job_usim = execute(qc, backend_u)

# Grab the results from the job.
result_usim = job_usim.result()
result_usim

# Output the unitary matrix
unitary = result_usim.get_unitary(qc)
print('%s\n' % unitary)

# Create the quantum circuit with the measurement in one go.
qc_ex1 = QuantumCircuit(q,c,name="ex1")

# Put the first qubit in equal superposition
qc_ex1.h(0)

# Rest of the circuit
qc_ex1.x(1)
qc_ex1.cx(0,1)
qc_ex1.barrier()
qc_ex1.measure(0,0)
qc_ex1.measure(1,1)
qc_ex1.draw(output='mpl')

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

job_ex1.status()

# Grab the results from the job.
result_ex1 = job_ex1.result()
result_ex1.get_counts(qc_ex1)

plot_histogram(result_ex1.get_counts(qc_ex1))

# Or get the histogram in one go
plot_histogram(job_ex1.result().get_counts(qc_ex1))

# 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')

# 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('IBM_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)

#![sdc2](jupyter_img/superdensecoding2.png)
# 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 14-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
import numpy as np
import matplotlib.pyplot as plt
# Importing standard Qiskit libraries and configuring account
from qiskit import *
from qiskit.visualization import *
from qiskit.circuit import Parameter

# Simple example

# Define two parameters, t1 and t2
theta1 = Parameter('t1')
theta2 = Parameter('t2')

# Build a 1-qubit circuit
qc = QuantumCircuit(1, 1)

# First parameter, t1, is used for a single qubit rotation of a controlled qubit
qc.ry(theta1,0)
qc.rz(theta2,0)

qc.barrier()

qc.measure(0, 0)

qc.draw(output='mpl')

theta1_range = np.linspace(0, 2 * np.pi, 20)
theta2_range = np.linspace(0, np.pi, 2)

circuits = [qc.bind_parameters({theta1: theta_val1, theta2: theta_val2})
            for theta_val2 in theta2_range for theta_val1 in theta1_range ]

# Visualize several circuits to check that correct circuits are generated correctly.
display(circuits[0].draw(output='mpl'))
display(circuits[1].draw(output='mpl'))
display(circuits[20].draw(output='mpl'))

# Execute multiple circuits
job = execute(qc, backend=Aer.get_backend('qasm_simulator'), shots = 8192,
              parameter_binds=[{theta1: theta_val1, theta2: theta_val2} 
                               for theta_val2 in theta2_range for theta_val1 in theta1_range])

# Store all counts
counts = [job.result().get_counts(i) for i in range(len(job.result().results))]

# Plot to visualize the result
plt.figure(figsize=(12,6))
plt.plot(range(len(theta1_range)*len(theta2_range)),
         list(map(lambda counts: (counts.get('0',0)-counts.get('1',1))/8192,counts)))
plt.show()

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

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()

# Execute multiple circuits
job_exp = execute(qc, backend=provider.get_backend('ibmq_essex'), shots = 8192,
              parameter_binds=[{theta1: theta_val1, theta2: theta_val2} 
                               for theta_val2 in theta2_range for theta_val1 in theta1_range])

# Monitor job status
job_monitor(job_exp)

# Store all counts
counts_exp = [job_exp.result().get_counts(i) for i in range(len(job_exp.result().results))]

# Plot to visualize the result
plt.figure(figsize=(12,6))
plt.rcParams.update({'font.size': 16})

plt.plot(range(len(theta1_range)*len(theta2_range)),
         list(map(lambda counts: (counts.get('0',0)-counts.get('1',1))/8192,counts)),'r',label='Simulation')
plt.plot(range(len(theta1_range)*len(theta2_range)),
         list(map(lambda counts_exp: (counts_exp.get('0',0)-counts_exp.get('1',1))/8192,counts_exp)),
         'b',label='Experiment')
plt.legend(loc='best')
plt.show()

from qiskit.compiler import transpile
from qiskit.transpiler import PassManager, Layout

display(plot_error_map(provider.get_backend('ibmqx2')))
display(plot_error_map(provider.get_backend('ibmq_burlington')))

# Create a dummy circuit for default transpiler demonstration
qc = QuantumCircuit(4)
qc.h(0)
qc.swap(0,1)
qc.cx(1,0)
qc.s(3)
qc.x(3)
qc.h(3)
qc.h(0)
qc.cx(0,2)
qc.ccx(0,1,2)
qc.h(0)
print('Original circuit')
display(qc.draw(output='mpl'))

# Transpile the circuit to run on ibmqx2
qt_qx2 = transpile(qc,provider.get_backend('ibmqx2'))
print('Transpiled circuit for ibmqx2')
display(qt_qx2.draw(output='mpl'))

# Transpile the circuit to run on ibmq_burlington
qt_bu = transpile(qc,provider.get_backend('ibmq_burlington'))
print('Transpiled circuit for ibmqx_Burlington')
display(qt_bu.draw(output='mpl'))

# Print out some circuit properties
# Total nunmber of operations
print("Number of operations for ibmqx2 = %s" % qt_qx2.size())
print("Number of operations for ibmq_burlington = %s \n" % qt_bu.size())

# Circuit depth
print("Circuit depth for ibmqx2 = %s" % qt_qx2.depth())
print("Circuit depth for ibmq_burlington = %s \n" % qt_bu.depth())

# Number of qubits
print("Number of qubits for ibmqx2 = %s" % qt_qx2.width())
print("Number of qubits for ibmq_burlington = %s \n" % qt_bu.width())

# Breakdown of operations by type
print("Operations for ibmqx2: %s" % qt_qx2.count_ops())
print("Operations for ibmq_burlington: %s \n" % qt_bu.count_ops())


# Number of unentangled subcircuits in this circuit.
# In principle, each subcircuit can be executed on a different quantum device.
print("Number of unentangled subcircuits for ibmqx2 = %s" % qt_qx2.num_tensor_factors())
print("Number of unentangled subcircuits for ibmq_burlington = %s" % qt_bu.num_tensor_factors())

qr = QuantumRegister(4,'q')
cr = ClassicalRegister(4,'c')
qc_test = QuantumCircuit(qr,cr)

qc_test.h(0)
for i in range(3):
    qc_test.cx(i,i+1)

qc_test.barrier()
qc_test.measure(qr,cr)

qc_test.draw(output='mpl')

qc_t = transpile(qc_test, backend = provider.get_backend('ibmq_london'))
# Display transpiled circuit
display(qc_t.draw(output='mpl'))
# Display the qubit layout
display(plot_error_map(provider.get_backend('ibmq_london')))

# Print out some circuit properties
# Total nunmber of operations
print("Number of operations = %s" % qc_t.size())
# Circuit depth
print("Circuit depth = %s" % qc_t.depth())

# Execute the circuit on the qasm simulator.
job_test = execute(qc_test, provider.get_backend('ibmq_london'), shots=8192)
job_monitor(job_test)

# Customize the layout
layout = Layout({qr[0]: 4, qr[1]: 3, qr[2]: 1, qr[3]:0})
# Map it onto 5 qubit backend ibmqx2
qc_test_new = transpile(qc_test, backend = provider.get_backend('ibmq_london'), initial_layout=layout, basis_gates=['u1','u2','u3','cx'])
display(qc_test_new.draw(output='mpl'))

# Print out some circuit properties
# Total nunmber of operations
print("Number of operations = %s" % qc_test_new.size())
# Circuit depth
print("Circuit depth = %s" % qc_test_new.depth())

# Execute the circuit on the qasm simulator.
job_test_new = execute(qc_test_new, provider.get_backend('ibmq_london'), shots=8192)
job_monitor(job_test_new)

# Now, compare the two
result_test = job_test.result()
result_test_new = job_test_new.result()

# Plot both experimental and ideal results
plot_histogram([result_test.get_counts(qc_test),result_test_new.get_counts(qc_test_new)],
               color=['green','blue'],legend=['default','custom'],figsize = [20,8])

# Apply 4-qubit controlled x gate
qr = QuantumRegister(5,'q')
qc = QuantumCircuit(qr)

qc.h(0)
qc.h(1)
qc.h(3)

qc.ccx(0,1,2)
qc.ccx(2,3,4)
qc.ccx(0,1,2)
    
display(qc.draw(output='mpl'))

# Print out some circuit properties
# Total nunmber of operations
print("Number of operations = %s \n" % qc.size())
# Count different types of operations
print("Operation counts = %s \n" % qc.count_ops())
# Circuit depth
print("Circuit depth = %s" % qc.depth())

qc_t = transpile(qc, provider.get_backend('ibmq_valencia'))
display(qc_t.draw(output='mpl'))

# Print out some circuit properties
# Total nunmber of operations
print("Number of operations = %s" % qc_t.size())
# Circuit depth
print("Circuit depth = %s" % qc_t.depth())

# Transpile many times (20 times in this example) and pick the best one
trial = 20
# Use ibmq_valencia for example
backend_exp = provider.get_backend('ibmq_valencia')

tcircs0 = transpile([qc]*trial, backend_exp, optimization_level=0)
tcircs1 = transpile([qc]*trial, backend_exp, optimization_level=1)
tcircs2 = transpile([qc]*trial, backend_exp, optimization_level=2)
tcircs3 = transpile([qc]*trial, backend_exp, optimization_level=3)

import matplotlib.pyplot as plt

num_cx0 = [c.count_ops()['cx'] for c in tcircs0]
num_cx1 = [c.count_ops()['cx'] for c in tcircs1]
num_cx2 = [c.count_ops()['cx'] for c in tcircs2]
num_cx3 = [c.count_ops()['cx'] for c in tcircs3]

num_tot0 = [c.size() for c in tcircs0]
num_tot1 = [c.size() for c in tcircs1]
num_tot2 = [c.size() for c in tcircs2]
num_tot3 = [c.size() for c in tcircs3]

num_depth0 = [c.depth() for c in tcircs0]
num_depth1 = [c.depth() for c in tcircs1]
num_depth2 = [c.depth() for c in tcircs2]
num_depth3 = [c.depth() for c in tcircs3]

plt.rcParams.update({'font.size': 16})

# Plot the number of CNOT gates
plt.figure(figsize=(12,6))
plt.plot(range(len(num_cx0)),num_cx0,'r',label='level 0')
plt.plot(range(len(num_cx1)),num_cx1,'b',label='level 1')
plt.plot(range(len(num_cx2)),num_cx2,'g',label='level 2')
plt.plot(range(len(num_cx3)),num_cx3,'k',label='level 3')
plt.legend(loc='upper left')
plt.xlabel('Random trial')
plt.ylabel('# of cx gates')
plt.show()

# Plot total number of gates
plt.figure(figsize=(12,6))
plt.plot(range(len(num_tot0)),num_tot0,'r',label='level 0')
plt.plot(range(len(num_tot1)),num_tot1,'b',label='level 1')
plt.plot(range(len(num_tot2)),num_tot2,'g',label='level 2')
plt.plot(range(len(num_tot3)),num_tot3,'k',label='level 3')
plt.legend(loc='upper left')
plt.xlabel('Random trial')
plt.ylabel('# of total gates')
plt.show()

# Plot the number of CNOT gates
plt.figure(figsize=(12,6))
plt.plot(range(len(num_depth0)),num_depth0,'r',label='level 0')
plt.plot(range(len(num_depth1)),num_depth1,'b',label='level 1')
plt.plot(range(len(num_depth2)),num_depth2,'g',label='level 2')
plt.plot(range(len(num_depth3)),num_depth3,'k',label='level 3')
plt.legend(loc='upper left')
plt.xlabel('Random trial')
plt.ylabel('Circuit depth')
plt.show()

print('Opt0: Minimum # of cx gates = %s' % min(num_cx0))
print('Opt0: The best circuit is the circut %s \n' % num_cx0.index(min(num_cx0)))

print('Opt1: Minimum # of cx gates = %s' % min(num_cx1))
print('Opt1: The best circuit is the circut %s \n' % num_cx1.index(min(num_cx1)))

print('Opt2: Minimum # of cx gates = %s' % min(num_cx2))
print('Opt2: The best circuit is the circut %s \n' % num_cx2.index(min(num_cx2)))

print('Opt3: Minimum # of cx gates = %s' % min(num_cx3))
print('Opt3: The best circuit is the circut %s' % num_cx3.index(min(num_cx3)))