import numpy as np
from qiskit import *
from qiskit.tools.jupyter import *
from qiskit.visualization import *
from ibm_quantum_widgets import *
from qiskit.quantum_info import Statevector


# Load your IBM Quantum account(s)
provider = IBMQ.load_account()

# here we create the four bell states 
# This statevector object do not need simulation to get the information
bell1 = QuantumCircuit(2)

bell1.h(0)
bell1.cx(0,1)

sv = Statevector.from_label('00') # here .from_label() is used, we could also use .from_int(), for more check docs!
sv_ev1 = sv.evolve(bell1) # then evolve this initial state through the circuit, What is evolve ?
sv_ev1.draw('latex')

bell2 = QuantumCircuit(2)

bell2.h(0)
bell2.cx(0,1)
bell2.z(0)

sv_ev2 = sv.evolve(bell2)
sv_ev2.draw('latex')

bell3 = QuantumCircuit(2)

bell3.h(0)
bell3.x(1)
bell3.cx(0,1)

sv_ev3 = sv.evolve(bell3)
sv_ev3.draw('latex')

bell4 = QuantumCircuit(2)

bell4.h(0)
bell4.x(1)
bell4.cx(0,1)
bell4.z(1)

sv_ev4 = sv.evolve(bell4)
sv_ev4.draw('latex')

# the Statevector object can be drawn on a qsphere 
# with the .draw() method by changing the call from 'latex' to 'qsphere'

sv_ev1.draw('qsphere')

sv_ev2.draw('qsphere')

sv_ev3.draw('qsphere')


sv_ev4.draw('qsphere')

# What is GHZ State ? (Greenberger–Horne–Zeilinger state)

ghz = QuantumCircuit(3)
ghz.h(0)
ghz.cx([0,0],[1,2]) # using []s notation we can apply 2 cx gates in same statement 
ghz.draw() # or use 'mpl' or 'text', leaving blank default uses mpl

sv = Statevector.from_int(0,2**3) # what is from _int ?
sv_ev = sv.evolve(ghz)
sv_ev.draw('latex')

sv = Statevector.from_label('000') #doing the same with .from_label()
sv_ev = sv.evolve(ghz)
sv_ev.draw('latex')

sv_ev.draw('qsphere')

# we can also plot hinton directly!, What is Hinton ? is similar to city graph but flat & represent matrix density distribution
sv_ev.draw('hinton')

# BasicAer has 3 main simulators:
# 1-'qasm_simulator'
# 2-'statevector_simulator'
# 3-'unitary_simulator'

BasicAer.backends()

backend_sv = BasicAer.get_backend('statevector_simulator') # get the statevector_simulator
job = execute(ghz, backend_sv, shots=1024)                 # specify job, which circuit to execute and how many times/shots
result = job.result()                                      # results from the job
sv_ev2 = result.get_statevector(ghz)                       # get statevector from the result

# this way of getting the statevector does not have the method '.draw()' in order to visualize you need plotting functions directly

plot_state_city(sv_ev2, title='my_city',color=['red','blue'])

plot_state_hinton(sv_ev2, title='my_hinton')

plot_state_qsphere(sv_ev2)

plot_state_paulivec(sv_ev2)

plot_bloch_multivector(sv_ev2)

# The multivector as we saw plots a state coming from a circuit
# whereas the bloch_vector needs coordinate specifications the coordinates can be cartesian, or spherical

plot_bloch_vector([1,1,1]) # here for [x,y,z]; x=Tr[Xρ] and similar for y and z

q_a = QuantumRegister(1,'q_a')
q_b = QuantumRegister(1, 'q_b')
qc = QuantumCircuit(q_a, q_b)
qc.h(0) 
qc.z(0) 
qc.draw()

qc.measure_all() # measure_all() will create a barrier & classical register for a circuit that doesn't have one
#NOTE: this also means, if your circuit already had a classical register, it would create another one!
#so measure_all() is usually used for circuits that do not have a classical register

qasm_sim = BasicAer.get_backend('qasm_simulator')
result = execute(qc, qasm_sim).result() 
counts = result.get_counts()
plot_histogram(counts)

meas = QuantumCircuit(3,3)
meas.barrier()
meas.measure([0,1,2],[0,1,2]) # here we measure respective quantum registers into respective classical registers, the ordering is important!
meas.draw('mpl')

circ = meas.compose(ghz,range(3),front=True) # compose to extend the circuit the front=True gets GHZ before the measure
circ.draw()

backend = BasicAer.get_backend('qasm_simulator')
circ = transpile(circ, backend)
job = backend.run(circ, shots=1024) #notice here we use .run instead of execute most usually we employ 'execute' though because it is more flexible
result = job.result()
counts = result.get_counts()
print(counts) #now, we could also just print our counts in an un-spectacular way compared to plotting the histograms!

