Advertisement
Guest User

TSP Genetic Algorithm

a guest
Apr 6th, 2020
250
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 17.10 KB | None | 0 0
  1. import numpy as np, random, operator, math
  2. import matplotlib.pyplot as plt
  3.  
  4. '''
  5.  class City: representa cada cidade com suas localizações
  6. descritas pelas coordenadas (x, y).
  7.  - Método __init__(self, x, y): inicializa objeto da classe com as coordenadas x e y;
  8.  - Método distance(self, city): calcula a distância entre o objeto e outro da classe City;
  9.  - Método __init__(self): reproduz em coordenadas cartesianas a localização da cidade.
  10. '''
  11. class City:
  12.   def __init__(self, x, y):
  13.     self.x = x
  14.     self.y = y
  15.  
  16.   def distance(self, city):
  17.     xDis = abs(self.x - city.x)
  18.     yDis = abs(self.y - city.y)
  19.     return xDis + yDis
  20.  
  21.   def __repr__(self):
  22.     return "(" + str(self.x) + "," + str(self.y) + ")"
  23. '''
  24.  class Fitness: calcula o valor de Fitness de um caminho passando por uma sequência de cidades. Quanto menor a extensão do caminho, maior o valor de Fitness.
  25.   - Método __init__(self, route): inicializa um objeto da classe Fitness;
  26.   - Método routeDistance(self): calcula a distância total percorrida na sequência de cidades dada;
  27.   - Método routeFitness(self): calcula o valor do Fitness do caminho a partir do valor obtido no método routeDistance().
  28. '''
  29. class Fitness:
  30.   def __init__(self, route):
  31.     self.route = route
  32.     self.distance = 0
  33.     self.fitness = 0.0
  34.  
  35.   def routeDistance(self):
  36.     if self.distance == 0:
  37.       pathDistance = 0
  38.       for i in range(0, len(self.route)):
  39.         fromCity = self.route[i]
  40.         toCity = None
  41.         if i + 1 < len(self.route):
  42.           toCity = self.route[i+1]
  43.         else:
  44.           toCity = self.route[0]
  45.         pathDistance += fromCity.distance(toCity)
  46.       self.distance = pathDistance
  47.     return self.distance
  48.  
  49.   def routeFitness(self):
  50.     if self.fitness == 0.0:
  51.       self.fitness = 1/float(self.routeDistance())
  52.     return self.fitness
  53.  
  54. random.seed('seed fixa') #seed usada para os métodos do módulo random
  55.  
  56. '''
  57.  Método  inicializeCities(cityPoints): recebe uma sequência de pontos como parâmetro que representam coordenadas de cidades e retorna uma lista de objetos da classe City localizados nos pontos dados como parâmetro.
  58. '''
  59. def initializeCities(cityPoints):
  60.   cityList = []
  61.   for i in range(0, int(len(cityPoints)/2)):
  62.     cityList.append(City(cityPoints[2*i],cityPoints[2*i+1]))
  63.   return cityList
  64.  
  65. '''
  66.  Método createRoute(cityList): recebe uma lista de objetos da classe City como parâmetro e os retorna em ordem aletória.
  67.  *Cada lista com as Cidades dispostas em uma ordem é considera um Indivíduo. Cada Indivíduo é considerada uma possível solução para o TSP.
  68. '''
  69. def createRoute(cityList):
  70.   route = random.sample(cityList,len(cityList))
  71.   return route
  72.  
  73. '''
  74.  Método createPopulation(cityList, popSize): recebe uma lista de objetos da classe City (cityList) e um int que reprezenta o tamanho da população (popSize) como parâmetros. Este método retorna uma lista de Indivíduos, sendo uma sequência de cidades. O conjunto desses indiíduos é chamado População e o tamanho da lista retornada é o tamanho da Poupulação.
  75. '''
  76. def createPopulation(cityList, popSize):
  77.   firstPop = []
  78.   for i in range(0, popSize):
  79.     firstPop.append(createRoute(cityList))
  80.   return firstPop
  81.  
  82. '''
  83.  Método rankRoutes(population): recebe uma População (population) como parâmetro e retorna um dicionário com os mesmos indivíduos, porém ordenados pelo valor de Fitness de cada um em ordem decrescente. Ou seja, os indivíduos com distância total do caminho menores ficam nas posições iniciais da lista. As chaves do dicionário retornado são as posições (índices) de cada objeto na lista de cidades (population) e os valores são os seus respectivos valores de Fitness.  
  84. '''
  85. def rankRoutes(population):
  86.   fitnessResults = {}
  87.   for i in range(0, len(population)):
  88.     fitnessResults[i] = Fitness(population[i]).routeFitness()
  89.   return sorted(fitnessResults.items(), key = operator.itemgetter(1), reverse = True)
  90.  
  91.  
  92. '''
  93.  Método selection(popRanked): recebe uma lista com Indivíduos ordenados em ordem decrescente de Fitness (popRanked) como parâmetro e retorna os índices dos Indivíduos que farão Mutação e Cruzamento para formar a próxima Geração.
  94. '''
  95. def selection(popRanked):
  96.   selectionResults = [] #inicializa a lista dos Indivíduos da próxima Geração
  97.  
  98.   '''
  99.    Soma os valores de Fitness de todos os Indivíduos (totalSum) e faz uma lista com as somas cumulativas dos Fitness até a posição de cada Indivíduo na lista Indivíduos ordenados por seus Fitness (cumSum[]).
  100.  '''
  101.   totalSum = 0
  102.   cumSum = []
  103.   for i in range(0, len(popRanked)):
  104.     totalSum += popRanked[i][1]
  105.     cumSum.append(totalSum)
  106.  
  107.   '''
  108.    Faz uma lista que representa o percentual de Fitness de cada Indivíduo (perc[]) sobre a soma dos Fitness de todos da População (totalSum) e outra com a soma cumulativa de seus percentuais (percSum[]).
  109.  '''
  110.   perc = []
  111.   percSum = []
  112.   for i in range(0, len(popRanked)):
  113.     perc.append(popRanked[i][1]/totalSum)
  114.     percSum.append(cumSum[i]/totalSum)  
  115.  
  116.   '''
  117.    Decide quantas vezes cada Indivíduo vai aparecer na lista dos Indivíduos que vão gerar a próxima Geração (selectionResults[]).
  118.    Considerando que perc[i]*len(popRanked) = n*1% + r, sendo n um número inteiro >= 0 e r um número decimal menor que 1, o número de vezes que um Indivíduo vai aparecer (selQuan[i]) na lista é n. Para todas as vezes em que n for igual a zero, o Indivíduo será selecionado aleatoriamente com chances proporcionais ao valor de seus Fitness (perc[i]).
  119.  '''
  120.   rest = 0
  121.   selQuant = []
  122.   for i in range(0, len(popRanked)):
  123.     selQuant.append(math.floor(perc[i]*len(popRanked)))    
  124.     if selQuant[i] == 0:
  125.       rest += 1
  126.  
  127.   '''
  128.    Se n >= 1, coloca os Indivíduos n vezes na lista da próxima Geração (selectionResults[]).
  129.  '''
  130.   for i in range(0, len(popRanked) - rest):
  131.     for j in range(0, selQuant[i]):
  132.       selectionResults.append(popRanked[i][0])
  133.  
  134.   '''
  135.    Seleciona os indivíduos que vão gerar a próxima Geração (selectionResults[]) que faltam aleatoriamente, com o chances proporcionais aos valores de seus Fitness.
  136.  '''  
  137.   for i in range(len(popRanked) - rest, len(popRanked)):
  138.     pick = random.random()
  139.     for i in range(0, len(perc)):
  140.       if pick < percSum[i]:
  141.         selectionResults.append(popRanked[i][0])
  142.         break
  143.   return selectionResults #retorna os índices dos Indivíduos que vão ser Cruzados e Mutados para formar a próxima Geração.
  144.  
  145. '''
  146.  Método matingPool(population, selectionResults): dados os índices de cada elemento na lista da População atual (selectionResults[i]) e a própria População atual (population[]), retorna a lista com os Índivíduos que formarão a próxima Geração.
  147. '''
  148. def matingPool(population, selectionResults):
  149.   matingPool = []
  150.   for i in range(0, len(selectionResults)):
  151.     index = selectionResults[i]
  152.     matingPool.append(population[index])
  153.   return matingPool
  154.  
  155. '''
  156.  Método breed(parent1, parent2): Recebe dois Indivíduos (parent1[] & parent2[]) como parâmetros e retorna um terceiro Indivíduo considerado filho dos dois anteriores (child[]). Este método é chamado de Cruzamento. É importante ressaltar que nenhuma cidade (objeto da classe City) presente nas listas de parent[1] e parent[2] é excluído da lista de child[], de mesma forma, nenhum objeto aparece mais de uma vez em child[].
  157. '''
  158. def breed(parent1, parent2):
  159.   '''
  160.    Inicializa a lista do Indivíduo filho (child[]), dos elementos de parent1 que vão ser herdados pelo filho (childP1[]) e dos elementos de parent2 que serão herdados pelo filho (childP2[]).
  161.  '''
  162.   child = []
  163.   childP1 = []
  164.   childP2 = []
  165.  
  166.   '''
  167.    Seleciona de forma aleatória os elementos de início (startGene) e de fim (endGene) na lista de elementos de parent1 que serão herdados pelo filho e os coloca na lista que o filho herdará de parent1[] (childP1[])
  168.  '''
  169.   geneA = int(random.random()*len(parent1))  
  170.   geneB = int(random.random()*len(parent1))
  171.   startGene = min(geneA, geneB)
  172.   endGene = max(geneA, geneB)
  173.   for i in range(startGene, endGene):        
  174.     childP1.append(parent1[i])
  175.  
  176.   '''
  177.    Adiciona na lista dos elementos herdados de parent2[] (childP2[]) todos os elementos que estão em parent2[] e não estão em parent1[]
  178.  '''
  179.   childP2 = [item for item in parent2 if item not in childP1]
  180.  
  181.   '''
  182.    Forma e retorna a lista do Indivíduo filho (child[])
  183.  '''
  184.   child = childP1 + childP2
  185.   return child
  186.  
  187. '''
  188.  Método breedPopulation(matingpool, breedingRate, eliteSize): recebe a lista de Indivíduos que serão cruzados para formar a prócima Geração (matingpool), a Taxa de Cruzamento (breedingRate) e o tamanho da Elite (eliteSize) como parâmetros e retorna a lista com os Indivíduos filhos (children[]). Taxa de Cruzamento representa as chances de haver Cruzamento entre dois Indivídos. Se não houver cruzamento, o Indivíduo aparecerá idêntico na lista dos filhos (children[]). Tamanho da Elite representa o número de elementos da lista de Indivíduos que não serão cruzados e serão passados diretamente para a próxima Geração. Isso acontece com os indivíduos com os melhores valores de Fitness.
  189. '''
  190. def breedPopulation(matingpool, breedingRate, eliteSize):
  191.   children = [] #inicializa a lista dos Indivíduos filhos
  192.  
  193.   '''
  194.    Insere os Indivíduos mais bem qualificados da População atual na lista dos filhos
  195.  '''  
  196.   for i in range(0, eliteSize):
  197.     children.append(matingpool[i])
  198.    
  199.   pool = random.sample(matingpool, len(matingpool)) #coloca os Indivíduos da População atual em ordem aleatória.
  200.  
  201.   '''
  202.    Faz o Cruzamento dos Indivíduos de acordo com as chances previstas na Taxa de Cruzamento (breedingRate) e retorna a População dos filhos completa (children[])
  203.  '''  
  204.   length = len(matingpool) - eliteSize
  205.   for i in range(0, length):
  206.     if random.random() < breedingRate:
  207.       child = breed(pool[i], pool[len(matingpool) - i - 1])
  208.     else:
  209.       child = pool[i]
  210.     children.append(child)
  211.   return children
  212.  
  213. '''
  214.  Método mutate(individual, mutationRate): recebe um objeto da classe City (individual) e a Taxa de Mutação (mutationRate) como parâmetros e retorna o Indivíduo (individual), tendo este sofrido a Mutação ou não. As chances de ele sofrer a Mutação são definidas pela Taxa de Mutação. Neste processo, dois Genes (objetos na classe City) aleatórios são trocados de lugar.
  215. '''  
  216. def mutate(individual, mutationRate):
  217.   if random.random() < mutationRate:
  218.     swapped = int(random.random()*len(individual))
  219.     swapWith = int(random.random()*len(individual))
  220.  
  221.     city1 = individual[swapped]
  222.     city2 = individual[swapWith]
  223.    
  224.     individual[swapped] = city2
  225.     individual[swapWith] = city1  
  226.   return individual
  227.  
  228. '''
  229.  Método mutatePopulation(population, mutationRate, eliteSize): recebe a População atual (population), a Taxa de Mutação (mutationRate) e o Tamanho da Elite (eliteSize) como parâmetros e realiza o processo de Mutação em toda a População de acordo com a Taxa de Mutação (mutationRate) e o tamanho da Elite (eliteSize), até retornar a nova População com os Genes trocados de lugar nos Indivíduos que sofreram a Mutação (mutatedPop[]). Taxa de Mutação representa as chances de havar Mutação em cada Indivíduo e Tamanho da Elite represente a quantidade de Indivíduos, os quais possuem os valores de Fitness mais altos da População atual, que não têm chances de sofrer Mutação.
  230. '''
  231. def mutatePopulation(population, mutationRate, eliteSize):
  232.   mutatedPop = []
  233.  
  234.   for i in range(0, eliteSize):
  235.     mutatedPop.append(population[i])  
  236.  
  237.   for i in range(eliteSize, len(population)):
  238.     mutatedInd = mutate(population[i], mutationRate)
  239.     mutatedPop.append(mutatedInd)
  240.   return mutatedPop
  241.  
  242. '''
  243.  Método nextGeneration(currentGen, breedingRate, mutationRate, eliteSize): recebe a Geração Atual (currentGen), Taxa de Cruzamento (breedingRate), Taxa de Mutação (mutationRate) e tamanho da Elite (eliteSize) como parâmetros e aplica os métodos descritos acima para retornar a próxima Geração (nextGeneration)
  244. '''
  245. def nextGeneration(currentGen, breedingRate, mutationRate, eliteSize):
  246.   popRanked = rankRoutes(currentGen)
  247.   selectionResults = selection(popRanked)
  248.   matingpool = matingPool(currentGen, selectionResults)
  249.   children = breedPopulation(matingpool, breedingRate, eliteSize)
  250.   nextGeneration = mutatePopulation(children, mutationRate, eliteSize)
  251.   return nextGeneration
  252.  
  253. '''
  254.  Método geneticAlgotithm(cityList, popSize, breedingRate, mutationRate, eliteSize, generations): recebe uma lista de objetos da classe City (cityList), o número de Indivíduos de cada Geração (popSize), a Taxa de Cruzamento em cada Geração (breedingRate), a Taxa de Mutação em cada Geração (mutationRate), o tamanho da Elite de cada Geração (eliteSize) e o número de Gerações (generations) da execução do Algoritmo Genético como parâmetros e executa o Algoritmo em si. O Algoritmo Genético retorna o melhor caminho encontrado em todas as iterações realizadas (bestRoute). O melhor caminho é a sequência de cidades que obtiveram o maior valor de Fitness e portanto, a menor distância total entre elas.
  255. '''
  256. def geneticAlgotithm(cityList, popSize, breedingRate, mutationRate, eliteSize, generations):
  257.   pop = createPopulation(cityList, popSize) # Iicializa uma população com as cidades dadas
  258.   shortest = [] # Inicializa a lista das menores distâncias obtidas em cada geração
  259.   mean = [] # Inicializa lista da média das distâncias obtidas em cada geração
  260.   startDistance = Fitness(pop[rankRoutes(pop)[0][0]]).routeDistance() # Calcula a menor distância da primeira Geração
  261.   shortest.append(startDistance) # Insere o primeiro elemento na lista de menores distâncias de cada Geração
  262.   v = [] # Inicializa a lista com as distâncias da primeira Geração, o loop abaixo a preenche
  263.   for i in range(0, len(pop)):
  264.     v.append(Fitness(pop[i]).routeDistance())
  265.   mean.append(np.mean(v)) # Calcula a média das distâncias da primeira Geração e insere na lista das médias
  266.   bestDistance = startDistance # Salva a menor distância obtida entre todos Indivíduos de todas as Gerações
  267.   bestRoute = pop[rankRoutes(pop)[0][0]] # Salva o caminho
  268.    
  269.   gen = 0 # Inicializa a contagem de Gerações
  270.   while gen <= generations: # Realiza o loop pelo número de Gerações determinado
  271.     pop = nextGeneration(pop, breedingRate, mutationRate, eliteSize) # Obtém a próxima Geração
  272.     rankedRoutes = rankRoutes(pop) # Coloca os Indivíduos da Geração atual em ordem decrescente de Fitness
  273.     m = [] # Inicializa a lista com as distâncias da Geração atual, o loop abaixo a preenche  
  274.     for i in range(0, len(pop)):
  275.       m.append(Fitness(pop[rankedRoutes[i][0]]).routeDistance())
  276.     media = np.mean(m) # Calcula a média das distâncias da Geração atual
  277.     distance = Fitness(pop[rankedRoutes[0][0]]).routeDistance() # Calcula a distância total do melhor Indivíduo da Geração atual
  278.     print("Best Distance: " + str(distance) + "  Média: " + str(media)) # Imprime a menor distância total da Geração atual
  279.     if distance < bestDistance: # Salva a melhor distância total e melhor caminho entre todas as Gerações, caso estes superem o melhor obtido até agora
  280.       bestDistance = distance
  281.       bestRoute = pop[rankRoutes(pop)[0][0]].copy()    
  282.     shortest.append(distance) # Insere a menor distância atual na lista de menores distâncias
  283.     mean.append(media) # Insere a média das distâncias da Geração atual na lista das médias  
  284.     gen += 1 # Continua a contagem de Gerações
  285.   '''
  286.    Imprime os resultados do Algoritmo Genético e salva os de  
  287.  '''
  288.   print("\nStart Distance: " + str(startDistance) + "\nFinal Distance: "
  289.         + str(distance) + "\nBest Distance: " + str(bestDistance)
  290.         + "\n" + str(generations) + " generations.\n")
  291.   plt.plot(shortest)
  292.   plt.ylabel('Distance')
  293.   plt.xlabel('Generation')
  294.   plt.grid(b = True, which = 'major', axis = 'both')
  295.   plt.savefig("shortest_TC1TM1.png", quality = 95)
  296.   plt.figure()
  297.   plt.plot(mean)
  298.   plt.ylabel('Média das Distâncias')
  299.   plt.xlabel('Generation')
  300.   plt.grid(b = True, which = 'major', axis = 'both')
  301.   plt.savefig("mean_TC1TM1.png", quality = 95)
  302.   return bestRoute
  303.  
  304. cityPoints = [19, 39, 9, 93, 35, 84, 14, 91, 66, 93, 3, 78,
  305.           93, 72, 7, 40, 99, 79, 82, 12, 78, 91, 42, 44,
  306.           97, 63, 93, 81, 61, 85, 87, 35, 22, 82, 11, 70,
  307.           17, 4, 88, 16, 32, 8, 53, 64, 47, 28, 68, 83, 40,
  308.           17, 93, 53, 47, 9, 63, 20, 66, 67, 63, 3, 78, 50,
  309.           71, 33, 76, 64, 15, 18, 60, 8, 50, 77, 12, 80, 94,
  310.           36, 2, 94, 65, 5, 86, 46, 46, 11, 39, 25, 80, 29,
  311.           51, 31]
  312.  
  313. cities = initializeCities(cityPoints)
  314.  
  315. # Para rodar o segundo caso é necessário comentar a linha referente ao Caso 1 e descomentar a linha referente ao Caso 2
  316. # Caso 1
  317. bestRoute = geneticAlgotithm(cityList = cities, popSize = 100, breedingRate = 0.55, mutationRate = 0.12, eliteSize = 10, generations = 2000)
  318.  
  319. # Caso 2
  320. # bestRoute = geneticAlgotithm(cityList = cities, popSize = 100, breedingRate = 0.43, mutationRate = 0.65, eliteSize = 10, generations = 2000)
  321.  
  322. print(bestRoute)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement