From 7b1f150daef8caea7edc2f0a7495b4f5a36b3402 Mon Sep 17 00:00:00 2001 From: Clarkzzzzz Date: Thu, 16 Nov 2023 15:58:14 +0800 Subject: [PATCH 1/5] add ant_colonyant_colony_optimization_algorithms.py --- graphs/ant_colony_optimization_algorithms.py | 136 +++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 graphs/ant_colony_optimization_algorithms.py diff --git a/graphs/ant_colony_optimization_algorithms.py b/graphs/ant_colony_optimization_algorithms.py new file mode 100644 index 000000000000..ba14586921f0 --- /dev/null +++ b/graphs/ant_colony_optimization_algorithms.py @@ -0,0 +1,136 @@ +""" +A simple example uses the ant colony optimization algorithm +to solve the classic TSP problem +https://en.wikipedia.org/wiki/Ant_colony_optimization_algorithms +Author: Clark +""" + +import copy +import random + + +def distance(city1: list[int], city2: list[int]) -> float: + """ + Calculate the distance between two coordinate points + >>> distance([0,0],[3,4] ) + 5.0 + """ + return (((city1[0] - city2[0]) ** 2) + ((city1[1] - city2[1]) ** 2)) ** 0.5 + + +def pheromone_update( + pheromone: list[list], + cities: dict[int, list[int]], + pheromone_evaporation: float, + ants_route: list[list[int]], + q: float, + best_path: list, + best_distance: float, +) -> tuple[list[list], list, float]: + """ + Update pheromones on the route and update the best route + """ + for a in range(cities_num): # Update the volatilization of pheromone on all routes + for b in range(cities_num): + pheromone[a][b] *= pheromone_evaporation + for ant_route in ants_route: + total_distance = 0.0 + for i in range(len(ant_route) - 1): # Calculate total distance + total_distance += distance(cities[ant_route[i]], cities[ant_route[i + 1]]) + delta_pheromone = q / total_distance + for i in range(len(ant_route) - 1): # Update pheromones + pheromone[ant_route[i]][ant_route[i + 1]] += delta_pheromone + pheromone[ant_route[i + 1]][ant_route[i]] = pheromone[ant_route[i]][ + ant_route[i + 1] + ] + + if total_distance < best_distance: + best_path = ant_route + best_distance = total_distance + + return pheromone, best_path, best_distance + + +def city_select( + pheromone: list[list[int]], + current_city: dict[int, list[int]], + unvisited_cities: dict[int, list[int]], + alpha: float, + beta: float, +) -> tuple[dict[int, list[int]], dict[int, list[int]]]: + """ + Choose the next city for ants + """ + probabilities = [] + for city in unvisited_cities: + city_distance = distance( + unvisited_cities[city], next(iter(current_city.values())) + ) + probability = (pheromone[city][next(iter(current_city.keys()))] ** alpha) * ( + (1 / city_distance) ** beta + ) + probabilities.append(probability) + + chosen_city_i = random.choices( + list(unvisited_cities.keys()), weights=probabilities + )[0] + chosen_city = {chosen_city_i: unvisited_cities[chosen_city_i]} + del unvisited_cities[next(iter(chosen_city.keys()))] + return chosen_city, unvisited_cities + + +if __name__ == "__main__": + # City coordinates for TSP problem + cities = { + 0: [0, 0], + 1: [0, 5], + 2: [3, 8], + 3: [8, 10], + 4: [12, 8], + 5: [12, 4], + 6: [8, 0], + 7: [6, 2], + } + + # Parameter settings + ANTS_NUM = 10 # Number of ants + ITERATIONS_NUM = 20 # Number of iterations + PHEROMONE_EVAPORATION = 0.7 # Pheromone volatilization coefficient, + # the larger the number, the greater the pheromone retention in each generation. + ALPHA = 1.0 + BETA = 5.0 + Q = 10.0 # Pheromone system parameters Q + + # Initialize the pheromone matrix + cities_num = len(cities) + pheromone = [[1] * cities_num] * cities_num + + best_path: list[int] = [] + best_distance = float("inf") + for _ in range(ITERATIONS_NUM): + ants_route = [] + for _ in range(ANTS_NUM): + unvisited_cities = copy.deepcopy(cities) + current_city = {next(iter(cities.keys())): next(iter(cities.values()))} + del unvisited_cities[next(iter(current_city.keys()))] + ant_route = [next(iter(current_city.keys()))] + while unvisited_cities: + current_city, unvisited_cities = city_select( + pheromone, current_city, unvisited_cities, ALPHA, BETA + ) + ant_route.append(next(iter(current_city.keys()))) + ant_route.append(0) + ants_route.append(ant_route) + + pheromone, best_path, best_distance = pheromone_update( + pheromone, + cities, + PHEROMONE_EVAPORATION, + ants_route, + Q, + best_path, + best_distance, + ) + + print("Best Path:", best_path) + print("Best Distance:", best_distance) From 38cae58c9c387394257f0b836986ee43a63b9f5c Mon Sep 17 00:00:00 2001 From: Clarkzzzzz Date: Fri, 17 Nov 2023 18:38:23 +0800 Subject: [PATCH 2/5] Modify details --- graphs/ant_colony_optimization_algorithms.py | 134 +++++++++++++------ 1 file changed, 93 insertions(+), 41 deletions(-) diff --git a/graphs/ant_colony_optimization_algorithms.py b/graphs/ant_colony_optimization_algorithms.py index ba14586921f0..4add378ee1d0 100644 --- a/graphs/ant_colony_optimization_algorithms.py +++ b/graphs/ant_colony_optimization_algorithms.py @@ -1,7 +1,14 @@ """ A simple example uses the ant colony optimization algorithm -to solve the classic TSP problem +to solve the classic TSP problem. +The travelling salesman problem (TSP) asks the following question: +"Given a list of cities and the distances between each pair of cities, + what is the shortest possible route that visits each city exactly once + and returns to the origin city?" + https://en.wikipedia.org/wiki/Ant_colony_optimization_algorithms +https://en.wikipedia.org/wiki/Travelling_salesman_problem + Author: Clark """ @@ -9,29 +16,94 @@ import random +def main( + cities: dict[int, list[int]], + ants_num: int, + iterations_num: int, + pheromone_evaporation: float, + alpha: float, + beta: float, + q: float, # Pheromone system parameters Q,which is a constant +) -> tuple[list[int], float]: + """ + Ant colony algorithm main function + >>> cities = {0:[0,0],1:[2,2]} + >>> ANTS_NUM = 5 + >>> ITERATIONS_NUM = 5 + >>> PHEROMONE_EVAPORATION = 0.7 + >>> ALPHA = 1.0 + >>> BETA = 5.0 + >>> Q = 10 + >>> main(cities,ANTS_NUM,ITERATIONS_NUM,PHEROMONE_EVAPORATION,ALPHA,BETA,Q) + ([0, 1, 0], 5.656854249492381) + """ + # Initialize the pheromone matrix + cities_num = len(cities) + pheromone = [[1.0] * cities_num] * cities_num + + best_path: list[int] = [] + best_distance = float("inf") + for _ in range(iterations_num): + ants_route = [] + for _ in range(ants_num): + unvisited_cities = copy.deepcopy(cities) + current_city = {next(iter(cities.keys())): next(iter(cities.values()))} + del unvisited_cities[next(iter(current_city.keys()))] + ant_route = [next(iter(current_city.keys()))] + while unvisited_cities: + current_city, unvisited_cities = city_select( + pheromone, current_city, unvisited_cities, alpha, beta + ) + ant_route.append(next(iter(current_city.keys()))) + ant_route.append(0) + ants_route.append(ant_route) + + pheromone, best_path, best_distance = pheromone_update( + pheromone, + cities, + pheromone_evaporation, + ants_route, + q, + best_path, + best_distance, + ) + return best_path, best_distance + + def distance(city1: list[int], city2: list[int]) -> float: """ Calculate the distance between two coordinate points - >>> distance([0,0],[3,4] ) + >>> distance([0,0], [3,4] ) 5.0 """ return (((city1[0] - city2[0]) ** 2) + ((city1[1] - city2[1]) ** 2)) ** 0.5 def pheromone_update( - pheromone: list[list], + pheromone: list[list[float]], cities: dict[int, list[int]], pheromone_evaporation: float, ants_route: list[list[int]], - q: float, + q: float, # Pheromone system parameters Q,which is a constant best_path: list, best_distance: float, -) -> tuple[list[list], list, float]: +) -> tuple[list[list[float]], list, float]: """ Update pheromones on the route and update the best route + >>> pheromone = [[1.0,1.0],[1.0,1.0]] + >>> cities = {0:[0,0],1:[2,2]} + >>> PHEROMONE_EVAPORATION = 0.7 + >>> ants_route = [[0,1,0]] + >>> Q = 10 + >>> best_path = [] + >>> best_distance = float("inf") + >>> pheromone_update( + ... pheromone,cities,PHEROMONE_EVAPORATION,ants_route,Q,best_path,best_distance + ... ) + ([[0.7, 4.235533905932737], [4.235533905932737, 0.7]], [0, 1, 0], 5.656854249492381) """ - for a in range(cities_num): # Update the volatilization of pheromone on all routes - for b in range(cities_num): + for a in range(len(cities)): # Update the volatilization of pheromone on all routes + for b in range(len(cities)): pheromone[a][b] *= pheromone_evaporation for ant_route in ants_route: total_distance = 0.0 @@ -52,7 +124,7 @@ def pheromone_update( def city_select( - pheromone: list[list[int]], + pheromone: list[list[float]], current_city: dict[int, list[int]], unvisited_cities: dict[int, list[int]], alpha: float, @@ -60,6 +132,13 @@ def city_select( ) -> tuple[dict[int, list[int]], dict[int, list[int]]]: """ Choose the next city for ants + >>> pheromone = [[1.0,1.0],[1.0,1.0]] + >>> current_city = {0:[0,0]} + >>> unvisited_cities = {1:[2,2]} + >>> ALPHA = 1.0 + >>> BETA = 5.0 + >>> city_select(pheromone,current_city,unvisited_cities,ALPHA,BETA) + ({1: [2, 2]}, {}) """ probabilities = [] for city in unvisited_cities: @@ -99,38 +178,11 @@ def city_select( # the larger the number, the greater the pheromone retention in each generation. ALPHA = 1.0 BETA = 5.0 - Q = 10.0 # Pheromone system parameters Q - - # Initialize the pheromone matrix - cities_num = len(cities) - pheromone = [[1] * cities_num] * cities_num + Q = 10.0 # Pheromone system parameters Q,which is a constant - best_path: list[int] = [] - best_distance = float("inf") - for _ in range(ITERATIONS_NUM): - ants_route = [] - for _ in range(ANTS_NUM): - unvisited_cities = copy.deepcopy(cities) - current_city = {next(iter(cities.keys())): next(iter(cities.values()))} - del unvisited_cities[next(iter(current_city.keys()))] - ant_route = [next(iter(current_city.keys()))] - while unvisited_cities: - current_city, unvisited_cities = city_select( - pheromone, current_city, unvisited_cities, ALPHA, BETA - ) - ant_route.append(next(iter(current_city.keys()))) - ant_route.append(0) - ants_route.append(ant_route) - - pheromone, best_path, best_distance = pheromone_update( - pheromone, - cities, - PHEROMONE_EVAPORATION, - ants_route, - Q, - best_path, - best_distance, - ) + best_path, best_distance = main( + cities, ANTS_NUM, ITERATIONS_NUM, PHEROMONE_EVAPORATION, ALPHA, BETA, Q + ) - print("Best Path:", best_path) - print("Best Distance:", best_distance) + print(f"{best_path = }") + print(f"{best_distance = }") From 6a8e37d92cdf4d81cb5d3670b992955264567b40 Mon Sep 17 00:00:00 2001 From: Clarkzzzzz Date: Fri, 17 Nov 2023 18:41:54 +0800 Subject: [PATCH 3/5] Modify type annotation --- graphs/ant_colony_optimization_algorithms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphs/ant_colony_optimization_algorithms.py b/graphs/ant_colony_optimization_algorithms.py index 4add378ee1d0..8de2145821a4 100644 --- a/graphs/ant_colony_optimization_algorithms.py +++ b/graphs/ant_colony_optimization_algorithms.py @@ -85,9 +85,9 @@ def pheromone_update( pheromone_evaporation: float, ants_route: list[list[int]], q: float, # Pheromone system parameters Q,which is a constant - best_path: list, + best_path: list[int], best_distance: float, -) -> tuple[list[list[float]], list, float]: +) -> tuple[list[list[float]], list[int], float]: """ Update pheromones on the route and update the best route >>> pheromone = [[1.0,1.0],[1.0,1.0]] From ece71ff91128fecb383f6ca9e39422cb29e8e3a1 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 25 Nov 2023 13:18:30 +0100 Subject: [PATCH 4/5] Add tests for KeyError, IndexError, StopIteration, etc. --- graphs/ant_colony_optimization_algorithms.py | 140 +++++++++++-------- 1 file changed, 85 insertions(+), 55 deletions(-) diff --git a/graphs/ant_colony_optimization_algorithms.py b/graphs/ant_colony_optimization_algorithms.py index 8de2145821a4..5257d6b64879 100644 --- a/graphs/ant_colony_optimization_algorithms.py +++ b/graphs/ant_colony_optimization_algorithms.py @@ -1,10 +1,9 @@ """ -A simple example uses the ant colony optimization algorithm -to solve the classic TSP problem. -The travelling salesman problem (TSP) asks the following question: -"Given a list of cities and the distances between each pair of cities, - what is the shortest possible route that visits each city exactly once - and returns to the origin city?" +Use an ant colony optimization algorithm to solve the travelling salesman problem (TSP) +which asks the following question: +"Given a list of cities and the distances between each pair of cities, what is the + shortest possible route that visits each city exactly once and returns to the origin + city?" https://en.wikipedia.org/wiki/Ant_colony_optimization_algorithms https://en.wikipedia.org/wiki/Travelling_salesman_problem @@ -15,6 +14,16 @@ import copy import random +cities = { + 0: [0, 0], + 1: [0, 5], + 2: [3, 8], + 3: [8, 10], + 4: [12, 8], + 5: [12, 4], + 6: [8, 0], + 7: [6, 2], +} def main( cities: dict[int, list[int]], @@ -27,14 +36,33 @@ def main( ) -> tuple[list[int], float]: """ Ant colony algorithm main function - >>> cities = {0:[0,0],1:[2,2]} - >>> ANTS_NUM = 5 - >>> ITERATIONS_NUM = 5 - >>> PHEROMONE_EVAPORATION = 0.7 - >>> ALPHA = 1.0 - >>> BETA = 5.0 - >>> Q = 10 - >>> main(cities,ANTS_NUM,ITERATIONS_NUM,PHEROMONE_EVAPORATION,ALPHA,BETA,Q) + >>> main(cities=cities, ants_num=10, iterations_num=20, + ... pheromone_evaporation=0.7, alpha=1.0, beta=5.0, q=10) + ([0, 1, 2, 3, 4, 5, 6, 7, 0], 37.909778143828696) + >>> main(cities={0: [0, 0], 1: [2, 2]}, ants_num=5, iterations_num=5, + ... pheromone_evaporation=0.7, alpha=1.0, beta=5.0, q=10) + ([0, 1, 0], 5.656854249492381) + >>> main(cities={0: [0, 0], 1: [2, 2], 4: [4, 4]}, ants_num=5, iterations_num=5, + ... pheromone_evaporation=0.7, alpha=1.0, beta=5.0, q=10) + Traceback (most recent call last): + ... + IndexError: list index out of range + >>> main(cities={}, ants_num=5, iterations_num=5, + ... pheromone_evaporation=0.7, alpha=1.0, beta=5.0, q=10) + Traceback (most recent call last): + ... + StopIteration + >>> main(cities={0: [0, 0], 1: [2, 2]}, ants_num=0, iterations_num=5, + ... pheromone_evaporation=0.7, alpha=1.0, beta=5.0, q=10) + ([], inf) + >>> main(cities={0: [0, 0], 1: [2, 2]}, ants_num=5, iterations_num=0, + ... pheromone_evaporation=0.7, alpha=1.0, beta=5.0, q=10) + ([], inf) + >>> main(cities={0: [0, 0], 1: [2, 2]}, ants_num=5, iterations_num=5, + ... pheromone_evaporation=1, alpha=1.0, beta=5.0, q=10) + ([0, 1, 0], 5.656854249492381) + >>> main(cities={0: [0, 0], 1: [2, 2]}, ants_num=5, iterations_num=5, + ... pheromone_evaporation=0, alpha=1.0, beta=5.0, q=10) ([0, 1, 0], 5.656854249492381) """ # Initialize the pheromone matrix @@ -73,7 +101,11 @@ def main( def distance(city1: list[int], city2: list[int]) -> float: """ Calculate the distance between two coordinate points - >>> distance([0,0], [3,4] ) + >>> distance([0, 0], [3, 4] ) + 5.0 + >>> distance([0, 0], [-3, 4] ) + 5.0 + >>> distance([0, 0], [-3, -4] ) 5.0 """ return (((city1[0] - city2[0]) ** 2) + ((city1[1] - city2[1]) ** 2)) ** 0.5 @@ -90,17 +122,26 @@ def pheromone_update( ) -> tuple[list[list[float]], list[int], float]: """ Update pheromones on the route and update the best route - >>> pheromone = [[1.0,1.0],[1.0,1.0]] - >>> cities = {0:[0,0],1:[2,2]} - >>> PHEROMONE_EVAPORATION = 0.7 - >>> ants_route = [[0,1,0]] - >>> Q = 10 - >>> best_path = [] - >>> best_distance = float("inf") - >>> pheromone_update( - ... pheromone,cities,PHEROMONE_EVAPORATION,ants_route,Q,best_path,best_distance - ... ) + >>> + >>> pheromone_update(pheromone=[[1.0, 1.0], [1.0, 1.0]], + ... cities={0: [0,0], 1: [2,2]}, pheromone_evaporation=0.7, + ... ants_route=[[0, 1, 0]], q=10, best_path=[], + ... best_distance=float("inf")) ([[0.7, 4.235533905932737], [4.235533905932737, 0.7]], [0, 1, 0], 5.656854249492381) + >>> pheromone_update(pheromone=[], + ... cities={0: [0,0], 1: [2,2]}, pheromone_evaporation=0.7, + ... ants_route=[[0, 1, 0]], q=10, best_path=[], + ... best_distance=float("inf")) + Traceback (most recent call last): + ... + IndexError: list index out of range + >>> pheromone_update(pheromone=[[1.0, 1.0], [1.0, 1.0]], + ... cities={}, pheromone_evaporation=0.7, + ... ants_route=[[0, 1, 0]], q=10, best_path=[], + ... best_distance=float("inf")) + Traceback (most recent call last): + ... + KeyError: 0 """ for a in range(len(cities)): # Update the volatilization of pheromone on all routes for b in range(len(cities)): @@ -132,13 +173,24 @@ def city_select( ) -> tuple[dict[int, list[int]], dict[int, list[int]]]: """ Choose the next city for ants - >>> pheromone = [[1.0,1.0],[1.0,1.0]] - >>> current_city = {0:[0,0]} - >>> unvisited_cities = {1:[2,2]} - >>> ALPHA = 1.0 - >>> BETA = 5.0 - >>> city_select(pheromone,current_city,unvisited_cities,ALPHA,BETA) + >>> city_select(pheromone=[[1.0, 1.0], [1.0, 1.0]], current_city={0: [0, 0]}, + ... unvisited_cities={1: [2, 2]}, alpha=1.0, beta=5.0) ({1: [2, 2]}, {}) + >>> city_select(pheromone=[], current_city={0: [0,0]}, + ... unvisited_cities={1: [2, 2]}, alpha=1.0, beta=5.0) + Traceback (most recent call last): + ... + IndexError: list index out of range + >>> city_select(pheromone=[[1.0, 1.0], [1.0, 1.0]], current_city={}, + ... unvisited_cities={1: [2, 2]}, alpha=1.0, beta=5.0) + Traceback (most recent call last): + ... + StopIteration + >>> city_select(pheromone=[[1.0, 1.0], [1.0, 1.0]], current_city={0: [0, 0]}, + ... unvisited_cities={}, alpha=1.0, beta=5.0) + Traceback (most recent call last): + ... + IndexError: list index out of range """ probabilities = [] for city in unvisited_cities: @@ -159,30 +211,8 @@ def city_select( if __name__ == "__main__": - # City coordinates for TSP problem - cities = { - 0: [0, 0], - 1: [0, 5], - 2: [3, 8], - 3: [8, 10], - 4: [12, 8], - 5: [12, 4], - 6: [8, 0], - 7: [6, 2], - } - - # Parameter settings - ANTS_NUM = 10 # Number of ants - ITERATIONS_NUM = 20 # Number of iterations - PHEROMONE_EVAPORATION = 0.7 # Pheromone volatilization coefficient, - # the larger the number, the greater the pheromone retention in each generation. - ALPHA = 1.0 - BETA = 5.0 - Q = 10.0 # Pheromone system parameters Q,which is a constant - - best_path, best_distance = main( - cities, ANTS_NUM, ITERATIONS_NUM, PHEROMONE_EVAPORATION, ALPHA, BETA, Q - ) + best_path, best_distance = main(cities=cities, ants_num=10, iterations_num=20, + pheromone_evaporation=0.7, alpha=1.0, beta=5.0, q=10) print(f"{best_path = }") print(f"{best_distance = }") From 536d1664d3040e16032d61c957daa5faf1a24437 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 25 Nov 2023 12:19:05 +0000 Subject: [PATCH 5/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- graphs/ant_colony_optimization_algorithms.py | 30 +++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/graphs/ant_colony_optimization_algorithms.py b/graphs/ant_colony_optimization_algorithms.py index 5257d6b64879..652ad6144297 100644 --- a/graphs/ant_colony_optimization_algorithms.py +++ b/graphs/ant_colony_optimization_algorithms.py @@ -15,16 +15,17 @@ import random cities = { - 0: [0, 0], - 1: [0, 5], - 2: [3, 8], - 3: [8, 10], - 4: [12, 8], - 5: [12, 4], - 6: [8, 0], - 7: [6, 2], + 0: [0, 0], + 1: [0, 5], + 2: [3, 8], + 3: [8, 10], + 4: [12, 8], + 5: [12, 4], + 6: [8, 0], + 7: [6, 2], } + def main( cities: dict[int, list[int]], ants_num: int, @@ -122,7 +123,7 @@ def pheromone_update( ) -> tuple[list[list[float]], list[int], float]: """ Update pheromones on the route and update the best route - >>> + >>> >>> pheromone_update(pheromone=[[1.0, 1.0], [1.0, 1.0]], ... cities={0: [0,0], 1: [2,2]}, pheromone_evaporation=0.7, ... ants_route=[[0, 1, 0]], q=10, best_path=[], @@ -211,8 +212,15 @@ def city_select( if __name__ == "__main__": - best_path, best_distance = main(cities=cities, ants_num=10, iterations_num=20, - pheromone_evaporation=0.7, alpha=1.0, beta=5.0, q=10) + best_path, best_distance = main( + cities=cities, + ants_num=10, + iterations_num=20, + pheromone_evaporation=0.7, + alpha=1.0, + beta=5.0, + q=10, + ) print(f"{best_path = }") print(f"{best_distance = }")