import qiskit.tools.jupyter
#%qiskit_job_watcher #this creates a pop up of your jobs

from qiskit.tools import job_monitor
#quito = provider.get_backend('ibmq_quito') # to call a real backend QPU, we use 'provider' object instead of BasicAer you can check which backends are available for you too!
#job = execute(circ, quito)
#job_monitor(job)

#result = job.result()
#counts = result.get_counts()
#plot_histogram(counts)

from qiskit.quantum_info import Operator
U = Operator(ghz) # we can turn our ghz circuit from above into an operator!
U.data
# this is very similar to getting the Statevector object directly, 


# if needed we can round all these numbers by using np.around and specify how many decimals we want
np.around(U.data, 3)

backend_uni = BasicAer.get_backend('unitary_simulator') 
U = execute(ghz,backend_uni).result().get_unitary(decimals=3)
U

from qiskit_textbook.tools import array_to_latex
array_to_latex(U, pretext="\\text{Statevector} = ")

q = QuantumRegister(2,'q_reg')
c = ClassicalRegister(2,'c_reg')
qc = QuantumCircuit(q,c)
qc.h(q[0:2])
qc.cx(q[0], q[1])
qc.draw('mpl')

U_bell = Operator(qc)
np.around(U_bell.data,3) # rounding decimal places

a = 1/np.sqrt(3) # we can define a state ourselves, we could also get a random state,
desired_state = [a,np.sqrt(1-a**2)] # state is defined such that it is a valid one!
q_reg = QuantumRegister(2,'q')
qc = QuantumCircuit(q_reg)
qc.initialize(desired_state,1)
qc.draw('mpl')

decomp = qc.decompose()
decomp.draw() #but what is this |0> ? It is a reset! Meaning that initialize as a whole is NOT a Gate!
#It is irreversible because it has a reset! check docs for more

c_reg = ClassicalRegister(1,'c')
meas = QuantumCircuit(q_reg, c_reg)
meas.measure(0,0)
circ = meas.compose(qc, range(2), front=True)
circ.draw('mpl')

#squaring the amplitudes
alpha_squared = 0.577 **  2
beta_squared = 0.816 ** 2
print(alpha_squared, beta_squared, alpha_squared+beta_squared)

back = BasicAer.get_backend('qasm_simulator')
job = execute(circ, back, shots=1000)
counts = job.result().get_counts()
print(counts)

plot_histogram(counts,title='test_title')

# state_fidelity

back_sv = BasicAer.get_backend('statevector_simulator')
result = execute(qc, back_sv).result()
qc_sv = result.get_statevector(qc)
qc_sv

from qiskit.quantum_info import state_fidelity
state_fidelity(desired_state, qc_sv) 
# this compares the statevector we got from simulation vs the state we wanted to initialize
# here we see they match perfectly, as expected because it is a simulator on actual  hardware they may not have matched so perfectly

# average_gate_fidelity

from qiskit.circuit.library import XGate
from qiskit.quantum_info import Operator, average_gate_fidelity, process_fidelity
op_a = Operator(XGate())
op_b = np.exp(1j / 2) * op_a

# these differ only by a phase so the gate and process fidelities are expected to be 1

a = average_gate_fidelity(op_a,op_b)
a

# process_fidelity

b = process_fidelity(op_a, op_b)
a == b

back_uni = BasicAer.get_backend('unitary_simulator')
job = execute(qc, back_uni)
result = job.result()
U_qc = result.get_unitary(decimals=3)
U_qc

qc = QuantumCircuit(3)
qc.mct([0,1],2) # What does it do ?
qc.cx(0,2)
qc.h(1)
qc.z(0)
qc.draw('mpl')

qc_gate = qc.to_gate()
qc_gate.name = 'my_gate_ϕ' # optionally we can give it a name!
circ = QuantumCircuit(3)
circ.append(qc_gate, [0,1,2])
circ.draw('mpl')

circ_decomp = circ.decompose() # decompose it back!
circ_decomp.draw('mpl')

from qiskit.circuit.library import HGate
ch = HGate().control(2) # specify how many controls we want
qc = QuantumCircuit(3)
qc.append(ch, [0,1,2]) # the [a,b,c] correspond to controls and target respectively
qc.draw('mpl')

# another little more complex example 
circ = QuantumCircuit(4)
circ.h(range(2))
circ.cx(0,1)
circ.cx(0,3)
circ.crz(np.pi/2,0,2)

my_gate = circ.to_gate().control(2) #this 
qc = QuantumCircuit(6)
qc.append(my_gate, [0,5,1,2,3,4])
qc.draw()

circ = qc.decompose()
circ.draw() 

# Again simpler circuit!
qc = QuantumCircuit(3)
qc.mct([0,1],2)
qc.cx(0,2)
qc.h(1)
qc.z(0)

