# -*- coding: utf-8 -*-
"""Real Quantum Device - Implementation of 2004 Talbi QIGA for TSP.ipynb

Automatically generated by Colaboratory.

Original file is located at
    https://colab.research.google.com/drive/1aJ4Xj7QqD0jyeZV-NZhRjX262SGcjR19
"""

#install Quantum library from IBM
pip install qiskit -q

pip install tsplib95 -q

import tsplib95

pip install networkx -q

import networkx as nx

import numpy as np
import matplotlib.pyplot as plt
import random
import pandas as pd
import math  
import time
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit import Aer, execute, IBMQ
from qiskit.tools.visualization import plot_histogram, plot_state_city
from qiskit.providers.aer.noise import NoiseModel
from qiskit.providers.ibmq import least_busy

import seaborn as sns

token = '19d63723cbc1c4747be173678dc6132c1ae1f6fe269298e740c6d3a928379c57020a6f2d04a1607232a3a0968c40a09fce7ec6d1769d878169a70b04feb73d04'
IBMQ.save_account(token)
# Choose a real device to simulate
provider = IBMQ.load_account()

"""###Google Access"""

#Access to google drive. The Video Store dataset was uploaded to google drive.
from google.colab import drive
drive.mount('/content/gdrive')

#Load dataset
# problem = tsplib95.load_problem('/content/gdrive/My Drive/Final Year Project/datasets/gr24.tsp')

# Build noise model from backend properties
provider = IBMQ.load_account()
backend = provider.get_backend('ibmq_vigo')
noise_model = NoiseModel.from_backend(backend)

# Get coupling map from backend
coupling_map = backend.configuration().coupling_map

# Get basis gates from noise model
basis_gates = noise_model.basis_gates

"""###Chromosome Class"""

