# build your code here
answer1 = [3]

# run this cell to submit your answer

# import the grader for the exercise 1 of the lab/badge 1
from qc_grader.challenges.quantum_explorers23 import grade_badge1_ex1
 
# expected result type: List
grade_badge1_ex1(answer1) 

# build your code here
answer2 = [1]

# eun this cell to submit your answer

# import the grader for the exercise 2 of the lab/badge 1
from qc_grader.challenges.quantum_explorers23 import grade_badge1_ex2
 
# expected result type: List
grade_badge1_ex2(answer2)

# build your code here
answer3 = [4]

# run this cell to submit your answer

# import the grader for the exercise 3 of the lab/badge 1
from qc_grader.challenges.quantum_explorers23 import grade_badge1_ex3
 
# expected result type: List
grade_badge1_ex3(answer3)

# build your code here
answer4 = [2]

# run this cell to submit your answer

# import the grader for the exercise 4 of the lab/badge 1
from qc_grader.challenges.quantum_explorers23 import grade_badge1_ex4
 
# expected result type: List
grade_badge1_ex4(answer4)

# build your code here
answer5 = [2]

# run this cell to submit your answer

# import the grader for the exercise 5 of the lab/badge 1
from qc_grader.challenges.quantum_explorers23 import grade_badge1_ex5

# expected result type: List
grade_badge1_ex5(answer5)

# build your code here
answer6 = [4]

# run this cell to submit your answer

# import the grader for the exercise 6 of the lab/badge 1
from qc_grader.challenges.quantum_explorers23 import grade_badge1_ex6
 
# expected result type: List
grade_badge1_ex6(answer6)

# build your code here
answer7 = [2]

# run this cell to submit your answer

# import the grader for the exercise 7 of the lab/badge 1
from qc_grader.challenges.quantum_explorers23 import grade_badge1_ex7

# expected result type: List
grade_badge1_ex7(answer7)

# build your code here
answer8 = [4]

# run this cell to submit your answer

# import the grader for the exercise 8 of the lab/badge 1
from qc_grader.challenges.quantum_explorers23 import grade_badge1_ex8
 
# expected result type: List
grade_badge1_ex8(answer8)

# build your code here
answer9 = [1]

# run this cell to submit your answer

# import the grader for the exercise 9 of the lab/badge 1
from qc_grader.challenges.quantum_explorers23 import grade_badge1_ex9

# expected result type: List
grade_badge1_ex9(answer9) 

# build your code here
answer10 = [2]

# run this cell to submit your answer

# import the grader for the exercise 10 of the lab/badge 1
from qc_grader.challenges.quantum_explorers23 import grade_badge1_ex10
 
# expected result type: List
grade_badge1_ex10(answer10)

# build your code here
answer11 = [1]

# run this cell to submit your answer

# import the grader for the exercise 11 of the lab/badge 1
from qc_grader.challenges.quantum_explorers23 import grade_badge1_ex11
 
# expected result type: List
grade_badge1_ex11(answer11)

# build your code here
answer12 = [2]

# run this cell to submit your answer

# import the grader for the exercise 12 of the lab/badge 1
from qc_grader.challenges.quantum_explorers23 import grade_badge1_ex12
 
# expected result type: List
grade_badge1_ex12(answer12)

# import the quantum circuit
# from the IBM's Qiskit library
from qiskit import QuantumCircuit

# import the plotting state of qsphere function
# from the IBM's Qiskit Visualization module
from qiskit.visualization import plot_state_qsphere


####### build your code below #########

# create a quantum circuit with
# a quantum register with 4 qubits
circuit = QuantumCircuit(4)

# apply the Hadamard gate to all the 4 qubits
# to see all the possible combinations of quantum states
# note: uncomment to see all the possible
#       combinations of quantum states
# circuit.h( [0, 1, 2, 3] )

# the (entangled) quantum state we need is
# |psi> = 1/sqrt(2) * ( |0010> + |1101> )

# apply the Hadamard gate to the 2nd qubit on
# the quantum register of the quantum circuit
circuit.h(1)


# apply the CX/CNOT gate on the 2nd qubit
# as control and on the 1st qubit as target,
# both on the quantum register of the quantum circuit
circuit.cx(1, 0)

# apply the CX/CNOT gate on the 2nd qubit
# as control and on the 3rd qubit as target,
# both on the quantum register of the quantum circuit
circuit.cx(1, 2)