trans = transpile(qc, basis_gates = ['u3','cx','s'])
trans.draw('mpl')

q_a = QuantumRegister(2, 'q_a')
q_b = QuantumRegister(4, 'q_b')
c_a = ClassicalRegister(2,'c_a')
c_b = ClassicalRegister(4,'c_b')

qc = QuantumCircuit(q_a, q_b,c_a, c_b)

qc.x(0)
qc.h(1)
qc.cx(0,1)
qc.barrier()
qc.cz(0,1)
qc.cx(0,4)
qc.h(3)
qc.h(4)
qc.barrier()
qc.x(3)
qc.ccx(0,3,5)
qc.barrier()
qc.measure(q_a, c_a)
qc.measure(q_b, c_b)
qc.draw()

#Now look at all the crazy things we can do with the .draw() method! Who would've imagined!
qc.draw(reverse_bits=True, plot_barriers=False,scale=0.5, style = {'backgroundcolor': 'gray'})

from qiskit.tools.visualization import circuit_drawer
circuit_drawer(qc, output='text') # circuit_drawer is also there

qc = QuantumCircuit(3,3)
qc.h(0)
qc.cx(0,1)
qc.cx(0,2)
qc.barrier()
qc.measure([0,1,2],[0,1,2])
qc.draw()

qasm_str = qc.qasm() #returning a qasm string, THIS SIMPLE
qasm_str

circ = QuantumCircuit.from_qasm_str(qasm_str) #you got to be kidding!
circ.draw() #you can also read a file directly with .from_qasm_file('path'), check out docs!

#what do you think the circuit depth of this picture is?
#hint: not 2

circ = QuantumCircuit(3)
circ.h(0)
circ.x(1)
circ.h(2)
circ.draw()

circ.depth()

qc =QuantumCircuit(3,3)
qc.h(0)
qc.cx(0,1)
qc.cx(0,2)
qc.h(2)
qc.barrier()
qc.cx(2,0)
qc.barrier() 

print(qc.depth()) # Barriers do not count for depth

qiskit.__version__ 

%qiskit_version_table

# # this will give all the information on all the hardware available
# import qiskit.tools.jupyter
# %qiskit_backend_overview

from qiskit.visualization import plot_gate_map
backend = provider.get_backend('ibmq_manila')
plot_gate_map(backend, plot_directed=True)

plot_error_map(backend)

qc = QuantumCircuit(3)
qc.measure_all()
sim = BasicAer.get_backend('qasm_simulator')
couple_map = [[0,1],[1,2]] # specify some linear connection
job = execute(qc, sim, shots=1000, coupling_map=couple_map)
result = job.result()
counts = result.get_counts()
print(counts)

#Answer is D
qc.draw('mpl',filename='test.png') ## check the folder where the notebook is located

qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
state = Statevector.from_instruction(qc)
state.draw('latex')

plot_state_paulivec(state, color=['midnightblue','green','orange'],title="New PauliVec plot")

qc = QuantumCircuit(3)
qc.x(0)
qc.y(1)
qc.cx(0,1)
qc.ccx(0,1,2)
backend = Aer.get_backend('statevector_simulator')
result = execute(qc, backend).result()
output = result.get_statevector()
output.draw('latex')

plot_state_paulivec(output, title = 'MY CITY', color = ['red','green'])

# 21)  Which one of the following output results are correct when the given code is executed?

q = QuantumRegister(2)
c = ClassicalRegister(2)
qc = QuantumCircuit(q,c)
qc.h(q[0])
qc.x(q[1])
qc.h(q[1])

sim = Aer.get_backend('unitary_simulator')
job = execute(qc, sim)
unitary = job.result().get_unitary()
unitary

from qiskit_textbook.tools import array_to_latex
array_to_latex(unitary, pretext="\\text{Statevector} = ")

# 22)  Which one of the following output results are correct when the given code is executed?
qc = QuantumCircuit(2,2)
qc.h(0)
qc.z(1)
backend = Aer.get_backend('statevector_simulator')
job = execute(qc, backend)
statevector = job.result().get_statevector()
print(statevector)

array_to_latex(statevector, pretext="\\text{Statevector} = ")

# 37) When executed, which one of the following codes produces the given image?
qc = QuantumCircuit(2,2)
qc.x(0)
qc.h(1)
qc.crz(np.pi,0,1)
qc.measure([0,1],[0,1])

#qc.draw('mpl')
#qc.draw()
#qc.draw('latex')
qc.draw('text')


# 42) Which one of the following codes will create a random circuit?
from qiskit.circuit.random import random_circuit
#circ = random_circuit(2, 2, reset=reset, measure=True)
#circ = random_circuit(2, 2,conditional=True, measurement=measure)
circ = random_circuit(2, 2, measure=False)
#circ = random_circuit(2, 2, max_operands=True)
circ.draw()

