# update/upgrade Python's pip module
!pip install --upgrade pip -q

# install the IBM Qiskit's Machine Learning and Optimization modules
!pip install qiskit_machine_learning qiskit_optimization -q

# build your code here
answer1 = [2]

# run this cell to submit your answer

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

# expected result type: List
grade_badge6_ex1(answer1)

# build your code here
answer2 = [4]

# run this cell to submit your answer

# import the grader for the exercise 2 of the lab/badge 6
from qc_grader.challenges.quantum_explorers23 import grade_badge6_ex2

# expected result type: List
grade_badge6_ex2(answer2)

# build your code here
answer3 = [3]

# run this cell to submit your answer

# import the grader for the exercise 3 of the lab/badge 6
from qc_grader.challenges.quantum_explorers23 import grade_badge6_ex3

# expected result type: List
grade_badge6_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 6
from qc_grader.challenges.quantum_explorers23 import grade_badge6_ex4

# expected result type: List
grade_badge6_ex4(answer4)

# build your code here
answer5 = [3]

# run this cell to submit your answer

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

# expected result type: List
grade_badge6_ex5(answer5)

# build your code here
answer6 = [1]

# run this cell to submit your answer

# import the grader for the exercise 6 of the lab/badge 6
from qc_grader.challenges.quantum_explorers23 import grade_badge6_ex6

# expected result type: List
grade_badge6_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 6
from qc_grader.challenges.quantum_explorers23 import grade_badge6_ex7

# expected result type: list
grade_badge6_ex7(answer7)

# build your code here
answer8 = [1]

# run this cell to submit your answer

# import the grader for the exercise 8 of the lab/badge 6
from qc_grader.challenges.quantum_explorers23 import grade_badge6_ex8

# expected result type: List
grade_badge6_ex8(answer8)

# build your code here
answer9 = [3]

# run this cell to submit your answer

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

# expected result type: List
grade_badge6_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 6
from qc_grader.challenges.quantum_explorers23 import grade_badge6_ex10

# expected result type: List
grade_badge6_ex10(answer10)

# build your code here
answer11 = [3]

# run this cell to submit your answer

# import the grader for the exercise 11 of the lab/badge 6
from qc_grader.challenges.quantum_explorers23 import grade_badge6_ex11

# expected result type: List
grade_badge6_ex11(answer11)

# build your code here
answer12 = [4]

# run this cell to submit your answer

# import the grader for the exercise 12 of the lab/badge 6
from qc_grader.challenges.quantum_explorers23 import grade_badge6_ex12

# expected result type: List
grade_badge6_ex12(answer12)

# cod block for the definition of helper functions
# to draw and visualize graph solution

# import the PyPlot module from the Matplot Library 
import matplotlib.pyplot as plt

# import the NetworkX Library
import networkx as nx


# definition of a helper function to draw the graph,
# using the NetworkX library's functionalities
def draw_graph(G, colors, pos):
    
    # retrieve the default axes of the plotting object with a frame-on
    default_axes = plt.axes(frameon = True)
    
    # draw a network representing the graph, with the respective nodes and edges
    nx.draw_networkx(G, node_color = colors, node_size = 600, alpha = 0.8,
                     ax = default_axes, pos = pos, node_shape = "o")
    
    # retrieve the weight labels of the edges of the graph
    edge_labels = nx.get_edge_attributes(G, "weight")
    
    # draw the weight labels of the edges to the resulting graph network
    nx.draw_networkx_edge_labels(G, pos = pos, edge_labels = edge_labels)


