├── FUNDING.yml ├── __pycache__ ├── ant.cpython-39.pyc ├── point.cpython-39.pyc ├── points.cpython-37.pyc ├── points.cpython-39.pyc ├── utils.cpython-39.pyc ├── antColony.cpython-39.pyc ├── genetic.cpython-39.pyc ├── manager.cpython-39.pyc └── setting.cpython-39.pyc ├── UI ├── __pycache__ │ ├── ui.cpython-39.pyc │ └── setup.cpython-39.pyc ├── setup.py └── ui.py ├── point.py ├── README.md ├── utils.py ├── ant.py ├── genetic.py ├── main.py ├── antColony.py └── manager.py /FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: auctux 2 | ko_fi: auctux 3 | -------------------------------------------------------------------------------- /__pycache__/ant.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Traveling-Salesman-Algorithm/HEAD/__pycache__/ant.cpython-39.pyc -------------------------------------------------------------------------------- /UI/__pycache__/ui.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Traveling-Salesman-Algorithm/HEAD/UI/__pycache__/ui.cpython-39.pyc -------------------------------------------------------------------------------- /__pycache__/point.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Traveling-Salesman-Algorithm/HEAD/__pycache__/point.cpython-39.pyc -------------------------------------------------------------------------------- /__pycache__/points.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Traveling-Salesman-Algorithm/HEAD/__pycache__/points.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/points.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Traveling-Salesman-Algorithm/HEAD/__pycache__/points.cpython-39.pyc -------------------------------------------------------------------------------- /__pycache__/utils.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Traveling-Salesman-Algorithm/HEAD/__pycache__/utils.cpython-39.pyc -------------------------------------------------------------------------------- /UI/__pycache__/setup.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Traveling-Salesman-Algorithm/HEAD/UI/__pycache__/setup.cpython-39.pyc -------------------------------------------------------------------------------- /__pycache__/antColony.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Traveling-Salesman-Algorithm/HEAD/__pycache__/antColony.cpython-39.pyc -------------------------------------------------------------------------------- /__pycache__/genetic.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Traveling-Salesman-Algorithm/HEAD/__pycache__/genetic.cpython-39.pyc -------------------------------------------------------------------------------- /__pycache__/manager.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Traveling-Salesman-Algorithm/HEAD/__pycache__/manager.cpython-39.pyc -------------------------------------------------------------------------------- /__pycache__/setting.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Traveling-Salesman-Algorithm/HEAD/__pycache__/setting.cpython-39.pyc -------------------------------------------------------------------------------- /UI/setup.py: -------------------------------------------------------------------------------- 1 | from UI.ui import * 2 | from manager import width, height 3 | 4 | panel = Panel() 5 | 6 | PauseButton = Button("Pause", (width - 270, 180), 150, 50, 0, (10, 10, 30), (255, 255, 255)) 7 | ResetButton = Button("Reset", (width - 270, 240), 150, 50, 0, (10, 10, 30), (255, 255, 255)) 8 | RandomButton = Button("Generate", (width - 270, 300), 150, 50, 0, (10, 10, 30), (255, 255, 255)) 9 | 10 | AlgorithmChoice = DropDownButton("Select", (width - 350, 400), 300, 50, 6, 2, (10, 10, 30), (255, 255, 255)) 11 | AlgorithmChoice.childs[0].text = "Brute Force" 12 | AlgorithmChoice.childs[1].text = "Lexicographic Order" 13 | AlgorithmChoice.childs[2].text = "Genetic Algorithm" 14 | AlgorithmChoice.childs[3].text = "Ant Colony ACS" 15 | AlgorithmChoice.childs[4].text = "AntC Elitist" 16 | AlgorithmChoice.childs[5].text = "AntC Max-Min" 17 | AlgorithmChoice.currentIndex = 2 18 | -------------------------------------------------------------------------------- /point.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | 3 | pygame.font.init() 4 | textColor = (0, 0, 0) 5 | # textFont = pg.font.Font("freesansbold.ttf", size) 6 | textFont = pygame.font.SysFont("Arial", 20) 7 | 8 | class Point: 9 | def __init__(self, x, y): 10 | self.x = x 11 | self.y = y 12 | self.radius = 1 13 | self.alpha = 150 14 | 15 | def Draw(self, manager, showIndex=False, highlight=False, point_index=0): 16 | surface = pygame.Surface((self.radius *2, self.radius*2), pygame.SRCALPHA, 32) 17 | 18 | if highlight: 19 | r, g, b = manager.White 20 | pygame.draw.circle(surface, (r, g, b, 255), (self.radius, self.radius), self.radius) 21 | pygame.draw.circle(surface, (r, g, b, 255), (self.radius, self.radius), self.radius, 1) 22 | 23 | 24 | manager.screen.blit(surface, (int(self.x-self.radius), int(self.y-self.radius))) 25 | 26 | if showIndex: 27 | textSurface = textFont.render(str(point_index), True, textColor) 28 | textRectangle = textSurface.get_rect(center=(self.x, self.y)) 29 | manager.screen.blit(textSurface, textRectangle) 30 | 31 | def GetTuple(self): 32 | return (self.x, self.y) 33 | 34 | def __repr__(self): 35 | return f"({self.x}, {self.y})" 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Traveling-Salesman-Algorithm 2 | Subscribe to My youtube Channel : [Auctux](https://www.youtube.com/c/Auctux) 3 | --- 4 | Traveling Salesman using 3 algorithms: 5 | - Brute force 6 | - Random 7 | - Lexicographical order 8 | - genetic algorithm 9 | - Ant Colony optimization 10 | - Elitist 11 | - Max-Min 12 | ## Requirements 13 | > **Pygame** : pip install pygame 14 | 15 | --- 16 | ## Controls 17 | - **Esc** To close the window 18 | - **Space** To Pause and Start Simulation 19 | - **Enter or Return** To toggle the UI Panel 20 | --- 21 | ```python:main.py 22 | # Brute force solution , just by using going through all the possible combination 23 | # with a random function until we find the shortest distance 24 | manager.BruteForce() 25 | # using Lexicographical order to solve the problem by going in order into all the possible routes 26 | manager.Lexicographic() 27 | # using genetic algorithm to find the fittest one 28 | manager.GeneticAlgorithm() 29 | # using AntColonyOptimization(name:String) 30 | manager.AntColonyOptimization("ACS") # or "ELITIST" or "MAX-MIN" 31 | 32 | ``` 33 | --- 34 | ![Screenshot (240)](https://user-images.githubusercontent.com/48150537/136697477-262bc770-9986-44ba-9441-7ea6964fb487.png) 35 | --- 36 | ## Unsolved Issues & Bugs 37 | The code is a little bit redundant , it's need some refactoring. 38 | And the ui also need some retouch 39 | --- 40 | ![Screenshot (241)](https://user-images.githubusercontent.com/48150537/136697483-3936c2de-323b-474a-95f8-7976a9447a96.png) 41 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | from math import sqrt 2 | from random import randint, uniform 3 | 4 | def Distance(a, b): 5 | return sqrt( (b.x-a.x)*(b.x-a.x) + (b.y-a.y)*(b.y-a.y) ) 6 | 7 | def SumDistance(points): 8 | s = 0 9 | for i in range(len(points)): 10 | dist = Distance(points[i], points[(i+1) % len(points)]) 11 | s += dist 12 | return s 13 | 14 | def PickSelection(myList, probabilities): 15 | i = 0 16 | r = uniform(0, 1) 17 | 18 | while r > 0: 19 | r -= probabilities[i] 20 | i += 1 21 | i -= 1 22 | return myList[i].copy() 23 | 24 | def LexicalOrder(orderList): 25 | x = -1 26 | y = -1 27 | 28 | # Step 1 : Find the largest x such that Order[x] parameter used to control the importance of the pheromone trail 16 | beta -> parameter used to control the heuristic information during selection 17 | """ 18 | self.edges = edges 19 | self.tour = None 20 | self.alpha = alpha 21 | self.beta = beta 22 | self.n_nodes = n_nodes 23 | self.distance = 0.0 24 | 25 | def NodeSelection(self): 26 | """ 27 | Constructing solution 28 | an ant will often follow the strongest 29 | pheromone trail when constructing a solution. 30 | 31 | state -> is a point on a graph or a City 32 | 33 | Here, an ant would be selecting the next city depending on the distance 34 | to the next city, and the amount of pheromone on the path between 35 | the two cities. 36 | """ 37 | roulette_wheel = 0 38 | states = [node for node in range(self.n_nodes) if node not in self.tour] 39 | heuristic_value = 0 40 | for new_state in states: 41 | heuristic_value += self.edges[self.tour[-1]][new_state].heuristic 42 | for new_state in states: 43 | A = math.pow(self.edges[self.tour[-1]][new_state].pheromone, self.alpha) 44 | B = math.pow((heuristic_value/self.edges[self.tour[-1]][new_state].heuristic), self.beta) 45 | roulette_wheel += A * B 46 | random_value = random.uniform(0, roulette_wheel) 47 | wheel_position = 0 48 | for new_state in states: 49 | A = math.pow(self.edges[self.tour[-1]][new_state].pheromone, self.alpha) 50 | B = math.pow((heuristic_value/self.edges[self.tour[-1]][new_state].heuristic), self.beta) 51 | wheel_position += A * B 52 | if wheel_position >= random_value: 53 | return new_state 54 | 55 | def UpdateTour(self): 56 | self.tour = [random.randint(0, self.n_nodes - 1)] 57 | while len(self.tour) < self.n_nodes: 58 | self.tour.append(self.NodeSelection()) 59 | return self.tour 60 | 61 | def CalculateDistance(self): 62 | self.distance = 0 63 | for i in range(self.n_nodes): 64 | self.distance += self.edges[self.tour[i]][self.tour[(i+1)%self.n_nodes]].heuristic 65 | return self.distance 66 | -------------------------------------------------------------------------------- /genetic.py: -------------------------------------------------------------------------------- 1 | from utils import * 2 | from random import randint 3 | 4 | class Genetic: 5 | def __init__(self, population=[], populationSize=0): 6 | self.population = population 7 | self.size = populationSize 8 | self.fitness = [0 for i in range(populationSize)] 9 | self.record = float("inf") 10 | self.currentDist = float("inf") 11 | self.current = None 12 | self.fitest = [] 13 | self.fitestIndex = 0 14 | self.mutation_rate = 0.01 15 | 16 | def CalculateFitness(self, points): 17 | for i in range(self.size): 18 | nodes = [] 19 | for j in self.population[i]: 20 | nodes.append(points[j]) 21 | #print(nodes) 22 | dist = SumDistance(nodes) 23 | if dist < self.currentDist: 24 | self.current = self.population[i] 25 | 26 | if dist < self.record : 27 | self.record = dist 28 | self.fitest = self.population[i] 29 | self.fitestIndex = i 30 | #print(f"Shortest distance: {dist}") 31 | self.fitness[i] = 1/ (dist+1) 32 | self.NormalizeFitnesss() 33 | 34 | def NormalizeFitnesss(self): 35 | s = 0 36 | for i in range(self.size): 37 | s += self.fitness[i] 38 | for i in range(self.size): 39 | self.fitness[i] = self.fitness[i]/s 40 | 41 | def Mutate(self, genes): 42 | for i in range(len(self.population[0])): 43 | if (randint(0, 100)/100) < self.mutation_rate: 44 | a = randint(0, len(genes)-1) 45 | b = randint(0, len(genes)-1) 46 | genes[a], genes[b] = genes[b], genes[a] 47 | 48 | def CrossOver(self, genes1, genes2): 49 | start = randint(0, len(genes1)-1) 50 | end = randint(start-1, len(genes2)-1) 51 | try: 52 | end = randint(start+1, len(genes2)-1) 53 | except: 54 | pass 55 | new_genes = genes1[start:end] 56 | for i in range(len(genes2)): 57 | p = genes2[i] 58 | if p not in new_genes: 59 | new_genes.append(p) 60 | return new_genes 61 | 62 | def NaturalSelection(self): 63 | nextPopulation = [] 64 | for i in range(self.size): 65 | generation1 = PickSelection(self.population, self.fitness) 66 | generation2 = PickSelection(self.population, self.fitness) 67 | genes = self.CrossOver(generation1, generation2) 68 | self.Mutate(genes) 69 | nextPopulation.append(genes) 70 | self.population = nextPopulation 71 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from point import * 3 | from manager import * 4 | from random import randint 5 | from UI.setup import * 6 | from utils import SumDistance 7 | 8 | pygame.init() 9 | 10 | manager = Manager() 11 | antColonyTypes = ["ACS", "ELITIST", "MAX-MIN"] 12 | # manager.ChangeAntColonyVariation("ELITIST") 13 | 14 | selectedIndex = 2 15 | 16 | pause = True 17 | started = False 18 | rightMouseClicked = False 19 | GenerateToggle = False 20 | reset = False 21 | 22 | PauseButton.state = pause 23 | ResetButton.state = reset 24 | RandomButton.state = GenerateToggle 25 | 26 | showUI = False 27 | run = True 28 | while run: 29 | manager.Background() 30 | 31 | delta_time = manager.SetFps() 32 | manager.UpdateCaption() 33 | 34 | # handle Events 35 | for event in pygame.event.get(): 36 | if event.type == pygame.QUIT: 37 | run = False 38 | if event.type == pygame.KEYDOWN: 39 | if event.key == pygame.K_ESCAPE: 40 | run = False 41 | if event.key == pygame.K_SPACE: 42 | pause = not pause 43 | started = True 44 | if event.key == pygame.K_RETURN: 45 | showUI = not showUI 46 | 47 | if event.type == pygame.MOUSEBUTTONDOWN: 48 | if event.button == 1: 49 | rightMouseClicked = True 50 | 51 | 52 | # Choose one method between the 3 below: bruteForce, lexicagraphic order, genetic algorithm 53 | if selectedIndex == 0: 54 | if pause == False: 55 | manager.BruteForce() 56 | manager.DrawPoints() 57 | manager.DrawShortestPath() 58 | # manager.Percentage(manager.PossibleCombinations) 59 | elif selectedIndex == 1: 60 | if pause == False: 61 | manager.Lexicographic() 62 | manager.DrawPoints() 63 | manager.DrawShortestPath() 64 | manager.Percentage(manager.PossibleCombinations) 65 | elif selectedIndex == 2: 66 | if pause == False: 67 | manager.GeneticAlgorithm() 68 | manager.DrawPoints() 69 | manager.DrawShortestPath() 70 | else: 71 | manager.AntColonyOptimization(pause) 72 | # print(selectedIndex-3) 73 | manager.ChangeAntColonyVariation(antColonyTypes[selectedIndex-3]) 74 | manager.Percentage(iterations) 75 | 76 | manager.ShowText(selectedIndex, started) 77 | 78 | # UI 79 | if showUI: 80 | panel.Render(manager.screen) 81 | AlgorithmChoice.Render(manager.screen, rightMouseClicked) 82 | if pause != PauseButton.state: 83 | PauseButton.state = pause 84 | 85 | PauseButton.Render(manager.screen, rightMouseClicked) 86 | ResetButton.Render(manager.screen, rightMouseClicked) 87 | RandomButton.Render(manager.screen, rightMouseClicked) 88 | 89 | pause = PauseButton.state 90 | reset = ResetButton.state 91 | 92 | if reset == True: 93 | reset = False 94 | ResetButton.state = False 95 | temp = manager.Points.copy() 96 | manager = Manager(temp) 97 | manager.OptimalRoutes = manager.Points.copy() 98 | manager.recordDistance = SumDistance(manager.Points) 99 | manager.ResetAntColony(manager.antColony.variation) 100 | manager.ResetGenetic() 101 | 102 | GenerateToggle = RandomButton.state 103 | if GenerateToggle == True: 104 | manager.RandomPoints() 105 | GenerateToggle = False 106 | RandomButton.state = False 107 | 108 | if pause == True: 109 | PauseButton.text = "Continue" 110 | else: 111 | PauseButton.text = "Pause" 112 | 113 | if rightMouseClicked: 114 | selectedIndex = AlgorithmChoice.currentIndex 115 | 116 | 117 | # point scale animation increment 118 | manager.scaler += 1 119 | if manager.scaler > manager.max_radius: 120 | manager.scaler = manager.max_radius 121 | 122 | pygame.display.flip() 123 | rightMouseClicked = False 124 | pygame.quit() 125 | -------------------------------------------------------------------------------- /antColony.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from ant import * 3 | from utils import translateValue 4 | pygame.font.init() 5 | textColor = (0, 0, 0) 6 | # textFont = pg.font.Font("freesansbold.ttf", size) 7 | textFont = pygame.font.SysFont("Arial", 20) 8 | 9 | class AntColony(object): 10 | def __init__(self, variation="ACS", size=5, elitist_weight=1.0, minFactor=0.001, alpha=1.0, beta=3.0, 11 | rho=0.1, phe_deposit_weight=1.0, pheromone=1.0, max_iterations=100, nodes=None, labels=None): 12 | self.variation = variation 13 | self.size = size 14 | self.elitist_weight = elitist_weight 15 | self.minFactor = minFactor 16 | self.alpha = alpha 17 | self.rho = rho 18 | self.phe_deposit_weight = phe_deposit_weight 19 | self.max_iterations = max_iterations 20 | self.n_nodes = len(nodes) 21 | self.nodes = nodes 22 | self.edges = [[None for j in range(self.n_nodes)] for i in range(self.n_nodes)] 23 | for x in range(self.n_nodes): 24 | for y in range(self.n_nodes): 25 | heuristic = math.sqrt( 26 | math.pow(self.nodes[x].x-self.nodes[y].x, 2) + 27 | math.pow(self.nodes[x].y-self.nodes[y].y, 2) 28 | ) 29 | self.edges[x][y] = self.edges[y][x] = Edge(x, y, heuristic, pheromone) 30 | self.ants = [Ant(self.edges, alpha, beta, self.n_nodes) for i in range(self.size)] 31 | 32 | # global Best route 33 | self.best_tour = [] 34 | self.best_distance = float("inf") 35 | 36 | self.local_best_route = [] 37 | self.local_best_distance = float("inf") 38 | 39 | def AddPheromone(self, tour, distance, heuristic=1): 40 | pheromone_to_add = self.phe_deposit_weight / distance 41 | for i in range(self.n_nodes): 42 | self.edges[tour[i]][tour[(i + 1) % self.n_nodes]].pheromone += heuristic 43 | 44 | def ACS(self): 45 | # for step in range(self.max_iterations): 46 | for ant in self.ants: 47 | self.AddPheromone(ant.UpdateTour(), ant.CalculateDistance()) 48 | if ant.distance < self.best_distance: 49 | self.best_tour = ant.tour 50 | self.best_distance = ant.distance 51 | 52 | for x in range(self.n_nodes): 53 | for y in range(x + 1, self.n_nodes): 54 | self.edges[x][y].pheromone *= (1 - self.rho) 55 | 56 | def ELITIST(self): 57 | """ 58 | In elitist ACO systems, either the best current, or global best ant, 59 | deposits extra pheromone during it's local pheromone update procedure. 60 | This encourages the colony to refine it's search around solutions 61 | which have a track record of being high quality. If all goes well, 62 | this should result in better search performance. 63 | """ 64 | # for step in range(self.max_iterations): 65 | for ant in self.ants: 66 | self.AddPheromone(ant.UpdateTour(), ant.CalculateDistance()) 67 | if ant.distance < self.best_distance: 68 | self.best_tour = ant.tour 69 | self.best_distance = ant.distance 70 | self.AddPheromone(self.best_tour, self.best_distance, self.elitist_weight) 71 | for x in range(self.n_nodes): 72 | for y in range(self.n_nodes): 73 | self.edges[x][y].pheromone *= (1-self.rho) 74 | 75 | def MAX_MIN(self, counter): 76 | """ 77 | The MaxMin algorithm is similar to the elitist ACO algorithm in that 78 | it gives preference to high ranking solutions. 79 | However, in MaxMin instead of simply giving extra weight to elite solutions, 80 | only the best current, or best global solution, is allowed to deposit a pheromone trail. 81 | """ 82 | _best_distance = float("inf") 83 | _best_tour = None 84 | for ant in self.ants: 85 | ant.UpdateTour() 86 | if ant.CalculateDistance() < _best_distance: 87 | _best_tour = ant.tour 88 | _best_distance = ant.distance 89 | if (counter + 1) / self.max_iterations <= 0.75: 90 | self.AddPheromone(_best_tour, _best_distance) 91 | max_pheromone = self.phe_deposit_weight / _best_distance 92 | else: 93 | if _best_distance < self.best_distance: 94 | self.best_tour = _best_tour 95 | self.best_distance = _best_distance 96 | self.AddPheromone(self.best_tour, self.best_distance) 97 | max_pheromone = self.phe_deposit_weight / self.best_distance 98 | min_pheromone = max_pheromone * self.minFactor 99 | 100 | for x in range(self.n_nodes): 101 | for y in range(1+x, self.n_nodes): 102 | self.edges[x][y].pheromone *= (1 - self.rho) 103 | if self.edges[x][y].pheromone > max_pheromone: 104 | self.edges[x][y].pheromone = max_pheromone 105 | elif self.edges[x][y].pheromone < min_pheromone: 106 | self.edges[x][y].pheromone = min_pheromone 107 | self.local_best_route = _best_tour 108 | self.local_best_distance = _best_distance 109 | 110 | def Simulate(self, counter): 111 | if self.variation == "ACS": 112 | self.ACS() 113 | elif self.variation == "ELITIST": 114 | self.ELITIST() 115 | elif self.variation == "MAX-MIN": 116 | self.MAX_MIN(counter) 117 | 118 | def Draw(self, manager): 119 | # Draw Best Routes 120 | for i in range(len(self.best_tour)): 121 | a = self.nodes[self.best_tour[i]] 122 | b = self.nodes[self.best_tour[(i+1) % len(self.best_tour)]] 123 | pygame.draw.line(manager.screen, manager.Highlight, a.GetTuple(), b.GetTuple(), manager.LineThickness) 124 | # Draw Pheromones 125 | if self.variation == "MAX-MIN": 126 | for ant in self.ants: 127 | for edge in ant.edges: 128 | for e in edge: 129 | r = g = b = int(min((e.pheromone)*math.pow(10, 5), 255)) 130 | thickness = int(translateValue(e.pheromone, 0, 255, 1, 8)) 131 | pygame.draw.line(manager.screen, (r, g, 0), self.nodes[e.a].GetTuple(), self.nodes[e.b].GetTuple(), thickness) 132 | else: 133 | for ant in self.ants: 134 | for edge in ant.edges: 135 | for e in edge: 136 | r = g = b = int(min((e.pheromone)*2, 255)) 137 | thickness = int(translateValue(e.pheromone, 0, 255, 1, 8)) 138 | pygame.draw.line(manager.screen, (r, g, 0), self.nodes[e.a].GetTuple(), self.nodes[e.b].GetTuple(), thickness) 139 | 140 | 141 | for node in self.nodes: 142 | pygame.draw.circle(manager.screen, manager.White, node.GetTuple(), manager.scaler) 143 | 144 | for i in self.best_tour: 145 | textSurface = textFont.render(str(i), True, textColor) 146 | textRectangle = textSurface.get_rect(center=(self.nodes[self.best_tour[i]].x, self.nodes[self.best_tour[i]].y)) 147 | manager.screen.blit(textSurface, textRectangle) 148 | -------------------------------------------------------------------------------- /manager.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import random 3 | from random import randint, sample 4 | from point import Point 5 | from utils import * 6 | from genetic import Genetic 7 | from antColony import * 8 | from ant import * 9 | 10 | offset = 100 11 | width, height = 1920, 1080 12 | populationSize = 300 13 | n = 15 14 | colony_size = 10 15 | iterations = 300 16 | pygame.font.init() 17 | 18 | class Manager(object): 19 | size = (width, height) 20 | fps = 30 21 | screen = pygame.display.set_mode(size) 22 | clock = pygame.time.Clock() 23 | scaler = 1 24 | max_radius = 15 25 | Black = (0, 0, 0) 26 | White = (255, 255, 255) 27 | Yellow = (255, 255, 0) 28 | Gray = (100, 100, 100) 29 | Highlight = (255, 255, 0) 30 | LineThickness = 4 31 | showIndex = True 32 | n_points = n 33 | algorithms = ["Brute Force", "Lexicographic Order", "Genetic Algorithm", "Ant Colony ACS", "Ant Colony Elitist", "Ant Colony Max-Min"] 34 | 35 | genetic = Genetic([sample(list(range(n)), n) for i in range(populationSize)], populationSize) 36 | 37 | PossibleCombinations = Factorial(n_points) 38 | # print("possible combinations : {}".format(Factorial(n_points))) 39 | 40 | Order = [i for i in range(n_points)] 41 | counter = 0 42 | 43 | def __init__(self, Points = [Point(randint(offset, width-offset), randint(offset, height-offset)) for i in range(n_points)]): 44 | self.Points = Points 45 | self.recordDistance = SumDistance(self.Points) 46 | self.OptimalRoutes = self.Points.copy() 47 | self.currentList = self.Points.copy() 48 | 49 | # --- Ant Colony --- 50 | self.antColony = AntColony(variation="ACS", size=colony_size, max_iterations = iterations, 51 | nodes=self.Points.copy(), alpha=1, beta=3, rho=0.1, pheromone=1, phe_deposit_weight=1) 52 | 53 | def ResetGenetic(self): 54 | self.genetic = Genetic([sample(list(range(n)), n) for i in range(populationSize)], populationSize) 55 | 56 | def ChangeAntColonyVariation(self, name): 57 | self.antColony.variation = name 58 | 59 | def ResetAntColony(self, name="ACS"): 60 | self.recordDistance = SumDistance(self.Points) 61 | self.antColony = AntColony(variation=name, size=colony_size, max_iterations = iterations, 62 | nodes=self.Points.copy(), alpha=1, beta=3, rho=0.1, pheromone=1, phe_deposit_weight=1) 63 | def SetFps(self): 64 | return self.clock.tick(self.fps)/1000.0 65 | 66 | def UpdateCaption(self): 67 | frameRate = int(self.clock.get_fps()) 68 | pygame.display.set_caption("Traveling Salesman Problem - Fps : {}".format(frameRate)) 69 | 70 | def Counter(self): 71 | self.counter += 1 72 | if self.counter > self.PossibleCombinations: 73 | self.counter = self.PossibleCombinations 74 | 75 | def BruteForce(self): 76 | if self.counter != self.PossibleCombinations: 77 | i1 = randint(0, self.n_points-1) 78 | i2 = randint(0, self.n_points-1) 79 | self.Points[i1], self.Points[i2] = self.Points[i2], self.Points[i1] 80 | 81 | # self.Counter() 82 | 83 | dist = SumDistance(self.Points) 84 | if dist < self.recordDistance: 85 | self.recordDistance = dist 86 | self.OptimalRoutes = self.Points.copy() 87 | #print("Shortest distance : {}" .format(self.recordDistance)) 88 | 89 | self.DrawLines() 90 | def Lexicographic(self): 91 | self.Order = LexicalOrder(self.Order) 92 | nodes = [] 93 | for i in self.Order: 94 | nodes.append(self.Points[i]) 95 | 96 | self.Counter() 97 | 98 | dist = SumDistance(nodes) 99 | if dist < self.recordDistance: 100 | self.recordDistance = dist 101 | self.OptimalRoutes = nodes.copy() 102 | #print("Shortest distance : {}" .format(self.recordDistance)) 103 | self.DrawLines() 104 | 105 | def GeneticAlgorithm(self): 106 | self.genetic.CalculateFitness(self.Points) 107 | self.genetic.NaturalSelection() 108 | 109 | # self.Counter() 110 | for i in range(self.n_points): 111 | self.currentList[i] = self.Points[self.genetic.current[i]] 112 | if self.genetic.record < self.recordDistance: 113 | for i in range(self.n_points): 114 | self.OptimalRoutes[i] = self.Points[self.genetic.fitest[i]] 115 | self.recordDistance = self.genetic.record 116 | 117 | # print(self.OptimalRoutes) 118 | 119 | self.DrawLines(True) 120 | 121 | def AntColonyOptimization(self, pause): 122 | if pause == False: 123 | self.counter += 1 124 | if self.counter > self.antColony.max_iterations: 125 | self.counter = self.antColony.max_iterations 126 | 127 | if self.counter < self.antColony.max_iterations: 128 | self.antColony.Simulate(self.counter) 129 | 130 | self.antColony.Draw(self) 131 | self.recordDistance = self.antColony.best_distance 132 | 133 | def RandomPoints(self): 134 | self.Points = [Point(randint(offset, width-offset), randint(offset, height-offset)) for i in range(self.n_points)] 135 | self.ResetAntColony(self.antColony.variation) 136 | self.recordDistance = SumDistance(self.Points) 137 | self.OptimalRoutes = self.Points.copy() 138 | self.currentList = self.Points.copy() 139 | 140 | def Percentage(self, val): 141 | percent = (self.counter/val) * 100 142 | textColor = (255, 255, 255) 143 | # textFont = pg.font.Font("freesansbold.ttf", size) 144 | textFont = pygame.font.SysFont("Arial", 20) 145 | textSurface = textFont.render(str(round(percent, 4)), False, textColor) 146 | self.screen.blit(textSurface, (width//2, 50)) 147 | 148 | def ShowText(self, selectedIndex, started = True): 149 | textColor = (255, 255, 255) 150 | # textFont = pg.font.Font("freesansbold.ttf", size) 151 | textFont = pygame.font.SysFont("Times", 20) 152 | textFont2 = pygame.font.SysFont("Arial Black", 40) 153 | 154 | textSurface1 = textFont.render("Best distance : " + str(round(self.recordDistance,2)), False, textColor) 155 | textSurface2 = textFont.render(self.algorithms[selectedIndex], False, textColor) 156 | textSurface3 = textFont2.render("... Press ' SPACE ' to start ..." ,False, textColor) 157 | 158 | self.screen.blit(textSurface1, (100, 70)) 159 | self.screen.blit(textSurface2, (100, 35)) 160 | if started == False: 161 | self.screen.blit(textSurface3, (width//2, height-200)) 162 | 163 | def DrawShortestPath(self): 164 | if len(self.OptimalRoutes) > 0: 165 | for n in range(self.n_points): 166 | _i = (n+1)%self.n_points 167 | pygame.draw.line(self.screen, self.Highlight, 168 | (self.OptimalRoutes[n].x, self.OptimalRoutes[n].y), 169 | (self.OptimalRoutes[_i].x, self.OptimalRoutes[_i].y), 170 | self.LineThickness) 171 | self.OptimalRoutes[n].Draw(self, self.showIndex, True, n) 172 | 173 | def DrawPoints(self, selected_index = 0): 174 | for point in self.Points: 175 | point.radius = self.scaler 176 | point.Draw(self) 177 | 178 | def DrawLines(self, drawCurrent=False): 179 | if drawCurrent == True: 180 | for i, point in enumerate(self.currentList): 181 | _i = (i+1)%len(self.currentList) 182 | pygame.draw.line(self.screen, self.Gray, (point.x, point.y), (self.currentList[_i].x, self.currentList[_i].y), 1) 183 | else: 184 | for i, point in enumerate(self.Points): 185 | _i = (i+1)%len(self.Points) 186 | pygame.draw.line(self.screen, self.Gray, (point.x, point.y), (self.Points[_i].x, self.Points[_i].y), 1) 187 | 188 | def Background(self): 189 | self.screen.fill(self.Black) 190 | -------------------------------------------------------------------------------- /UI/ui.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from manager import width, height 3 | 4 | def translate(value, min1, max1, min2, max2): 5 | return min2 + (max2-min2)*((value-min1)/(max1-min1)) 6 | 7 | class Button: 8 | def __init__(self, text, position = (width-230, 400) , w = 100, h= 50, border=0, color = (0, 0, 0), borderColor = (0, 0, 0)): 9 | self.text = text 10 | self.position = position 11 | self.w = w 12 | self.h = h 13 | self.border = border 14 | self.temp = color 15 | self.color = color 16 | self.borderColor = borderColor 17 | self.font = 'freesansbold.ttf' 18 | self.fontSize = 25 19 | self.textColor = (255, 255, 255) 20 | self.state = False 21 | self.action = None 22 | self.clicked = False 23 | 24 | def HandleMouse(self,mouseClicked, HoverColor = (50, 120, 140)): 25 | m = pygame.mouse.get_pos() 26 | if m[0] >= self.position[0] and m[0] <= self.position[0] + self.w: 27 | if m[1] >= self.position[1] and m[1] <= self.position[1] + self.h: 28 | self.color = HoverColor 29 | if mouseClicked : 30 | self.color = (200, 200, 200) 31 | if self.action == None and mouseClicked == True: 32 | self.state = not self.state 33 | else: 34 | self.color = self.temp 35 | else: 36 | self.color = self.temp 37 | 38 | 39 | def Render(self, screen, mouseClicked, checker=True): 40 | if checker: 41 | self.HandleMouse(mouseClicked) 42 | font = pygame.font.Font(self.font, self.fontSize) 43 | text = font.render(self.text, True, self.textColor) 44 | textRect = text.get_rect() 45 | textRect.center = (self.position[0]+self.w//2, self.position[1]+self.h//2) 46 | if self.border > 0: 47 | pygame.draw.rect(screen, self.borderColor, pygame.Rect(self.position[0] - self.border//2, self.position[1] - self.border//2, self.w + self.border, self.h + self.border)) 48 | pygame.draw.rect(screen, self.color, pygame.Rect(self.position[0], self.position[1], self.w, self.h)) 49 | 50 | screen.blit(text, textRect) 51 | 52 | class Panel: 53 | def __init__(self, position = (width-400, 90), w= 385, h= 800, color=(2, 3, 12), alpha=100): 54 | self.position = position 55 | self.w = w 56 | self.h = h 57 | self.color = color 58 | self.alpha = alpha 59 | 60 | def Render(self, screen): 61 | s = pygame.Surface((self.w, self.h)) 62 | s.set_alpha(self.alpha) 63 | s.fill(self.color) 64 | screen.blit(s, (self.position[0], self.position[1])) 65 | #pygame.draw.rect(screen, self.color, pygame.Rect(self.position[0], self.position[1], self.w, self.h)) 66 | 67 | class ToggleButton: 68 | def __init__(self, position= ((width-200, 400)), w = 30, h=30, state=False, color=(20, 40, 50), activeColor=(240, 140, 200)): 69 | self.position = position 70 | self.w = w 71 | self.h = h 72 | self.clicked = False 73 | self.state = state 74 | self.temp = (activeColor, color) 75 | self.activeColor = activeColor 76 | self.color = color 77 | 78 | def HandleMouse(self, HoverColor = (50, 120, 140)): 79 | m = pygame.mouse.get_pos() 80 | 81 | if m[0] >= self.position[0] and m[0] <= self.position[0] + self.w: 82 | if m[1] >= self.position[1] and m[1] <= self.position[1] + self.h: 83 | self.color = HoverColor 84 | self.activeColor = HoverColor 85 | if pygame.mouse.get_pressed()[0]: 86 | self.color = (255, 255, 255) 87 | if self.clicked: 88 | self.state = not self.state 89 | else: 90 | self.color = self.temp[1] 91 | self.activeColor =self.temp[0] 92 | else: 93 | self.color = self.temp[1] 94 | self.activeColor =self.temp[0] 95 | 96 | def Render(self, screen, clicked): 97 | self.HandleMouse() 98 | self.clicked = clicked 99 | if self.state == True: 100 | pygame.draw.rect(screen, self.activeColor, pygame.Rect(self.position[0], self.position[1], self.w, self.h)) 101 | else: 102 | pygame.draw.rect(screen, self.color, pygame.Rect(self.position[0], self.position[1], self.w, self.h)) 103 | return self.state 104 | 105 | class TextUI: 106 | def __init__(self,text, position, fontColor, anchor='center'): 107 | self.position = position 108 | self.text = text 109 | self.font = 'freesansbold.ttf' 110 | self.anchor = anchor 111 | self.fontSize = 18 112 | self.fontColor = fontColor 113 | def Render(self, screen): 114 | font = pygame.font.Font(self.font, self.fontSize) 115 | text = font.render(self.text, True, self.fontColor) 116 | textRect = text.get_rect() 117 | setattr(textRect, self.anchor, self.position) 118 | #textRect.center = (self.position[0], self.position[1]) 119 | screen.blit(text, textRect) 120 | 121 | class DigitInput: 122 | def __init__(self,startingValue, position = (width-320, 100), w= 300, h= 600, color=(8, 3, 12)): 123 | self.position = position 124 | self.text = str(startingValue) 125 | self.fontColor = (255, 255, 255) 126 | self.fontSize = 18 127 | self.font = 'freesansbold.ttf' 128 | self.w = w 129 | self.h = h 130 | self.color = color 131 | self.value = int(self.text) 132 | self.hoverEnter = False 133 | 134 | def Check(self, backspace,val): 135 | 136 | if self.hoverEnter == True: 137 | if backspace == True: 138 | 139 | if len(str(self.value)) <= 0 or len(str(self.value))-1 <= 0: 140 | self.value = 0 141 | else: 142 | self.value = int(str(self.value)[:-1]) 143 | 144 | else: 145 | if self.text.isdigit(): 146 | self.value = int(str(self.value) + str(self.text)) 147 | else: 148 | for el in self.text: 149 | if el.isdigit() != True: 150 | self.text = self.text.replace(el, "") 151 | backspace == False 152 | self.text = "" 153 | 154 | def updateText(self, val, pressed): 155 | m = pygame.mouse.get_pos() 156 | if m[0] >= self.position[0] and m[0] <= self.position[0] + self.w: 157 | if m[1] >= self.position[1] and m[1] <= self.position[1] + self.h: 158 | self.hoverEnter = True 159 | if pressed == True: 160 | self.text += val 161 | else: 162 | self.hoverEnter = False 163 | val = "" 164 | else: 165 | self.hoverEnter = False 166 | val = "" 167 | 168 | 169 | def Render(self, screen, val, backspace, pressed): 170 | self.updateText(val, pressed) 171 | self.Check(backspace, val) 172 | font = pygame.font.Font(self.font, self.fontSize) 173 | text = font.render(str(self.value), True, self.fontColor) 174 | textRect = text.get_rect() 175 | textRect.center = (self.position[0]+self.w//2, self.position[1]+self.h//2) 176 | pygame.draw.rect(screen, self.color, pygame.Rect(self.position[0], self.position[1], self.w, self.h)) 177 | screen.blit(text, textRect) 178 | 179 | class Slider: 180 | def __init__(self,x, y, val, min1, max1, length, h, max=500): 181 | self.value = val 182 | self.x = x 183 | self.y = y 184 | self.h = h 185 | self.min1 = min1 186 | self.max1 = max1 187 | self.length = length 188 | self.lineColor = (20, 10, 20) 189 | self.rectradius = 10 190 | self.temp_radius = self.rectradius 191 | self.rectColor = (255, 255, 255) 192 | self.v = 0.2 193 | self.temp = self.lineColor 194 | self.max = max 195 | 196 | def Calculate(self, val): 197 | self.v = translate(val, 0, self.length, 0, 1) 198 | self.value = self.v * self.max 199 | 200 | def HandleMouse(self): 201 | mx, my = pygame.mouse.get_pos() 202 | 203 | if mx >= self.x and mx <= self.x + self.length: 204 | if my >= self.y and my <= self.y + self.h: 205 | self.rectradius = 15 206 | if pygame.mouse.get_pressed()[0]: 207 | self.Calculate(mx - self.x) 208 | else: 209 | self.lineColor = self.temp 210 | self.rectradius = self.temp_radius 211 | else: 212 | self.lineColor = self.temp 213 | self.rectradius = self.temp_radius 214 | 215 | def Render(self,screen): 216 | self.HandleMouse() 217 | pygame.draw.rect(screen, self.lineColor, pygame.Rect(self.x, self.y, self.length, self.h)) 218 | x = int((self.v * self.length) + self.x) 219 | pygame.draw.rect(screen, self.rectColor, pygame.Rect(self.x, self.y, int( self.v * self.length), self.h)) 220 | pygame.draw.circle(screen, (130, 213, 151), (x, self.y + (self.rectradius//2)), self.rectradius) 221 | return self.value 222 | 223 | 224 | 225 | ## Don't Mind the code below it's an absolute mess 🍝 226 | class DropDownButton: 227 | def __init__(self, text="Select", position = (width-230, 400) , w = 100, h= 50, children_size=2, border=0, color = (0, 0, 0), borderColor = (0, 0, 0)): 228 | self.text = text 229 | self.position = position 230 | self.w = w 231 | self.h = h 232 | self.border = border 233 | self.temp = color 234 | self.color = color 235 | self.children_size = children_size 236 | self.childs = [] 237 | for i in range(self.children_size): 238 | button = Button("button " +str(i), (position[0], position[1] + h + h*i+ 2), w, h, border, color, borderColor) 239 | self.childs.append(button) 240 | self.borderColor = borderColor 241 | self.font = 'freesansbold.ttf' 242 | self.fontSize = 25 243 | self.textColor = (255, 255, 255) 244 | self.state = False 245 | self.action = None 246 | self.selected = False 247 | self.folded = False 248 | self.currentIndex = None 249 | 250 | def updateChildren(self, value): 251 | self.children_size = value 252 | for i in range(self.children_size): 253 | button = Button("button " +str(i), (self.position[0], self.position[1] + h + h*i+ 2), self.w, self.h, 0, self.border, self.color, self.borderColor) 254 | self.childs.append(button) 255 | 256 | def HandleMouse(self,mouseClicked, HoverColor = (50, 120, 140)): 257 | if self.folded == False: 258 | m = pygame.mouse.get_pos() 259 | if m[0] >= self.position[0] and m[0] <= self.position[0] + self.w: 260 | if m[1] >= self.position[1] and m[1] <= self.position[1] + self.h: 261 | self.color = HoverColor 262 | if mouseClicked == True: 263 | self.folded = not self.folded 264 | self.color = (200, 200, 200) 265 | if self.action == None and mouseClicked == True: 266 | self.state = not self.state 267 | else: 268 | self.color = self.temp 269 | else: 270 | self.color = self.temp 271 | else: 272 | m = pygame.mouse.get_pos() 273 | if m[0] >= self.position[0] and m[0] <= self.position[0] + self.w: 274 | if m[1] >= self.position[1] and m[1] <= self.position[1] + self.h: 275 | self.color = HoverColor 276 | if mouseClicked == True: 277 | self.folded = not self.folded 278 | self.color = (200, 200, 200) 279 | if self.action == None and mouseClicked == True: 280 | self.state = not self.state 281 | else: 282 | self.color = self.temp 283 | else: 284 | self.color = self.temp 285 | for child in self.childs: 286 | m = pygame.mouse.get_pos() 287 | if m[0] >= child.position[0] and m[0] <= child.position[0] + child.w: 288 | if m[1] >= child.position[1] and m[1] <= child.position[1] + child.h: 289 | child.color = HoverColor 290 | if mouseClicked==True: 291 | child.color = (200, 200, 200) 292 | self.text = child.text 293 | self.currentIndex = self.childs.index(child) 294 | self.folded = not self.folded 295 | if child.action == None and mouseClicked == True: 296 | child.state = not child.state 297 | else: 298 | child.color = child.temp 299 | else: 300 | child.color = child.temp 301 | 302 | 303 | def Render(self, screen, mouseClicked, checker = True): 304 | if checker: 305 | self.HandleMouse(mouseClicked) 306 | font = pygame.font.Font(self.font, self.fontSize) 307 | text = font.render(self.text, True, self.textColor) 308 | textRect = text.get_rect() 309 | textRect.center = (self.position[0]+self.w//2, self.position[1]+self.h//2) 310 | if self.border > 0: 311 | pygame.draw.rect(screen, self.borderColor, pygame.Rect(self.position[0] - self.border//2, self.position[1] - self.border//2, self.w + self.border, self.h + self.border)) 312 | pygame.draw.rect(screen, self.color, pygame.Rect(self.position[0], self.position[1], self.w, self.h)) 313 | 314 | screen.blit(text, textRect) 315 | if self.folded == True: 316 | for child in self.childs: 317 | child.Render(screen, mouseClicked, False) 318 | --------------------------------------------------------------------------------