class Chromosome:
  quantumCircuit = None
  matrix = None
  size = 0
  fitness = 0
  #current_state_vector_list = list()
  binary_state_vector_list = list()
  results_list = list()
  counts = None
  visiting_order = []
  simulator = Aer.get_backend('statevector_simulator')
  def __init__(self,size): #resolved
    
    self.size = size
    #create the quantum circuit
    self.quantumCircuit = self.createChromosome(size)

    #create the array
    self.matrix = self.createIndexedMatrix(size)
    
    #putting the quantum circuit to super position (For a Statevector a simulator)
    #self.addToSuperposition(self.quantumCircuit,self.size)
    
    #reinitialize
    self.reinitialize()
    #print('Hi')

  def createChromosome(self,n): #resolved
    quantum_circs = list()
    #create n number of Quantum circuits
    for i in range(n*n):
      circ = QuantumCircuit(1, 1)
      quantum_circs.append(circ)
    
    return quantum_circs

  def createIndexedMatrix(self,n): #resolved
    arr = np.empty([n,n],list)
    count = 0
    for i in range(n):
      for j in range(n):
        init_alpha = 1/math.sqrt(2) #this is the inital qubit value to be initialized to
        init_beta = 1/math.sqrt(2)
        arr[i][j] = [self.quantumCircuit[count],count,[init_alpha,init_beta]]
        count = count + 1
    
    return arr

  def getQubitState(self,loc): #resolved
    
    index = int(self.matrix[loc[0]][loc[1]][1])
    val = '-1'
    if '1' in self.binary_state_vector_list[index]:
      val = '1'
    else:
      val = '0'
    return val
      
    return -1
    #print(index)
  
  def addToSuperposition(self,circ,size): #resolved
    for i in range(len(circ)):
      circ[i].h(0)
  
  def measure(self): #resolved
    for i in range(len(self.quantumCircuit)):
      self.quantumCircuit[i].measure(0,0)

  def qubit_rotate(self,loc,angle):
    index = self.matrix[loc[0]][loc[1]][1]
    qc = self.quantumCircuit[index]
    #add rotation
    qc.rz(angle,0)


  def chromosome_execute(self, state_return): #resolved
    #get backend ###################
    large_enough_devices = provider.backends(filters=lambda x: x.configuration().n_qubits < 10 and not x.configuration().simulator)
    backend = least_busy(large_enough_devices)
    ################################
    clear_s=time.time()

    # if(len(self.current_state_vector_list) != 0 and len(self.binary_state_vector_list) != 0):
    #   self.current_state_vector_list.clear()
    #   self.binary_state_vector_list.clear()

    if(len(self.binary_state_vector_list) == n_cities*n_cities):
      self.binary_state_vector_list.clear()

    clear_e = time.time()
    clear_time_log.append(clear_e-clear_s)
    #select the backend
    
    # properties = simulator.properties()
    # noise_model = noise.device.basic_device_noise_model(properties)
    # basis_gates = noise_model.basis_gates
    # coupling_map = device.configuration().coupling_map

    set_states_s = time.time()
    for i in range(len(self.quantumCircuit)):
      select_circ_s = time.time()
      circ = self.quantumCircuit[i]
      select_circ_e = time.time()
      select_circ_time_log.append(select_circ_e - select_circ_s)

      #print('\t\t Now drawing\n\n')
      #circ.draw()
      #print('\n\n')

      exe_s = time.time()
      #for simulation
      #result = execute(circ, self.simulator).result()
      #For real device
      result = execute(circ, backend=backend, shots=1).result()
      #noise
      #result = execute(circ, Aer.get_backend('qasm_simulator'),shots=1, coupling_map=coupling_map, basis_gates=basis_gates, noise_model=noise_model).result()
      gen_cir.append(circ)
      exe_e = time.time()
      exe_time_log.append(exe_e - exe_s)

      if(state_return):
        #calc binary statues
        bin_s = time.time()
        self.binary_state_vector_list.append(result.get_counts(circ))
        bin_e=time.time()
        bin_time_log.append(bin_e - bin_s)
      else:
        #calc statevector
        st_s = time.time()
        #self.current_state_vector_list.append(result.get_statevector(circ))
        st_e = time.time()
        st_time_log.append(st_e - st_s)
    set_states_e = time.time() 
    set_states_time_log.append(set_states_e-set_states_s) 

  def calculate_distance(self,start_city,end_city):
    # start_index = 0
    # end_index = 1
    #write something to calculate distance
    #distance = problem.wfunc(start_city, end_city)
    distance = dist_matrix[start_city, end_city]
    return distance
  
  def show_solution(self): #resolved
    solution = np.empty([self.size,self.size])
    for i in range(self.size):
      for j in range(self.size):
        solution[i][j] = self.getQubitState([i,j])

    #print(solution)
    return solution
  
  def create_visiting_order(self):
    solution = self.show_solution()
    #clear visiting order
    self.visiting_order.clear()
    for i in range(self.size):
      for j in range(self.size):
        val = solution[i][j]
        if(val == 1):
          self.visiting_order.append(j)

  def plot_solution(self):
    print('Currently out of order')
    # x_points = []
    # y_points = []
    # for i in range(len(self.visiting_order)):
    #   city = self.visiting_order[i]
    #   x_points.append(city_locs[city][0])
    #   y_points.append(city_locs[city][1])

    # plt.scatter(x_points,y_points,c='red')
    # plt.plot(x_points,y_points)

  
  def getIndex(self,loc):
    access_list = self.matrix[loc[0]][loc[1]]
    index = access_list[1] 
    return index
  
  def reinitialize(self):
    # for l in range(len(self.quantumCircuit)):
    #   alpha_state = -1
    #   beta_state = -1
    #   for i in range(self.size):
    #     for j in range(self.size):
    #       index = self.matrix[i][j][1]
    #       #when taking from the state vector
    #       #alpha_state = self.current_state_vector_list[index][0]
    #       #beta_state = self.current_state_vector_list[index][1]
          
    #       #when calculated and kept track
    #       alpha_state = self.matrix[i][j][2][0]
    #       beta_state = self.matrix[i][j][2][1]
    #       #print('\t\t\t Total : ' + str(math.pow(alpha_state,2) + math.pow(beta_state,2)))
    #   self.quantumCircuit[l].initialize([alpha_state,beta_state],[0])

    for i in range(self.size):
      for j in range(self.size):
        index = self.matrix[i][j][1]
        alpha = self.matrix[i][j][2][0]
        beta = self.matrix[i][j][2][1]

        self.quantumCircuit[index].initialize([alpha,beta],[0])

      

  
  def calculate_fitness(self):
    state_bin_s = time.time()
    #save the current state vector
    self.chromosome_execute(False)

    mes_s = time.time() 
    #add measuring gates
    self.measure()
    mes_e = time.time()
    mes_time_log.append(mes_e - mes_s)

    #quantum measurement 
    self.chromosome_execute(True)
    state_bin_e = time.time()
    state_bin_time_log.append(state_bin_e - state_bin_s)
    
    #calculate fiteness
    start = -1
    end = -1 
    self.fitness = 0

    one_check_s = time.time()
    #checking row wise
    for row in range(self.size):
      one_count = 0
      for col in range(self.size):
        if(self.getQubitState([row,col]) == '1'):
          one_count += 1
      
      if(one_count == 1):
        #correctly made for this row
        if(start == -1 and end == -1):
          #first city to start
          start = j
        elif(start != -1 and end == -1):
          #start city is already alocated and not the end city
          end = j
          #add cost
          self.fitness = self.calculate_distance(start,end)
          #reset values
          start = end
          end = -1
        
      else:
        #not correctly made
        #print('\t More than one 1 per row. No cost added')
        #to avoid remaining in 0 cost
        self.fitness += one_count*10

    one_check_e = time.time()
    one_check_time_log.append(one_check_e-one_check_s)
    #function to check column wise ones is not done. Since there is no description about it in the algorithm
    #therefore as per this the best solution should be all 0 if the minimum cost is 0