# apply the CX/CNOT gate on the 2nd qubit
# as control and on the 4th qubit as target,
# both on the quantum register of the quantum circuit
circuit.cx(1, 3)


# apply the Pauli-X gate to the 2nd qubit on
# the quantum register of the quantum circuit
circuit.x(1)

####### build your code above #########


# display the plot of the quantum state from the QSphere
# applied to the quantum circuit defined before
display( plot_state_qsphere( circuit ) )

# draw the quantum circuit defined before
display( circuit.draw("mpl") )

# run this cell to submit your answer

# import the grader for the coding exercise of the lab/badge 1
from qc_grader.challenges.quantum_explorers23 import grade_badge1_code
 
# expected result type: QuantumCircuit
answer_code = grade_badge1_code(circuit)

#### get statevector from a quantum circuit ####

# import the statevector from the IBM's Qiskit
# Quantum Information module
from qiskit.quantum_info import Statevector

# retrieve the statevector from the instructions of
# the quantum circuit defined before
statevector = Statevector.from_instruction( circuit )

#### run this cell to view the output of ####
#### your quantum circuit as a histogram ####

# import the Sampler from the IBM's Qiskit Aer Primitives module,
# with the 'AerSampler' alias being defined
from qiskit_aer.primitives import Sampler as AerSampler

# import the plot distribution function
# from the IBM's Qiskit Tools for Visualization module
from qiskit.tools.visualization import plot_distribution


#### run the circuit on the simulator ####
#### and get probability distribution ####


# create an Aer Sampler object
sampler = AerSampler()

# measure all the qubits from
# the quantum circuit defined before
circuit.measure_all()

# execute the quantum circuit defined before,
# on the Aer Sampler, retrieving the respective job
job = sampler.run(circuit)


# retrieve the probability distribution
# from the quasi-distribution of
# the results obtained from the execution of
# the quantum circuit created before
prob_distribution = job.result().quasi_dists[0].binary_probabilities()


# print the statevector from the instructions of
# the quantum circuit defined before
print( statevector )

# print the probability distribution
# from the quasi-distribution of
# the results obtained from the execution of
# the quantum circuit created before
print( "Simulator Probability Distribution:", prob_distribution )


# plot the probability distribution
# from the quasi-distribution of
# the results obtained from the execution of
# the quantum circuit created before
plot_distribution( prob_distribution, color=[ "lightblue" ] )

# import the Qiskit's Runtime Service, Session, and Sampler
# from the Qiskit's IBM Runtime module
from qiskit_ibm_runtime import QiskitRuntimeService, Session, Sampler


# create a Qiskit's Runtime Service object,
# using the IBM Quantum channel
service = QiskitRuntimeService( channel="ibm_quantum" )

# retrieve the least busy backend
# (this step may take a while)
real_backend = service.least_busy( min_num_qubits=4, simulator=False )


# within the context of a session
with Session( service, backend=real_backend ) as session:
    
    # create the Sampler object for the Session defined before
    sampler = Sampler( session=session )
    
    # execute the quantum simulation of
    # the quantum circuit created before,
    # using the Sampler, and retrieving
    # the respective job on
    # a real quantum device
    job_real = sampler.run( circuit )

# retrieve and show the status of
# the job created for the execution of
# the quantum circuit defined before,
# on the Aer Sampler, using
# a real quantum device
job_real.status()

# retrieve the probability distribution
# from the quasi-distribution of
# the results obtained from the execution of
# the quantum circuit created before,
# using a real quantum device
prob_distribution_real = job_real.result().quasi_dists[0].binary_probabilities()


# fix the probability distributions
# from the quasi-distributions of
# the results obtained from the execution of
# the quantum circuit created before,
# using the simulator, for values
# lower or equal to 0 (zero)
prob_distribution = { binary_string : prob if prob > 0 else 1e-8
                      for binary_string, prob in prob_distribution.items() }


# create a list with all the possible binary strings
# with 4 bits (i.e., a total of 16 possible different values)
binary_strings_4_bits = [ bin( num )[2:].zfill(4) for num in range( 2**4 ) ]


# for each possible binary string with 4 bits
for binary_string_4_bits in binary_strings_4_bits:
    
    # if the current binary string with 4 bits is
    # not in the diectionary of the probability
    # distributions from the quasi-distributions of
    # the results obtained from the execution of
    # the quantum circuit created before,
    # using the simulator
    if binary_string_4_bits not in prob_distribution:
        
        # add the missing binary string with 4 bits to
        # the diectionary of the probability
        # distributions from the quasi-distributions of
        # the results obtained from the execution of
        # the quantum circuit created before,
        # using the simulator, with a negligible value
        prob_distribution[ binary_string_4_bits ] = 1e-8