# definition of a helper function to draw the solution of
# the Traveling Salesman Problem (TSP), in the respective graph representation
def draw_tsp_solution(G, order, colors, pos):
    
    # convert the structure of the initial given
    # undirected graph to a directed graph
    G2 = nx.DiGraph()
    
    # add the nodes from the initial given
    # undirected graph to the created directed graph
    G2.add_nodes_from(G)
    
    # retrieves the number of edges of the directed graph representation
    n = len(order)
    
    
    # for any edge for the directed graph representing the solution
    # for the given Traveling Salesman Problem (TSP)
    for i in range(n):
        
        # retrieve the destination node for the current directed edge
        j = (i + 1) % n
        
        # add the current directed edge to
        # the directed graph representing the solution
        # for the given Traveling Salesman Problem (TSP)
        G2.add_edge(order[i], order[j], weight = G[order[i]][order[j]]["weight"])
    
    
    # retrieve the default axes of the plotting object with a frame-on
    default_axes = plt.axes(frameon = True)
    
    # draw a network representing the new directed graph,
    # with the respective nodes and edges, representing
    # the solution for the given Traveling Salesman Problem (TSP)
    nx.draw_networkx(G2, node_color = colors, edge_color = "b",
                     node_size = 600, alpha = 0.8,
                     ax = default_axes, pos = pos)
    
    # retrieve the weight labels of the edges of the new directed graph,
    # representing the solution for the given Traveling Salesman Problem (TSP)
    edge_labels = nx.get_edge_attributes(G2, "weight")
    
    # draw the weight labels of the edges to the resulting new directed graph network,
    # representing the solution for the given Traveling Salesman Problem (TSP)
    nx.draw_networkx_edge_labels(G2, pos, font_color = "b", edge_labels = edge_labels)

# import the Traveling Salesman Problem (TSP) from
# the IBM Qiskit's Optimization Application module
from qiskit_optimization.applications import Tsp


# create a random instance of the Traveling Salesman Problem (TSP),
# with 4 nodes and a fixed pseudo-random seed
tsp = Tsp.create_random_instance(4, seed=43)

# retrieve the adjoint matrix for the graph representation of
# the instance of the Traveling Salesman Problem (TSP) previously created
adj_matrix = nx.to_numpy_array(tsp.graph)

# print the adjoint matrix for the graph representation of
# the instance of the Traveling Salesman Problem (TSP) previously created,
# as the distances (edges' weigths) between the respective nodes
print("Distances:\n", adj_matrix)

# set the color of the nodes of
# the instance of the Traveling Salesman Problem (TSP)
# previously created, with the red color 
colors = ["r"] * len(tsp.graph.nodes)

# retrieve the positions of the nodes of
# the instance of the Traveling Salesman Problem (TSP)
pos = [tsp.graph.nodes[node]["pos"] for node in tsp.graph.nodes]

# draw the graph representation of
# the instance of the Traveling Salesman Problem (TSP),
# using the NetworkX library's functionalities
draw_graph(tsp.graph, colors, pos)

# import the Quandratic Program to
# Quadratic Unconstrained Binary Optimization (QUBO) converter,
# from the IBM Qiskit's Optimization Convertes module
from qiskit_optimization.converters import QuadraticProgramToQubo


# convert the original optimization formulation of
# the Traveling Salesman Problem (TSP) created before,
# to a Quadratic Program, to solve it using a quantum computer
qp = tsp.to_quadratic_program()

# initialize the Quandratic Program to
# Quadratic Unconstrained Binary Optimization (QUBO) converter
qp2qubo = QuadraticProgramToQubo()

# convert the Quadratic Program formulation of
# the Traveling Salesman Problem (TSP) created before,
# to a Quadratic Unconstrained Binary Optimization (QUBO) Program,
# to solve it using a quantum computer
qubo = qp2qubo.convert(qp)

# convert the Quadratic Unconstrained Binary Optimization (QUBO)
# formulation of the Traveling Salesman Problem (TSP) created before,
# to an Ising Hamiltonian, to solve it using a quantum computer,
# retrieving the respective qubit operations
qubitOp, offset = qubo.to_ising()

# import the Sampling Variational Quantum Eigensolver (VQE) algorithm
# from the IBM Qiskit's Minimum Eigesolvers Algorithms module
from qiskit.algorithms.minimum_eigensolvers import SamplingVQE