"""###Functions"""

#save global best
def save_global_best(new_chrom): 
  # new chrom
  c1 = Chromosome(n_cities)

  #copy state and binary
  for g in range(n_cities*n_cities):
    #binary
    c1.binary_state_vector_list[g] = new_chrom.binary_state_vector_list[g]

  #copy the matrix
  for h in range(n_cities):
    for j in range(n_cities):
      c1.matrix[h][j] = new_chrom.matrix[h][j]
  
  global_best_solution = c1

#create population function
def create_population(size,n_cities): #checked

  #create a temp list to store the population created
  population = list()

  for i in range(size):
    #generate population
    solution = Chromosome(n_cities)
    population.append(solution)
  
  #return the population list  
  return population

# calculates fitness for each solution
def calculate_fitness(population):  #checked
  #get population size
  popSize = len(population)
  for i in range(popSize):
    population[i].calculate_fitness()

# get the solution with the best fitness
def get_best_fit_solution(population):  #checked
  # lower the fitness better
  # variable to store the best fitness value
  best_fitness = 10000 
  # variable to store the best fitness id
  best_id = -1
  #population size
  popSize = len(population)

  for i in range(popSize):
    get_fitness = population[i].fitness
    if(get_fitness < best_fitness):
      #better solution found
      best_fitness = get_fitness
      best_id = i
  return best_id

# runs quantum interference on the circuits
def quantum_interfer(population,n_cities): #checked
  popSize = len(population)
  #get the best fit solution
  best_fit_id = get_best_fit_solution(population)
  best_chromosome = population[best_fit_id]

  #iterationg through each solution in the population
  for l in range(popSize):
    # interference takes place only if the selected solution is not the best solution
    if(l != best_fit_id):
      # if not the best solution selected  
      selected_chrom = population[l]

      for i in range(n_cities):
        delta_plus = False
        delta_minus = False
        for j in range(n_cities):
          # getting the reference bit of the best solution so far
          reference_bit = best_chromosome.getQubitState([i,j])

          #selected item
          index = selected_chrom.matrix[i][j][1]

          # selected_alpha = np.real((selected_chrom.current_state_vector_list[index])[0])
          # selected_beta= np.real((selected_chrom.current_state_vector_list[index])[1])

          selected_alpha = np.real(selected_chrom.matrix[i][j][2][0])
          selected_beta = np.real(selected_chrom.matrix[i][j][2][1])

          #lookup table

          #delta plus
          sit_1_plus = (selected_alpha > 0 and selected_beta > 0 and reference_bit == '1')
          sit_2_plus = (selected_alpha > 0 and selected_beta < 0 and reference_bit == '0')
          sit_3_plus = (selected_alpha < 0 and selected_beta > 0 and reference_bit == '0')
          sit_4_plus = (selected_alpha < 0 and selected_beta < 0 and reference_bit == '1')

          delta_plus = sit_1_plus or sit_2_plus or sit_3_plus or sit_4_plus

          #delta minus
          sit_1_minus = (selected_alpha > 0 and selected_beta > 0 and reference_bit == '0')
          sit_2_minus = (selected_alpha > 0 and selected_beta < 0 and reference_bit == '1')
          sit_3_minus = (selected_alpha < 0 and selected_beta > 0 and reference_bit == '1')
          sit_4_minus = (selected_alpha < 0 and selected_beta < 0 and reference_bit == '0')

          delta_minus = sit_1_minus or sit_2_minus or sit_3_minus or sit_4_minus
          #print('selected alpha : ' + str(selected_alpha) + ' selected beta : ' + str(selected_beta) + ' Reference : ' + str(reference_bit) +'\n')

          if(delta_plus and delta_minus == False):
            #delta plus
            angle = np.pi/25
            #print('Delta Positive turn')
            selected_chrom.qubit_rotate([i,j],angle)
            
            #update states
            alpha_new, beta_new = rotation_update_state(angle,[selected_alpha,selected_beta])
            selected_chrom.matrix[i][j][2][0] = alpha_new
            selected_chrom.matrix[i][j][2][1] = beta_new
          elif(delta_minus and delta_plus == False):
            #delta minus
            #print('Delta negative turn')
            angle = (np.pi/25)*(-1)
            selected_chrom.qubit_rotate([i,j],angle)

            #update states
            alpha_new, beta_new = rotation_update_state(angle,[selected_alpha,selected_beta])
            selected_chrom.matrix[i][j][2][0] = alpha_new
            selected_chrom.matrix[i][j][2][1] = beta_new
          #else:
            #error
            #print('Special case occured => ' + 'Delta Positive : ' + str(delta_plus) + ' Delta Negative : ' + str(delta_minus))
            #print('sp1 :' + str(sit_1_plus) + ' sp2 :' + str(sit_2_plus) + ' sp3 :' + str(sit_3_plus) + ' sp4 :' + str(sit_4_plus))
            #print('sn1 :' + str(sit_1_minus) + ' sn2 :' + str(sit_2_minus) + ' sn3 :' + str(sit_3_minus) + ' sn4 :' + str(sit_4_minus))
            #print('selected alpha : ' + str(selected_alpha) + ' selected beta : ' + str(selected_beta) + ' Reference : ' + str(reference_bit) +'\n')

