Skip to content

Commit b907292

Browse files
Clarkzzzzzcclausspre-commit-ci[bot]
authored andcommitted
add graphs/ant_colony_optimization_algorithms.py (TheAlgorithms#11163)
* add ant_colonyant_colony_optimization_algorithms.py * Modify details * Modify type annotation * Add tests for KeyError, IndexError, StopIteration, etc. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Christian Clauss <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent b615a3b commit b907292

File tree

1 file changed

+226
-0
lines changed

1 file changed

+226
-0
lines changed

Diff for: graphs/ant_colony_optimization_algorithms.py

+226
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
"""
2+
Use an ant colony optimization algorithm to solve the travelling salesman problem (TSP)
3+
which asks the following question:
4+
"Given a list of cities and the distances between each pair of cities, what is the
5+
shortest possible route that visits each city exactly once and returns to the origin
6+
city?"
7+
8+
https://en.wikipedia.org/wiki/Ant_colony_optimization_algorithms
9+
https://en.wikipedia.org/wiki/Travelling_salesman_problem
10+
11+
Author: Clark
12+
"""
13+
14+
import copy
15+
import random
16+
17+
cities = {
18+
0: [0, 0],
19+
1: [0, 5],
20+
2: [3, 8],
21+
3: [8, 10],
22+
4: [12, 8],
23+
5: [12, 4],
24+
6: [8, 0],
25+
7: [6, 2],
26+
}
27+
28+
29+
def main(
30+
cities: dict[int, list[int]],
31+
ants_num: int,
32+
iterations_num: int,
33+
pheromone_evaporation: float,
34+
alpha: float,
35+
beta: float,
36+
q: float, # Pheromone system parameters Q,which is a constant
37+
) -> tuple[list[int], float]:
38+
"""
39+
Ant colony algorithm main function
40+
>>> main(cities=cities, ants_num=10, iterations_num=20,
41+
... pheromone_evaporation=0.7, alpha=1.0, beta=5.0, q=10)
42+
([0, 1, 2, 3, 4, 5, 6, 7, 0], 37.909778143828696)
43+
>>> main(cities={0: [0, 0], 1: [2, 2]}, ants_num=5, iterations_num=5,
44+
... pheromone_evaporation=0.7, alpha=1.0, beta=5.0, q=10)
45+
([0, 1, 0], 5.656854249492381)
46+
>>> main(cities={0: [0, 0], 1: [2, 2], 4: [4, 4]}, ants_num=5, iterations_num=5,
47+
... pheromone_evaporation=0.7, alpha=1.0, beta=5.0, q=10)
48+
Traceback (most recent call last):
49+
...
50+
IndexError: list index out of range
51+
>>> main(cities={}, ants_num=5, iterations_num=5,
52+
... pheromone_evaporation=0.7, alpha=1.0, beta=5.0, q=10)
53+
Traceback (most recent call last):
54+
...
55+
StopIteration
56+
>>> main(cities={0: [0, 0], 1: [2, 2]}, ants_num=0, iterations_num=5,
57+
... pheromone_evaporation=0.7, alpha=1.0, beta=5.0, q=10)
58+
([], inf)
59+
>>> main(cities={0: [0, 0], 1: [2, 2]}, ants_num=5, iterations_num=0,
60+
... pheromone_evaporation=0.7, alpha=1.0, beta=5.0, q=10)
61+
([], inf)
62+
>>> main(cities={0: [0, 0], 1: [2, 2]}, ants_num=5, iterations_num=5,
63+
... pheromone_evaporation=1, alpha=1.0, beta=5.0, q=10)
64+
([0, 1, 0], 5.656854249492381)
65+
>>> main(cities={0: [0, 0], 1: [2, 2]}, ants_num=5, iterations_num=5,
66+
... pheromone_evaporation=0, alpha=1.0, beta=5.0, q=10)
67+
([0, 1, 0], 5.656854249492381)
68+
"""
69+
# Initialize the pheromone matrix
70+
cities_num = len(cities)
71+
pheromone = [[1.0] * cities_num] * cities_num
72+
73+
best_path: list[int] = []
74+
best_distance = float("inf")
75+
for _ in range(iterations_num):
76+
ants_route = []
77+
for _ in range(ants_num):
78+
unvisited_cities = copy.deepcopy(cities)
79+
current_city = {next(iter(cities.keys())): next(iter(cities.values()))}
80+
del unvisited_cities[next(iter(current_city.keys()))]
81+
ant_route = [next(iter(current_city.keys()))]
82+
while unvisited_cities:
83+
current_city, unvisited_cities = city_select(
84+
pheromone, current_city, unvisited_cities, alpha, beta
85+
)
86+
ant_route.append(next(iter(current_city.keys())))
87+
ant_route.append(0)
88+
ants_route.append(ant_route)
89+
90+
pheromone, best_path, best_distance = pheromone_update(
91+
pheromone,
92+
cities,
93+
pheromone_evaporation,
94+
ants_route,
95+
q,
96+
best_path,
97+
best_distance,
98+
)
99+
return best_path, best_distance
100+
101+
102+
def distance(city1: list[int], city2: list[int]) -> float:
103+
"""
104+
Calculate the distance between two coordinate points
105+
>>> distance([0, 0], [3, 4] )
106+
5.0
107+
>>> distance([0, 0], [-3, 4] )
108+
5.0
109+
>>> distance([0, 0], [-3, -4] )
110+
5.0
111+
"""
112+
return (((city1[0] - city2[0]) ** 2) + ((city1[1] - city2[1]) ** 2)) ** 0.5
113+
114+
115+
def pheromone_update(
116+
pheromone: list[list[float]],
117+
cities: dict[int, list[int]],
118+
pheromone_evaporation: float,
119+
ants_route: list[list[int]],
120+
q: float, # Pheromone system parameters Q,which is a constant
121+
best_path: list[int],
122+
best_distance: float,
123+
) -> tuple[list[list[float]], list[int], float]:
124+
"""
125+
Update pheromones on the route and update the best route
126+
>>>
127+
>>> pheromone_update(pheromone=[[1.0, 1.0], [1.0, 1.0]],
128+
... cities={0: [0,0], 1: [2,2]}, pheromone_evaporation=0.7,
129+
... ants_route=[[0, 1, 0]], q=10, best_path=[],
130+
... best_distance=float("inf"))
131+
([[0.7, 4.235533905932737], [4.235533905932737, 0.7]], [0, 1, 0], 5.656854249492381)
132+
>>> pheromone_update(pheromone=[],
133+
... cities={0: [0,0], 1: [2,2]}, pheromone_evaporation=0.7,
134+
... ants_route=[[0, 1, 0]], q=10, best_path=[],
135+
... best_distance=float("inf"))
136+
Traceback (most recent call last):
137+
...
138+
IndexError: list index out of range
139+
>>> pheromone_update(pheromone=[[1.0, 1.0], [1.0, 1.0]],
140+
... cities={}, pheromone_evaporation=0.7,
141+
... ants_route=[[0, 1, 0]], q=10, best_path=[],
142+
... best_distance=float("inf"))
143+
Traceback (most recent call last):
144+
...
145+
KeyError: 0
146+
"""
147+
for a in range(len(cities)): # Update the volatilization of pheromone on all routes
148+
for b in range(len(cities)):
149+
pheromone[a][b] *= pheromone_evaporation
150+
for ant_route in ants_route:
151+
total_distance = 0.0
152+
for i in range(len(ant_route) - 1): # Calculate total distance
153+
total_distance += distance(cities[ant_route[i]], cities[ant_route[i + 1]])
154+
delta_pheromone = q / total_distance
155+
for i in range(len(ant_route) - 1): # Update pheromones
156+
pheromone[ant_route[i]][ant_route[i + 1]] += delta_pheromone
157+
pheromone[ant_route[i + 1]][ant_route[i]] = pheromone[ant_route[i]][
158+
ant_route[i + 1]
159+
]
160+
161+
if total_distance < best_distance:
162+
best_path = ant_route
163+
best_distance = total_distance
164+
165+
return pheromone, best_path, best_distance
166+
167+
168+
def city_select(
169+
pheromone: list[list[float]],
170+
current_city: dict[int, list[int]],
171+
unvisited_cities: dict[int, list[int]],
172+
alpha: float,
173+
beta: float,
174+
) -> tuple[dict[int, list[int]], dict[int, list[int]]]:
175+
"""
176+
Choose the next city for ants
177+
>>> city_select(pheromone=[[1.0, 1.0], [1.0, 1.0]], current_city={0: [0, 0]},
178+
... unvisited_cities={1: [2, 2]}, alpha=1.0, beta=5.0)
179+
({1: [2, 2]}, {})
180+
>>> city_select(pheromone=[], current_city={0: [0,0]},
181+
... unvisited_cities={1: [2, 2]}, alpha=1.0, beta=5.0)
182+
Traceback (most recent call last):
183+
...
184+
IndexError: list index out of range
185+
>>> city_select(pheromone=[[1.0, 1.0], [1.0, 1.0]], current_city={},
186+
... unvisited_cities={1: [2, 2]}, alpha=1.0, beta=5.0)
187+
Traceback (most recent call last):
188+
...
189+
StopIteration
190+
>>> city_select(pheromone=[[1.0, 1.0], [1.0, 1.0]], current_city={0: [0, 0]},
191+
... unvisited_cities={}, alpha=1.0, beta=5.0)
192+
Traceback (most recent call last):
193+
...
194+
IndexError: list index out of range
195+
"""
196+
probabilities = []
197+
for city in unvisited_cities:
198+
city_distance = distance(
199+
unvisited_cities[city], next(iter(current_city.values()))
200+
)
201+
probability = (pheromone[city][next(iter(current_city.keys()))] ** alpha) * (
202+
(1 / city_distance) ** beta
203+
)
204+
probabilities.append(probability)
205+
206+
chosen_city_i = random.choices(
207+
list(unvisited_cities.keys()), weights=probabilities
208+
)[0]
209+
chosen_city = {chosen_city_i: unvisited_cities[chosen_city_i]}
210+
del unvisited_cities[next(iter(chosen_city.keys()))]
211+
return chosen_city, unvisited_cities
212+
213+
214+
if __name__ == "__main__":
215+
best_path, best_distance = main(
216+
cities=cities,
217+
ants_num=10,
218+
iterations_num=20,
219+
pheromone_evaporation=0.7,
220+
alpha=1.0,
221+
beta=5.0,
222+
q=10,
223+
)
224+
225+
print(f"{best_path = }")
226+
print(f"{best_distance = }")

0 commit comments

Comments
 (0)