TahaRasouli commited on
Commit
b2c59a2
·
verified ·
1 Parent(s): fbc2a66

Create ants.py

Browse files
Files changed (1) hide show
  1. ants.py +179 -0
ants.py ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import numpy as np
3
+ import networkx as nx
4
+ import matplotlib
5
+ matplotlib.use('Agg')
6
+ import matplotlib.pyplot as plt
7
+ from scipy.sparse.csgraph import shortest_path
8
+
9
+ # Import visualizer's sorter to ensure coordinates match the app perfectly
10
+ from visualizer import get_sorted_nodes
11
+
12
+ def get_physical_path(start, end, preds):
13
+ path = []
14
+ curr = end
15
+ while curr != start and curr >= 0:
16
+ path.append(curr)
17
+ curr = preds[start, curr]
18
+ path.reverse()
19
+ return path
20
+
21
+ def run_ant_colony(distances, coords, n_ants=50, n_iterations=100, decay=0.1, alpha=1.0, beta=2.0):
22
+ n_nodes = distances.shape[0]
23
+
24
+ dist_matrix_for_pathing = np.where(distances == 0, np.inf, distances)
25
+ np.fill_diagonal(dist_matrix_for_pathing, 0)
26
+ all_pairs_distances, predecessors = shortest_path(csgraph=dist_matrix_for_pathing, directed=False, return_predecessors=True)
27
+
28
+ pheromones = np.ones((n_nodes, n_nodes)) * 0.1
29
+ best_macro_tour = None
30
+ best_length = float('inf')
31
+
32
+ start_node = np.lexsort((coords[:, 0], coords[:, 1]))[0]
33
+
34
+ for iteration in range(n_iterations):
35
+ all_tours = []
36
+ all_lengths = []
37
+
38
+ for ant in range(n_ants):
39
+ unvisited = set(range(n_nodes))
40
+ current_node = start_node
41
+ tour = [current_node]
42
+ unvisited.remove(current_node)
43
+ tour_length = 0.0
44
+
45
+ while unvisited:
46
+ candidates = list(unvisited)
47
+ pher_values = pheromones[current_node, candidates]
48
+ dist_values = all_pairs_distances[current_node, candidates]
49
+ heuristic = 1.0 / (dist_values + 1e-10)
50
+
51
+ probabilities = (pher_values ** alpha) * (heuristic ** beta)
52
+ if probabilities.sum() == 0:
53
+ probabilities = np.ones(len(candidates)) / len(candidates)
54
+ else:
55
+ probabilities /= probabilities.sum()
56
+
57
+ next_node = np.random.choice(candidates, p=probabilities)
58
+
59
+ tour.append(next_node)
60
+ tour_length += all_pairs_distances[current_node, next_node]
61
+ unvisited.remove(next_node)
62
+ current_node = next_node
63
+
64
+ tour_length += all_pairs_distances[tour[-1], tour[0]]
65
+ tour.append(tour[0])
66
+
67
+ all_tours.append(tour)
68
+ all_lengths.append(tour_length)
69
+
70
+ if tour_length < best_length:
71
+ best_length = tour_length
72
+ best_macro_tour = tour
73
+
74
+ pheromones *= (1.0 - decay)
75
+
76
+ for tour, length in zip(all_tours, all_lengths):
77
+ deposit_amount = 100.0 / length
78
+ for i in range(len(tour) - 1):
79
+ u, v = tour[i], tour[i+1]
80
+ pheromones[u, v] += deposit_amount
81
+ pheromones[v, u] += deposit_amount
82
+
83
+ # Unpack the macro tour into physical steps
84
+ physical_tour = [best_macro_tour[0]]
85
+ for i in range(len(best_macro_tour) - 1):
86
+ start_n = best_macro_tour[i]
87
+ end_n = best_macro_tour[i+1]
88
+ segment = get_physical_path(start_n, end_n, predecessors)
89
+ physical_tour.extend(segment)
90
+
91
+ return physical_tour, best_length
92
+
93
+ def draw_base_graph_edges(ax, distances, coords, color='red'):
94
+ n_nodes = distances.shape[0]
95
+ for u in range(n_nodes):
96
+ for v in range(u + 1, n_nodes):
97
+ if distances[u, v] > 0 and distances[u, v] != np.inf:
98
+ start_c = coords[u]
99
+ end_c = coords[v]
100
+ ax.annotate("", xy=end_c, xytext=start_c,
101
+ arrowprops=dict(arrowstyle="-", color=color, linewidth=4.0, alpha=0.6, zorder=1))
102
+
103
+ def visualize_tour_app(coords, physical_tour, title, distances, width, height):
104
+ fig, ax = plt.figure(figsize=(10, 10)), plt.gca()
105
+
106
+ # Force grid to match app dimensions
107
+ ax.set_xlim(-0.5, width - 0.5)
108
+ ax.set_ylim(-0.5, height - 0.5)
109
+
110
+ xs = coords[:, 0]
111
+ ys = coords[:, 1]
112
+
113
+ draw_base_graph_edges(ax, distances, coords)
114
+ ax.scatter(xs, ys, c='blue', s=100, zorder=5)
115
+
116
+ entrance_idx = physical_tour[0]
117
+ ax.scatter(coords[entrance_idx, 0], coords[entrance_idx, 1], c='yellow', edgecolors='black', s=400, marker='*', zorder=10, label="Entrance")
118
+
119
+ tour_coords = coords[physical_tour]
120
+
121
+ for i in range(len(tour_coords) - 1):
122
+ start_c = tour_coords[i]
123
+ end_c = tour_coords[i+1]
124
+
125
+ dx = end_c[0] - start_c[0]
126
+ dy = end_c[1] - start_c[1]
127
+
128
+ ax.plot([start_c[0], end_c[0]], [start_c[1], end_c[1]],
129
+ color="blue", linewidth=2.0, linestyle="--", zorder=6)
130
+
131
+ mid_x = start_c[0] + dx * 0.50
132
+ mid_y = start_c[1] + dy * 0.50
133
+ target_x = start_c[0] + dx * 0.51
134
+ target_y = start_c[1] + dy * 0.51
135
+
136
+ ax.annotate("", xy=(target_x, target_y), xytext=(mid_x, mid_y),
137
+ arrowprops=dict(arrowstyle="-|>,head_width=0.4,head_length=0.8",
138
+ color="blue", linewidth=2.0, zorder=7))
139
+
140
+ ax.set_title(title, pad=20, fontsize=14, fontweight='bold')
141
+ ax.invert_yaxis()
142
+ ax.grid(True, linestyle=':', alpha=0.6)
143
+
144
+ from matplotlib.lines import Line2D
145
+ custom_lines = [Line2D([0], [0], color="blue", linewidth=2.0, linestyle="--"),
146
+ Line2D([0], [0], marker='*', color='w', markerfacecolor='yellow', markeredgecolor='black', markersize=15),
147
+ Line2D([0], [0], color="red", linewidth=4.0, alpha=0.6)]
148
+ ax.legend(custom_lines, ['Worker Route', 'Entrance', 'Available Hallways'], loc="best")
149
+
150
+ save_dir = "temp_visuals"
151
+ os.makedirs(save_dir, exist_ok=True)
152
+ save_filename = os.path.join(save_dir, "app_ant_route.png")
153
+
154
+ plt.savefig(save_filename, bbox_inches='tight')
155
+ plt.close()
156
+ return save_filename
157
+
158
+ def solve_and_visualize(G, width, height):
159
+ """Main function to be called from app.py"""
160
+ sorted_nodes = get_sorted_nodes(G)
161
+ n_nodes = len(sorted_nodes)
162
+
163
+ # 1. Build Coordinates Matrix
164
+ coords = np.array(sorted_nodes, dtype=np.float64)
165
+
166
+ # 2. Build Distance Matrix (1 Move = 1 Travel Meter)
167
+ distances = np.zeros((n_nodes, n_nodes), dtype=np.float64)
168
+ for u, v in G.edges():
169
+ idx_u = sorted_nodes.index(u)
170
+ idx_v = sorted_nodes.index(v)
171
+ distances[idx_u, idx_v] = 1.0
172
+ distances[idx_v, idx_u] = 1.0
173
+
174
+ # 3. Run the swarm
175
+ physical_tour, best_length = run_ant_colony(distances, coords)
176
+
177
+ # 4. Draw the image and return the path
178
+ img_path = visualize_tour_app(coords, physical_tour, f"Ant Colony Optimized Route\nTotal Moves: {int(best_length)}", distances, width, height)
179
+ return img_path, int(best_length)