#crossover
def crossover(popSize,n_cities): #checked
  cross_col = 0
  
  for i in range(popSize-1):
    for k in range(i+1,popSize):
      #print('\n Current i :' + str(i) +'\n')
      #get the parents
      parent_1 = population[i]
      parent_2 = population[k]

      #create the children
      c1 = Chromosome(n_cities)
      c2 = Chromosome(n_cities)

      #duplicate
      #copy the state vector and binary
      for g in range(n_cities*n_cities):
        #state vector
        #c1.current_state_vector_list[g] = parent_1.current_state_vector_list[g]
        #c2.current_state_vector_list[g] = parent_2.current_state_vector_list[g]

        #binary
        c1.binary_state_vector_list[g] = parent_1.binary_state_vector_list[g]
        c2.binary_state_vector_list[g] = parent_2.binary_state_vector_list[g]

      #copy the matrix
      for h in range(n_cities):
        for j in range(n_cities):
          c1.matrix[h][j] = parent_1.matrix[h][j]
          c2.matrix[h][j] = parent_2.matrix[h][j]
      
      #actualy crossover
      for j in range(n_cities):
        #temp = parent_1[j][cross_col]
        c1.matrix[j][cross_col] = parent_2.matrix[j][cross_col]
        c2.matrix[j][cross_col] = parent_1.matrix[j][cross_col]
        

        #c1.current_state_vector_list[parent_1.matrix[j][cross_col][1]] = parent_2.current_state_vector_list[parent_2.matrix[j][cross_col][1]]
        #c2.current_state_vector_list[parent_2.matrix[j][cross_col][1]] = parent_1.current_state_vector_list[parent_1.matrix[j][cross_col][1]]

        c1.binary_state_vector_list[parent_1.matrix[j][cross_col][1]] = parent_2.binary_state_vector_list[parent_2.matrix[j][cross_col][1]]
        c2.binary_state_vector_list[parent_2.matrix[j][cross_col][1]] = parent_1.binary_state_vector_list[parent_1.matrix[j][cross_col][1]]

      population.append(c1)
      population.append(c2)

#mutation
def permute(population,n_cities,permute_thres,permute_prob):
  
  for chrom in range(len(population)):
    #probability
    r = np.random.uniform(0,1)
    
    if(r <= permute_thres):
      #decided to apply permutation
      for i in range(n_cities):
        r = np.random.uniform(0,1)
        if(r<= permute_prob):
          j = np.random.randint(0,n_cities)
          
          if(i != j):
            #2 selected rows not the same
            sol = population[chrom]
            
            for col in range(n_cities):
              temp_qubit = sol.matrix[i][col][0]
              temp_index = sol.matrix[i][col][1]
              temp_states = sol.matrix[i][col][2]
              sol.matrix[i][col][0] = sol.matrix[j][col][0]
              sol.matrix[i][col][1] = sol.matrix[j][col][1]
              sol.matrix[i][col][2] = sol.matrix[j][col][2]
              sol.matrix[j][col][0] = temp_qubit
              sol.matrix[j][col][1] = temp_index
              sol.matrix[j][col][2] = temp_states