# import the Nakanishi-Fujii-Todo (NFT) Optimizer
# from the IBM Qiskit's Optimizers Algorithms module
from qiskit.algorithms.optimizers import NFT

# import the parametrized two-local quantum circuits
# from the IBM Qiskit's Circuit Library module
from qiskit.circuit.library import TwoLocal

# import the Aer Sample from
# the IBM Qiskit's Aer Primitives module
from qiskit_aer.primitives import Sampler as AerSampler

# import the Algorithm's Global Parameters from
# the IBM Qiskit's Utilities module
from qiskit.utils import algorithm_globals


# set a fixed pseudo-random seed
# as an Algorithm's Global Parameter
algorithm_globals.random_seed = 123


# create a Nakanishi-Fujii-Todo (NFT) Optimizer instance,
# with a maximum number of 100 iterations
optimizer = NFT(maxiter=100)

# create a Parametrized Two-Local Ansatz Quantum Circuit,
# for the parametrized y-axis rotations for the qubits,
# aiming a linear quantum entanglement
ry = TwoLocal(qubitOp.num_qubits, "ry", "cz",
              reps = 2, entanglement = "linear")


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

# build a Variational Quantum Eigensolver (VQE) algorithm instance,
# with the respective Aer Sample, Parametrized Two-Local Ansatz
# Quantum Circuit, and Nakanishi-Fujii-Todo (NFT) Optimizer,
# to solve the Traveling Salesman Problem (TSP) created before
vqe = SamplingVQE(sampler=AerSampler(), ansatz=ry, optimizer=optimizer)

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


# compute the minimum eigenvalue for
# the Variational Quantum Eigensolver (VQE) algorithm instance
# created before to solve the Traveling Salesman Problem (TSP)
# created before, using the Qubit Operations retrieved before
result = vqe.compute_minimum_eigenvalue(qubitOp)

# print the energy values from minimum eigenvalue computed before
# from Variational Quantum Eigensolver (VQE) algorithm instance
# that solved the Traveling Salesman Problem (TSP)
print("Energy:", result.eigenvalue.real)

# print the elepsed time to compute minimum eigenvalue
# from Variational Quantum Eigensolver (VQE) algorithm instance
# that solved the Traveling Salesman Problem (TSP)
print("Elapsed Time (secs):", result.optimizer_time)


# retrieve the most likely saple from the eigenstate
# from the minimum eigenvalue computed before
# from Variational Quantum Eigensolver (VQE) algorithm instance
# that solved the Traveling Salesman Problem (TSP),
# as one the possible solutions for it
x = tsp.sample_most_likely(result.eigenstate)

# retrieve the boolean flag with respect to
# the fact that the solution achieved is feasible
# for the Quadratic Unconstrained Binary Optimization (QUBO) Program
# formulating the pretended Traveling Salesman Problem (TSP)
print("Feasible:", qubo.is_feasible(x))

# retrieves (and interprets) the solution result as a list of
# nodes' indices corresponding to the real solution
# for the pretended Traveling Salesman Problem (TSP)
z = tsp.interpret(x)

# print the list of nodes' indices
# corresponding to the real solution (nodes' sequence)
# for the pretended Traveling Salesman Problem (TSP)
print("Solution:", z)

# print solution to the objective function
# corresponding to the real solution (nodes' sequence)
# for the pretended Traveling Salesman Problem (TSP)
print("Solution Objective:", tsp.tsp_value(z, adj_matrix))

# draw the solution of the pretended Traveling Salesman Problem (TSP)
# solution, in the respective graph representation 
draw_tsp_solution(tsp.graph, z, colors, pos)

# run this cell to submit your answer

# import the grader for the coding exercise of the lab/badge 6
from qc_grader.challenges.quantum_explorers23 import grade_badge6_code

# expected result type: ( tsp: Tsp, qubo: QuadraticProgram,
#                         result: SamplingVQEResult )
grade_badge6_code(tsp, qubo, result)