# fix the probability distributions
# from the quasi-distributions of
# the results obtained from the execution of
# the quantum circuit created before,
# using a real quantum device,
# for values lower or equal to 0 (zero)
prob_distribution_real = { binary_string : prob if prob > 0 else 1e-8
                           for binary_string, prob in prob_distribution_real.items() }


# plot the probability distributions
# from the quasi-distributions of
# the results obtained from the execution of
# the quantum circuit created before,
# using the simulator and a real quantum device
plot_distribution(
    data=[ prob_distribution, prob_distribution_real ], 
    legend=[ "Simulator", "Quantum Real Device" ], 
    color=[ "lightblue", "black" ], 
    bar_labels = False
)

# import the Quantum Circuit and the execute
# instruction from the IBM's Qiskit library
from qiskit import QuantumCircuit, execute

# import the Qiskit's Aer object
# from the IBM's Qiskit library
from qiskit import Aer


# retrieve the Unitary Simulator object
backend = Aer.get_backend( "unitary_simulator" )


######################################

#### for the 1st quantum circuit ####

# create the 1st quantum circuit,
# with a quantum register of 2 qubits
qc_1 = QuantumCircuit(2)


# apply the inverse of S gate on
# the 2nd qubit on the quantum register of
# the 1st quantum circuit created before
qc_1.sdg(1)

# apply the CX/CNOT gate on the 1st qubit
# as control and on the 2nd qubit as target,
# both on the quantum register of
# the 1st quantum circuit created before
qc_1.cx( 0, 1 )

# apply the S gate on the 2nd
# qubit on the quantum register of
# the 1st quantum circuit created before
qc_1.s(1)


# execute the quantum simulation of
# the 1st quantum circuit created before,
# and retrieve the respective job
job = execute( qc_1, backend, shots=10000 )

# retrieve the result obtained
# from the quantum simulation of
# the 1st quantum circuit created before
result = job.result()


# retrieve the unitary operator from the results
# obtained from the quantum simulation of
# the 1st quantum circuit created before
qc_1_unitary = result.get_unitary( qc_1, decimals = 3 )


######################################

#### for the 2nd quantum circuit ####

# create the 2nd quantum circuit,
# with a quantum register of 2 qubits
qc_2 = QuantumCircuit(2)


# apply the CY gate on the 1st qubit
# as control and on the 2nd qubit as target,
# both on the quantum register of
# the 2nd quantum circuit created before
qc_2.cy( 0, 1)


# execute the quantum simulation of
# the 2nd quantum circuit created before,
# and retrieve the respective job
job = execute( qc_2, backend, shots=10000 )

# retrieve the result obtained
# from the quantum simulation of
# the 2nd quantum circuit created before
result = job.result()


# retrieve the unitary operator from the results
# obtained from the quantum simulation of
# the 2nd quantum circuit created before
qc_2_unitary = result.get_unitary( qc_2, decimals = 3 )


# if the two quantum circuits are
# represented by the same unitary operator 
if( qc_1_unitary == qc_2_unitary ):

    # print an informative success message,
    # in the case that the two quantum circuits
    # are represented by the same unitary operator 
    print( "The two quantum circuits are represented by "
           "the same unitary operator!" )

# build your code here
answer13 = [3]

# run this cell to submit your answer

# import the grader for the exercise 13 of the lab/badge 1
from qc_grader.challenges.quantum_explorers23 import grade_badge1_ex13

# expected result type: List
grade_badge1_ex13(answer13)

# build your code here
answer14 = [3]

# run this cell to submit your answer

# import the grader for the exercise 14 of the lab/badge 1
from qc_grader.challenges.quantum_explorers23 import grade_badge1_ex14

# expected result type: List
grade_badge1_ex14(answer14)

# build your code here
answer15 = [4]

# run this cell to submit your answer

# import the grader for the exercise 15 of the lab/badge 1
from qc_grader.challenges.quantum_explorers23 import grade_badge1_ex15
 
# expected result type: List
grade_badge1_ex15(answer15)

# run this cell to check whether you have passed

# import the grader for the badge 1 of
# the IBM Qiskit's Quantum Explorers 2023-2024
from qc_grader.challenges.quantum_explorers23 import grade_badge1_score

# grade the score of the quiz of the lab for the badge 1
grade_badge1_score("en")