def shift(shift_thres):
  #generate 
  r = random.random()

  if(r <= shift_thres):
    Nbr = random.randint(1,int(n_cities/3))
    if(n_cities - Nbr > 1):
      i = random.randint(1,n_cities - Nbr)
      if(n_cities-Nbr-i > 1):
        step = random.randint(1,n_cities-Nbr-i)

        #Shift Nbr lines starting from i according to steps
        for line in range(i,i+Nbr+1):
          for col in range(n_cities):
            
            if(line + step < n_cities):
              temp = population[i].matrix[line + step][col]
              #print('Temp : ' + str(temp[1]))
              population[i].matrix[line + step][col] = population[i].matrix[line][col]
              #print('Copied : ' + str(population[i].matrix[line + step][col][1]))
              population[i].matrix[line][col] = temp 
              #print('Replace : ' + str(population[i].matrix[line][col][1]))
            #else :
              #print('Goes out of array. Shift was not done')

def iterate(times,population):
  
  for i in range(times):
    start_time = time.time()
    #Calculate fitness
    print('Iteration : ' + str(i)  + ' Fitness started')
    Fs = time.time()

    for j in range(len(population)):
      print('\t Fitness Calc : ' + str(j)  + ' out of' + str(len(population)))
      population[j].calculate_fitness()
    
    Fe = time.time()
    fitness_time_log.append(Fe-Fs)

    #Run Interference
    Is =time.time()
    print('Iteration : ' + str(i)  + ' Interference started')
    
    quantum_interfer(population,n_cities)
    
    Ie = time.time()
    Interfer_time_log.append(Ie-Is)

    #reinitialize
    print('Iteration : ' + str(i)  + ' Reinitialize started')
    RIs = time.time()
    
    for j in range(len(population)):
      print('\t Reintialize : ' + str(j)  + ' out of' + str(len(population)))
      population[j].reinitialize()
    
    RIe = time.time()
    Reinit_time_log.append(RIe - RIs)

    #crossover
    Cs = time.time()
    print('Iteration : ' + str(i)  + ' Crossover started')
    
    crossover(popSize,n_cities)
    
    Ce = time.time()
    Crossover_time_log.append(Ce - Cs)
    
    #permute
    print('Iteration : ' + str(i)  + ' Permute started')
    Ps = time.time()
    
    permute(population,n_cities,permute_thres,permute_prob)
    
    Pe = time.time()
    Permute_time_log.append(Pe-Ps)

    #shift
    print('Iteration : ' + str(i)  + ' Shift started')
    shift(shift_thres)
    
    #Create visisitng order
    print('Iteration : ' + str(i)  + ' Visiting order started')
    VOs = time.time()

    for j in range(len(population)):
      print('\t Visiting order : ' + str(j)  + ' out of' + str(len(population)))
      population[j].create_visiting_order()
    
    VOe = time.time()
    VisitingOrder_time_log.append(VOe-VOs)

    #selection
    #(also store the best solution in each iteration)
    #selection
    print('Iteration : ' + str(i)  + ' Selection started')
    selected = []
    top_needed = 4
    
    Bests = time.time()
    
    for j in range(top_needed):
      best = get_best_fit_solution(population)
      if(j==0):
        best_fitness.append(population[best].fitness)
      selected.append(population[best])
      population.pop(best)

    Beste= time.time()
    Best_fit_time_log.append(Beste-Bests)

    population = selected

    #clear circs
    for m in range(len(population)):
      for row in range(n_cities):
        for col in range(n_cities):
          #get num of gates in the circ
          chrom = population[m]
          index = chrom.matrix[row][col][1]
          selected_circ = chrom.quantumCircuit[index]
          num = len(selected_circ.data)
          #clear
          for id in range(num):
            selected_circ.data.pop(num-id-1)
    
    print('Iteration : ' + str(i)  + ' Last reinitialize started')
    #reinitilize
    ReReInitS = time.time()
    for j in range(len(population)):
      print('\t Last Reintialize : ' + str(j)  + ' out of' + str(len(population)))
      population[j].reinitialize()
    print('Completed ' + str(i+1) + ' out of ' + str(times))
    ReReInitE = time.time()
    ReReInit_time_log.append(ReReInitE-ReReInitS)

    end_time = time.time()

    time_log.append(end_time-start_time)
  return population