# import the Qiskit Runtime Service, Session, and Sampler,
# from the Qiksit'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
# with a minimum number of qubits
# corresponding to the number of qubits
# required for the retrieved Qubit Operations
# (this step can take a while)
real_backend = service.least_busy( min_num_qubits=qubitOp.num_qubits,
                                   simulator=False )

# print the information about the best (i.e., the least-busy)
# backend with a minimum number of two qubits, and an informative
# message about the job submission on this backend
print(f"The least busy backend is {real_backend.name}!")

# within the context of a Session configured
# with the Qiskit's Runtime Service object,
# created before, using the IBM Quantum channel,
# and also using the backend defined before
# with Session( service=service, backend=real_backend ) as session:
    
    ###### add your answers from the above code cell here #######
    ###### before running this code cell itself           #######
    
    # build a Variational Quantum Eigensolver (VQE) algorithm instance,
    # with the respective Aer Sample, Parametrized Two-Local Ansatz
    # Quantum Circuit, and Nakanishi-Fujii-Todo (NFT) Optimizer,
    # to solve the Traveling Salesman Problem (TSP) created before
    #vqe = SamplingVQE(sampler=Sampler(), ansatz=ry, optimizer=optimizer)   

    # compute the minimum eigenvalue for
    # the Variational Quantum Eigensolver (VQE) algorithm instance
    # created before to solve the Traveling Salesman Problem (TSP)
    # created before, using the Qubit Operations retrieved before
    #result = vqe.compute_minimum_eigenvalue(qubitOp)


# compute the minimum eigenvalue for
# the Variational Quantum Eigensolver (VQE) algorithm instance
# created before to solve the Traveling Salesman Problem (TSP)
# created before, using the Qubit Operations retrieved before
#result = vqe.compute_minimum_eigenvalue(qubitOp)

# print the energy values from minimum eigenvalue computed before
# from Variational Quantum Eigensolver (VQE) algorithm instance
# that solved the Traveling Salesman Problem (TSP)
#print("Energy:", result.eigenvalue.real)

# print the elepsed time to compute minimum eigenvalue
# from Variational Quantum Eigensolver (VQE) algorithm instance
# that solved the Traveling Salesman Problem (TSP)
#print("Elapsed Time (secs):", result.optimizer_time)


# retrieve the most likely saple from the eigenstate
# from the minimum eigenvalue computed before
# from Variational Quantum Eigensolver (VQE) algorithm instance
# that solved the Traveling Salesman Problem (TSP),
# as one the possible solutions for it
#x = tsp.sample_most_likely(result.eigenstate)

# retrieve the boolean flag with respect to
# the fact that the solution achieved is feasible
# for the Quadratic Unconstrained Binary Optimization (QUBO) Program
# formulating the pretended Traveling Salesman Problem (TSP)
#print("Feasible:", qubo.is_feasible(x))

# retrieves (and interprets) the solution result as a list of
# nodes' indices corresponding to the real solution
# for the pretended Traveling Salesman Problem (TSP)
#z = tsp.interpret(x)

# print the list of nodes' indices
# corresponding to the real solution (nodes' sequence)
# for the pretended Traveling Salesman Problem (TSP)
#print("Solution:", z)

# print solution to the objective function
# corresponding to the real solution (nodes' sequence)
# for the pretended Traveling Salesman Problem (TSP)
#print("Solution Objective:", tsp.tsp_value(z, adj_matrix))

# draw the solution of the pretended Traveling Salesman Problem (TSP)
# solution, in the respective graph representation 
#draw_tsp_solution(tsp.graph, z, colors, pos)

# build your code here
answer13 = [1]

# run this cell to submit your answer

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

# expected result type: List
grade_badge6_ex13(answer13)

# build your code here
answer14 = [1]

# run this cell to submit your answer

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

# expected result type: List
grade_badge6_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 6
from qc_grader.challenges.quantum_explorers23 import grade_badge6_ex15

# expected result type: List    
grade_badge6_ex15(answer15)

# run this cell to check whether you have passed

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

# grade the score of the quiz of the lab for the badge 6
grade_badge6_score("en")