# 17) Which operator is the O/P of the following circuit?
qc = QuantumCircuit(2)
qc.x(0) 
qc.cx(0,1)
op = Operator(qc)
array_to_latex(op, pretext="\\text{Operator} = ")

# Q Given this code, which two inserted code fragments result in the state vector represented by this Bloch sphere?
qc = QuantumCircuit(1,1)

qc.h(0)

#qc.rx(np.pi/4,0)

#qc.ry(-np.pi/4,0)

qc.rz(-np.pi/4,0)

#qc.ry(np.pi,0)

simulator = Aer.get_backend('statevector_simulator')
job = execute(qc, simulator)
result = job.result()
outputstate = result.get_statevector(qc)
plot_bloch_multivector(outputstate)

qc = QuantumCircuit(2)
qc.mct([0],1)
qc.draw()

q = QuantumRegister(1)
qc = QuantumCircuit(q)
qc.x(q[0])
qc.h(q[0])
style = {'backgroundcolor': 'lightgreen'}
qc.draw(output='mpl', style=style, scale=3.5, plot_barriers=False, reverse_bits=False)


qc = QuantumCircuit(1)
qc.h(0)
qc.x(0)
qc.ry(np.pi/2,0)
qc.rx(-np.pi/2,0)
qc.x(0)

simulator = Aer.get_backend('statevector_simulator')
job = execute(qc, simulator)
result = job.result()
outputstate = result.get_statevector(qc)
plot_bloch_multivector(outputstate)

qc = QuantumCircuit(3)
qc.x(0)
qc.y(1)
qc.cx(0,1)
qc.ccx(0,1,2)
backend = Aer.get_backend('statevector_simulator')
result = execute(qc, backend).result()
output = result.get_statevector()
plot_state_paulivec(output, title = 'MY CITY', color = ['red','green'])

array_to_latex(output, pretext="\\text{Operator} = ")

output

qc = QuantumCircuit(3,3)
qc.h(0)
qc.cx(0,1)
qc.cry(np.pi,0,1)
qc.barrier()
qc.cx(0,2)
qc.rz(np.pi/3,1)
qc.ccx(0,2,1)
qc.measure_all()
qc.draw('mpl', scale=1, reverse_bits=True)

qc = QuantumCircuit(2,2)
qc.x(0)
qc.cx(1,0)
qc.h(1)
qc.measure(range(2),range(2))
backend = Aer.get_backend('qasm_simulator')
result = execute(qc, backend).result()
counts1 = result.get_counts()
counts2 = result.get_counts()
legend = counts1, counts2
plot_histogram([counts1,counts2], legend=legend, title = 'HISTOGRAM')

qc = QuantumCircuit(2,2)
qc.h(0)
qc.cx(0,1) 
qc.cx(1,0) 

backend = BasicAer.get_backend('statevector_simulator')
job = execute(qc, backend, shots=1024)
result = job.result()
sv = result.get_statevector(qc)
plot_state_qsphere(sv)

qc = QuantumCircuit(3,3)
qc.h(0) 
qc.x(1) 
qc.ccx(0,2,1) 
qc.measure(range(3),range(3))
backend = Aer.get_backend('qasm_simulator')
result = execute(qc, backend).result()
counts = result.get_counts()
plot_histogram(counts)

q = QuantumRegister(2,'hello')
c = QuantumRegister(2,'hey')
qc = QuantumCircuit(q,c)
qc.x([0,1])
qc.h(3)
qc.cz(0,1)
qc.draw()


qc= QuantumCircuit(1,1) 

pi = np.pi

qc.z(0) #4
qc.t(0) #5
qc.sdg(0) #2
qc.h(0) #3
qc.x(0) #1

simulator = Aer.get_backend('statevector_simulator') 
job = execute(qc, simulator) 
result = job.result() 
outputstate = result.get_statevector(qc) 
plot_bloch_multivector(outputstate)

array_to_latex(statevector, pretext="\\text{Operator} = ")

cords=[1,1,1]
#cords=[0,1,0]
#cords=[1,0,0]
#cords=[0,0,1]
plot_bloch_vector(cords, title='Image')


q = QuantumRegister(2)
c = ClassicalRegister(2)
qc = QuantumCircuit(q, c)
qc.h(q[0])
qc.x(q[1])
qc.h(q[1])
sim = Aer.get_backend('unitary_simulator')
job = execute(qc, sim)
unitary = job.result().get_unitary()
array_to_latex(unitary, pretext="\\text{Operator} = ")

qc = QuantumCircuit(3,3)
qc.h(0)
qc.cx(0,1)
qc.cry(pi,0,1)
qc.barrier()
qc.cx(0,2)
qc.rz(pi/3,1)
qc.ccx(0,2,1)
qc.measure_all()
qc.draw('mpl', scale=1, reverse_bits=True)