def rotation_update_state(angle,state_matrix):

  alpha = state_matrix[0]
  beta = state_matrix[1]

  incoming_total = math.pow(alpha,2) + math.pow(beta,2)
  print('\t\t\t Incoming Total ' + str(incoming_total) + 'Incoming Alpha :' +str(alpha) + ' Beta : ' + str(beta))
  if(incoming_total > 1):
    print(str(alpha) + ', ' + str(beta))
  ######################################################
  alpha_new = alpha*math.cos(angle) - beta*math.sin(angle)
  beta_new = alpha*math.sin(angle) + beta*math.cos(angle)
  
  generated_total = math.pow(alpha_new,2) + math.pow(beta_new,2)
  print('\t\t\t Generated Total ' + str(generated_total))

  return alpha_new,beta_new

"""###EXE Codes

#### Distances
"""

#distances
num_cities = 4

#city_locs = np.array([[10,10],[100,10],[150,150],[150,300],[100,400],[50,300],[10,200],[30,180]])
city_locs = np.array([[10,10],[100,10],[100,400],[50,300]])
#city cords
#city_locs = np.empty([num_cities,2])

#add data
# for i in range(num_cities):
#   city_locs[i][0] = random.randint(1,100)
#   city_locs[i][1] = random.randint(1,100)

#city location matrix init
dist_matrix = np.empty([num_cities,num_cities],float)

#calculate city distances
for i in range(num_cities):
  for j in range(num_cities):
    #start city
    x_loc = city_locs[i][0]
    y_loc = city_locs[i][1]

    #changing city
    x = city_locs[j][0]
    y = city_locs[j][1]

    dist_sqrd = math.pow((x-x_loc),2) + math.pow((y-y_loc),2)
    dist = math.sqrt(dist_sqrd)

    dist_matrix[i][j] = dist

"""### Exec

#### params
"""

popSize = 2 #population size
n_cities  = 4 #number of cities
permute_thres = 0.1
permute_prob = 0.06
iterate_counts = 2
best_fitness = []
shift_thres = 0.25

global_best_solution = None

time_log = []
fitness_time_log = []
Interfer_time_log = []
Reinit_time_log = []
Crossover_time_log = []
Permute_time_log = []
VisitingOrder_time_log = []
Best_fit_time_log = []
ReReInit_time_log = []

one_check_time_log = []
state_bin_time_log = []

clear_time_log=[]
set_states_time_log=[]

bin_time_log=[]
st_time_log=[]

select_circ_time_log=[]
exe_time_log=[]

mes_time_log = []

gen_cir = []

"""#### Running on real IBM Quantum Computer"""

population = create_population(popSize,n_cities)

start_q = time.time()
population = iterate(iterate_counts,population)
end_q = time.time()

job.error_message()
#print('Time taken : ' + str((end_q - start_q)/60))

# population[3].show_solution()

#population[0].binary_state_vector_list

"""###plots"""

sns.set()
plt.title("Best Fitness per Evolution vs Evolution")
plt.xlabel("Evolution count")
plt.ylabel("Fitness value")


plt.plot([k for k in range(len(best_fitness))],best_fitness)

# plt.legend(['Decay Function','Constant Volume'])
plt.savefig('qc-24-10-final.pdf')

#save bestfitness csv
df = pd.DataFrame(best_fitness)
csv_data = df.to_csv(index=False)
df.to_csv('qc-24-10-final.csv', index=False)

"""###other"""

plt.scatter([x+1 for x in range(len(best_fitness))],np.asarray(best_fitness),c='orange')
plt.plot([x+1 for x in range(len(best_fitness))],np.asarray(best_fitness))

plt.scatter([x+1 for x in range(len(time_log))],np.asarray(time_log),c='red')
plt.plot([x+1 for x in range(len(time_log))],np.asarray(time_log))

plt.scatter([x+1 for x in range(len(fitness_time_log))],np.asarray(fitness_time_log),c='red')
plt.plot([x+1 for x in range(len(fitness_time_log))],np.asarray(fitness_time_log))

plt.scatter([x+1 for x in range(len(Interfer_time_log))],np.asarray(Interfer_time_log),c='red')
plt.plot([x+1 for x in range(len(Interfer_time_log))],np.asarray(Interfer_time_log))

plt.scatter([x+1 for x in range(len(Reinit_time_log))],np.asarray(Reinit_time_log),c='red')
plt.plot([x+1 for x in range(len(Reinit_time_log))],np.asarray(Reinit_time_log))

plt.scatter([x+1 for x in range(len(Crossover_time_log))],np.asarray(Crossover_time_log),c='red')
plt.plot([x+1 for x in range(len(Crossover_time_log))],np.asarray(Crossover_time_log))

plt.scatter([x+1 for x in range(len(Permute_time_log))],np.asarray(Permute_time_log),c='red')
plt.plot([x+1 for x in range(len(Permute_time_log))],np.asarray(Permute_time_log))

plt.scatter([x+1 for x in range(len(VisitingOrder_time_log))],np.asarray(VisitingOrder_time_log),c='red')
plt.plot([x+1 for x in range(len(VisitingOrder_time_log))],np.asarray(VisitingOrder_time_log))

plt.scatter([x+1 for x in range(len(Best_fit_time_log))],np.asarray(Best_fit_time_log),c='red')
plt.plot([x+1 for x in range(len(Best_fit_time_log))],np.asarray(Best_fit_time_log))

plt.scatter([x+1 for x in range(len(ReReInit_time_log))],np.asarray(ReReInit_time_log),c='red')
plt.plot([x+1 for x in range(len(ReReInit_time_log))],np.asarray(ReReInit_time_log))

plt.scatter([x+1 for x in range(len(state_bin_time_log))],np.asarray(state_bin_time_log),c='red')
plt.plot([x+1 for x in range(len(state_bin_time_log))],np.asarray(state_bin_time_log))

plt.scatter([x+1 for x in range(len(one_check_time_log))],np.asarray(one_check_time_log),c='red')
plt.plot([x+1 for x in range(len(one_check_time_log))],np.asarray(one_check_time_log))

plt.scatter([x for x in range(len(clear_time_log))], np.asarray(clear_time_log),c='red')
plt.plot([x for x in range(len(clear_time_log))], np.asarray(clear_time_log))

plt.scatter([x for x in range(len(set_states_time_log))], np.asarray(set_states_time_log),c='red')
plt.plot([x for x in range(len(set_states_time_log))], np.asarray(set_states_time_log))

plt.scatter([x for x in range(len(bin_time_log))], np.asarray(bin_time_log),c='red')
plt.plot([x for x in range(len(bin_time_log))], np.asarray(bin_time_log))

plt.scatter([x for x in range(len(st_time_log))], np.asarray(st_time_log),c='red')
plt.plot([x for x in range(len(st_time_log))], np.asarray(st_time_log))



plt.scatter([x for x in range(len(select_circ_time_log))], np.asarray(select_circ_time_log),c='red')
plt.plot([x for x in range(len(select_circ_time_log))], np.asarray(select_circ_time_log))

plt.scatter([x for x in range(len(exe_time_log))], np.asarray(exe_time_log),c='red')
plt.plot([x for x in range(len(exe_time_log))], np.asarray(exe_time_log))

plt.scatter([x for x in range(len(mes_time_log))], np.asarray(mes_time_log),c='red')
plt.plot([x for x in range(len(mes_time_log))], np.asarray(mes_time_log))

for i in range(len(population)):
  print(population[i].fitness)

"""#### Unit testing"""

#create population
population = create_population(popSize,n_cities)

#create population
population = create_population(popSize,n_cities)

for times in range(5):
  #Calculate fitness
  for i in range(len(population)):
    population[i].fitness = random.randint(10,100)
  
  
  #Run Interference
  quantum_interfer(population,n_cities)

  # #Reinitialize 
  # for i in range(len(population)):
  #   population[i].reinitialize()
  
  #Crossover
  #crossover(popSize,n_cities)
  
  #Permute
  #permute(population,n_cities,permute_thres,permute_prob)

  #Create visisitng order
  # for i in range(len(population)):
  #   population[i].create_visiting_order()
  
  #selection
  selected = []
  top_needed = 4
  for i in range(top_needed):
    best = get_best_fit_solution(population)
    selected.append(population[best])
    population.pop(best)

  population = selected

  #Reinitialize 
  for i in range(len(population)):
    population[i].reinitialize()

  print('Completed ' + str(times+1) + '\n\n')

#Calculate fitness
for i in range(len(population)):
  population[i].calculate_fitness()

#print fitness
for i in range(len(population)):
  print('Pop ' + str(i) +' ' +str(population[i].fitness))

#Run Interference
quantum_interfer(population,n_cities)

population[0].quantumCircuit[5].draw()

#Reinitialize 
for i in range(len(population)):
  population[i].reinitialize()

population[0].quantumCircuit[5].draw()

# for i in range(len(population)):
#   population[i].show_solution()

#Crossover
crossover(popSize,n_cities)

#Permute
permute(population,n_cities,permute_thres,permute_prob)

#shift
shift(shift_thres)

#Create visisitng order
for i in range(len(population)):
  population[i].create_visiting_order()

#selection
selected = []
top_needed = 4
for i in range(top_needed):
  best = get_best_fit_solution(population)
  selected.append(population[best])
  population.pop(best)

population = selected

population

def iterate(times):
  for i in range(times+1):
    if(i == 0):
      #first time 
      #create population
    else:
    
    #measure

    #calc fitness

    #interfer

    #reinitialize

    #crossover

    #permute

    #shift
    #not yet done

    #measure

    #selection
    #(also store the best solution in each iteration)

population[6].plot_solution()

plt.plot([1,2,3],[1,4,9])

distances = {0:[0,2,8,4],1:[2,0,10,9],2:[8,10,0,3],3:[4,9,3,0]}

df = pd.DataFrame(distances)

df.loc[]

dist = np.array([[0,2,8,4],[2,0,10,9],[8,10,0,3],[4,9,3,0]])

dist

dist[0]

city_locs = np.array([[0,0],[10,0],[10,4],[0,4]])

city_locs

dist_matrix = np.empty([4,4],float)

dist_matrix

demo_solution = np.array([[1,0,1,0],[1,0,0,0],[0,1,0,1],[1,0,0,0]])

demo_solutin

visiting_order = []

for i in range(4):
  for j in range(4):
    val = demo_solution[i][j]
    if(val == 1):
      visiting_order.append(j)

x_points = []
y_points = []
for i in range(len(visiting_order)):
  city = visiting_order[i]
  x_points.append(city_locs[city][0])
  y_points.append(city_locs[city][1])

plt.scatter(x_points,y_points,c='red')
plt.plot(x_points,y_points)

plt.scatter(x_points,y_points,c='red')
plt.plot(x_points,y_points)

visiting_order

kk = [1,2,34]
dd = np.asarray(kk)
dd

for i in range(4):
  for j in range(4):
    #start city
    x_loc = city_locs[i][0]
    y_loc = city_locs[i][1]

    #changing city
    x = city_locs[j][0]
    y = city_locs[j][1]

    dist_sqrd = math.pow((x-x_loc),2) + math.pow((y-y_loc),2)
    dist = math.sqrt(dist_sqrd)

    dist_matrix[i][j] = dist

dist_matrix

aa = ['1','2']

bb = aa

bb[0] = 'a'

bb

aa

population

for i in range(len(population)):
  print(population[i].fitness)

population[5].show_solution()

current_state_vector = None
  binary_state_vector = None

a.measure()

a.quantumCircuit.draw()

a.calculate_fitness()

a.fitness

a.show_solution()

a.quantumCircuit.draw()

a.binary_state_vector

qc = QuantumRegister(1,'q')
cr = ClassicalRegister(1,'c')
circ = QuantumCircuit(qc,cr)

circ.h(0)
# circ.h(1)

circ.measure([0],[0])

circ.draw()

simulator = Aer.get_backend('statevector_simulator')
result = execute(circ, simulator,memory=True).result()
statevector = result.get_statevector(circ)

statevector

qr  = QuantumRegister  (4)# 1110
cr  = ClassicalRegister(4)
cc  = QuantumCircuit(qr,cr)

cc.x(qr[1]);cc.x(qr[2]);cc.x(qr[3])# 1110

for i in range(4): cc.measure(qr[i],cr[i])#read

cc.draw()

simulator = Aer.get_backend('statevector_simulator')
result = execute(cc, simulator,memory=True).result()
statevector = result.get_counts(cc)

for key in statevector:
  print(int(key)+8)

statevector.get('1110')

job=qiskit.execute(cc,backend,shots=1024)
result=job.result()
counts=result.get_counts(cc)
print(counts) # {'1110': 1024}

type(qc[0])

qc[1]

circ.clbits[0].

"""### Test Run"""

# Choose a real device to simulate
IBMQ.load_account()
provider = IBMQ.get_provider(group='open')
device = provider.get_backend('ibmq_16_melbourne')
properties = device.properties()
coupling_map = device.configuration().coupling_map

# Generate an Aer noise model for device
noise_model = noise.device.basic_device_noise_model(properties)
basis_gates = noise_model.basis_gates

# Generate a quantum circuit
qc = QuantumCircuit(2, 2)

qc.h(0)
qc.cx(0, 1)
qc.measure([0, 1], [0, 1])

# Perform noisy simulation
backend = Aer.get_backend('qasm_simulator')
job_sim = execute(qc, backend,
                  coupling_map=coupling_map,
                  noise_model=noise_model,
                  basis_gates=basis_gates)

sim_result = job_sim.result()
print(sim_result.get_counts(qc))

