Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,288 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import numpy as np
|
3 |
+
import random
|
4 |
+
import matplotlib.pyplot as plt
|
5 |
+
from scipy.spatial import distance
|
6 |
+
from sklearn.cluster import KMeans
|
7 |
+
import networkx as nx
|
8 |
+
from collections import deque
|
9 |
+
|
10 |
+
# Constants
|
11 |
+
GRID_SIZE = 200
|
12 |
+
FOOD_SOURCES = [(20, 20), (80, 80), (150, 150), (40, 160), (180, 30)]
|
13 |
+
OBSTACLES = [(50, 50), (100, 100), (150, 50), (70, 130), (120, 80)]
|
14 |
+
PHEROMONE_DECAY_RATE = 0.02
|
15 |
+
PHEROMONE_DIFFUSION_RATE = 0.05
|
16 |
+
MAX_ANTS = 100
|
17 |
+
MUTATION_RATE = 0.01
|
18 |
+
|
19 |
+
# Pheromone Grid
|
20 |
+
pheromone_grid = np.zeros((GRID_SIZE, GRID_SIZE, 3)) # 3 channels: food, danger, exploration
|
21 |
+
|
22 |
+
# Graph representation of the environment
|
23 |
+
env_graph = nx.grid_2d_graph(GRID_SIZE, GRID_SIZE)
|
24 |
+
|
25 |
+
# Remove edges for obstacles
|
26 |
+
for obstacle in OBSTACLES:
|
27 |
+
env_graph.remove_node(obstacle)
|
28 |
+
|
29 |
+
# Ant Class
|
30 |
+
class Ant:
|
31 |
+
def __init__(self, position, genome):
|
32 |
+
self.position = position
|
33 |
+
self.genome = genome
|
34 |
+
self.carrying_food = False
|
35 |
+
self.energy = 100
|
36 |
+
self.memory = deque(maxlen=20)
|
37 |
+
self.path_home = []
|
38 |
+
self.role = "explorer"
|
39 |
+
self.communication_range = 10
|
40 |
+
self.q_table = np.zeros((GRID_SIZE, GRID_SIZE, 4))
|
41 |
+
|
42 |
+
def perceive_environment(self, pheromone_grid, ants):
|
43 |
+
self.food_pheromone = pheromone_grid[self.position[0], self.position[1], 0]
|
44 |
+
self.danger_pheromone = pheromone_grid[self.position[0], self.position[1], 1]
|
45 |
+
self.exploration_pheromone = pheromone_grid[self.position[0], self.position[1], 2]
|
46 |
+
|
47 |
+
# Perceive nearby ants
|
48 |
+
self.nearby_ants = [ant for ant in ants if distance.euclidean(self.position, ant.position) <= self.communication_range]
|
49 |
+
|
50 |
+
def act(self, pheromone_grid):
|
51 |
+
possible_actions = self.get_possible_actions()
|
52 |
+
|
53 |
+
if random.random() < self.genome['exploration_rate']:
|
54 |
+
action = random.choice(possible_actions)
|
55 |
+
else:
|
56 |
+
action = np.argmax(self.q_table[self.position[0], self.position[1], possible_actions])
|
57 |
+
|
58 |
+
reward = self.calculate_reward()
|
59 |
+
self.update_q_table(action, reward)
|
60 |
+
|
61 |
+
return action
|
62 |
+
|
63 |
+
def calculate_reward(self):
|
64 |
+
if self.carrying_food:
|
65 |
+
return 10
|
66 |
+
elif self.position in FOOD_SOURCES:
|
67 |
+
return 20
|
68 |
+
elif self.position in OBSTACLES:
|
69 |
+
return -10
|
70 |
+
else:
|
71 |
+
return -1 + self.food_pheromone - self.danger_pheromone + 0.5 * self.exploration_pheromone
|
72 |
+
|
73 |
+
def update_q_table(self, action, reward):
|
74 |
+
self.q_table[self.position[0], self.position[1], action] = (
|
75 |
+
(1 - self.genome['learning_rate']) * self.q_table[self.position[0], self.position[1], action] +
|
76 |
+
self.genome['learning_rate'] * (reward + self.genome['discount_factor'] * np.max(self.q_table[self.position[0], self.position[1]]))
|
77 |
+
)
|
78 |
+
|
79 |
+
def get_possible_actions(self):
|
80 |
+
return list(env_graph.neighbors(self.position))
|
81 |
+
|
82 |
+
def update(self, pheromone_grid, ants):
|
83 |
+
self.perceive_environment(pheromone_grid, ants)
|
84 |
+
action = self.act(pheromone_grid)
|
85 |
+
self.position = action
|
86 |
+
|
87 |
+
self.energy -= 1
|
88 |
+
if self.energy <= 0:
|
89 |
+
return False # Ant dies
|
90 |
+
|
91 |
+
if self.carrying_food:
|
92 |
+
pheromone_grid[self.position[0], self.position[1], 0] += 5
|
93 |
+
if self.position == (0, 0): # Drop food at nest
|
94 |
+
self.carrying_food = False
|
95 |
+
self.energy = min(100, self.energy + 50)
|
96 |
+
return True # Food collected successfully
|
97 |
+
|
98 |
+
if self.position in FOOD_SOURCES and not self.carrying_food:
|
99 |
+
self.carrying_food = True
|
100 |
+
pheromone_grid[self.position[0], self.position[1], 0] += 10
|
101 |
+
|
102 |
+
if self.position in OBSTACLES:
|
103 |
+
pheromone_grid[self.position[0], self.position[1], 1] += 5
|
104 |
+
|
105 |
+
pheromone_grid[self.position[0], self.position[1], 2] += 1 # Exploration pheromone
|
106 |
+
|
107 |
+
self.memory.append(self.position)
|
108 |
+
|
109 |
+
# Update role based on situation
|
110 |
+
if self.carrying_food:
|
111 |
+
self.role = "carrier"
|
112 |
+
elif self.food_pheromone > 5:
|
113 |
+
self.role = "follower"
|
114 |
+
else:
|
115 |
+
self.role = "explorer"
|
116 |
+
|
117 |
+
# Path planning
|
118 |
+
if self.carrying_food and not self.path_home:
|
119 |
+
self.path_home = nx.shortest_path(env_graph, self.position, (0, 0))
|
120 |
+
|
121 |
+
return True # Ant survives
|
122 |
+
|
123 |
+
# Pheromone Diffusion using Convolution
|
124 |
+
def diffuse_pheromones(pheromone_grid):
|
125 |
+
kernel = np.array([[0.05, 0.1, 0.05],
|
126 |
+
[0.1, 0.4, 0.1],
|
127 |
+
[0.05, 0.1, 0.05]])
|
128 |
+
for i in range(3): # For each pheromone type
|
129 |
+
pheromone_grid[:,:,i] = np.convolve2d(pheromone_grid[:,:,i], kernel, mode='same', boundary='wrap')
|
130 |
+
|
131 |
+
# Genetic Algorithm
|
132 |
+
def crossover(parent1, parent2):
|
133 |
+
child = {}
|
134 |
+
for key in parent1.keys():
|
135 |
+
if random.random() < 0.5:
|
136 |
+
child[key] = parent1[key]
|
137 |
+
else:
|
138 |
+
child[key] = parent2[key]
|
139 |
+
return child
|
140 |
+
|
141 |
+
def mutate(genome):
|
142 |
+
for key in genome.keys():
|
143 |
+
if random.random() < MUTATION_RATE:
|
144 |
+
genome[key] += random.uniform(-0.1, 0.1)
|
145 |
+
genome[key] = max(0, min(1, genome[key]))
|
146 |
+
return genome
|
147 |
+
|
148 |
+
# Simulation Loop
|
149 |
+
def simulate(ants):
|
150 |
+
global pheromone_grid
|
151 |
+
food_collected = 0
|
152 |
+
for ant in ants:
|
153 |
+
if ant.update(pheromone_grid, ants):
|
154 |
+
if ant.position == (0, 0) and not ant.carrying_food:
|
155 |
+
food_collected += 1
|
156 |
+
|
157 |
+
pheromone_grid *= (1 - PHEROMONE_DECAY_RATE)
|
158 |
+
diffuse_pheromones(pheromone_grid)
|
159 |
+
|
160 |
+
# Genetic Algorithm
|
161 |
+
if len(ants) > MAX_ANTS:
|
162 |
+
ants.sort(key=lambda x: x.energy, reverse=True)
|
163 |
+
survivors = ants[:MAX_ANTS//2]
|
164 |
+
new_ants = []
|
165 |
+
while len(new_ants) < MAX_ANTS//2:
|
166 |
+
parent1, parent2 = random.sample(survivors, 2)
|
167 |
+
child_genome = crossover(parent1.genome, parent2.genome)
|
168 |
+
child_genome = mutate(child_genome)
|
169 |
+
new_ant = Ant((random.randint(0, GRID_SIZE-1), random.randint(0, GRID_SIZE-1)), child_genome)
|
170 |
+
new_ants.append(new_ant)
|
171 |
+
ants = survivors + new_ants
|
172 |
+
|
173 |
+
return ants, food_collected
|
174 |
+
|
175 |
+
# Clustering for strategic analysis
|
176 |
+
def analyze_ant_clusters(ants):
|
177 |
+
positions = np.array([ant.position for ant in ants])
|
178 |
+
kmeans = KMeans(n_clusters=3)
|
179 |
+
kmeans.fit(positions)
|
180 |
+
return kmeans.cluster_centers_
|
181 |
+
|
182 |
+
# Visualization Functions
|
183 |
+
def plot_environment(pheromone_grid, ants, cluster_centers):
|
184 |
+
fig, ax = plt.subplots(figsize=(10, 10))
|
185 |
+
ax.imshow(np.sum(pheromone_grid, axis=2), cmap='viridis', alpha=0.7)
|
186 |
+
|
187 |
+
for ant in ants:
|
188 |
+
color = 'blue' if ant.role == 'explorer' else 'red' if ant.role == 'carrier' else 'green'
|
189 |
+
ax.plot(ant.position[1], ant.position[0], 'o', color=color, markersize=4)
|
190 |
+
|
191 |
+
for food_x, food_y in FOOD_SOURCES:
|
192 |
+
ax.plot(food_y, food_x, 'go', markersize=10)
|
193 |
+
|
194 |
+
for obstacle_x, obstacle_y in OBSTACLES:
|
195 |
+
ax.plot(obstacle_y, obstacle_x, 'ro', markersize=10)
|
196 |
+
|
197 |
+
for center in cluster_centers:
|
198 |
+
ax.plot(center[1], center[0], 'mo', markersize=15, alpha=0.7)
|
199 |
+
|
200 |
+
ax.set_xlim([0, GRID_SIZE])
|
201 |
+
ax.set_ylim([GRID_SIZE, 0])
|
202 |
+
return fig
|
203 |
+
|
204 |
+
# Streamlit App
|
205 |
+
st.title("Advanced Ant Hivemind Simulation")
|
206 |
+
|
207 |
+
# Sidebar controls
|
208 |
+
st.sidebar.header("Simulation Parameters")
|
209 |
+
num_ants = st.sidebar.slider("Number of Ants", 10, MAX_ANTS, 50)
|
210 |
+
exploration_rate = st.sidebar.slider("Exploration Rate", 0.0, 1.0, 0.2)
|
211 |
+
learning_rate = st.sidebar.slider("Learning Rate", 0.0, 1.0, 0.1)
|
212 |
+
discount_factor = st.sidebar.slider("Discount Factor", 0.0, 1.0, 0.9)
|
213 |
+
|
214 |
+
# Initialize ants
|
215 |
+
ants = [Ant((random.randint(0, GRID_SIZE-1), random.randint(0, GRID_SIZE-1)),
|
216 |
+
{'exploration_rate': exploration_rate,
|
217 |
+
'learning_rate': learning_rate,
|
218 |
+
'discount_factor': discount_factor})
|
219 |
+
for _ in range(num_ants)]
|
220 |
+
|
221 |
+
# Simulation control
|
222 |
+
start_simulation = st.sidebar.button("Start Simulation")
|
223 |
+
stop_simulation = st.sidebar.button("Stop Simulation")
|
224 |
+
reset_simulation = st.sidebar.button("Reset Simulation")
|
225 |
+
|
226 |
+
# Main simulation loop
|
227 |
+
if start_simulation:
|
228 |
+
total_food_collected = 0
|
229 |
+
iterations = 0
|
230 |
+
cluster_centers = np.array([[0, 0], [0, 0], [0, 0]])
|
231 |
+
|
232 |
+
progress_bar = st.progress(0)
|
233 |
+
stats_placeholder = st.empty()
|
234 |
+
plot_placeholder = st.empty()
|
235 |
+
|
236 |
+
while not stop_simulation:
|
237 |
+
ants, food_collected = simulate(ants)
|
238 |
+
total_food_collected += food_collected
|
239 |
+
iterations += 1
|
240 |
+
|
241 |
+
if iterations % 10 == 0:
|
242 |
+
cluster_centers = analyze_ant_clusters(ants)
|
243 |
+
|
244 |
+
if iterations % 5 == 0:
|
245 |
+
progress_bar.progress(min(iterations / 1000, 1.0))
|
246 |
+
stats_placeholder.write(f"Iterations: {iterations}, Total Food Collected: {total_food_collected}")
|
247 |
+
fig = plot_environment(pheromone_grid, ants, cluster_centers)
|
248 |
+
plot_placeholder.pyplot(fig)
|
249 |
+
plt.close(fig)
|
250 |
+
|
251 |
+
if reset_simulation:
|
252 |
+
pheromone_grid = np.zeros((GRID_SIZE, GRID_SIZE, 3))
|
253 |
+
ants = [Ant((random.randint(0, GRID_SIZE-1), random.randint(0, GRID_SIZE-1)),
|
254 |
+
{'exploration_rate': exploration_rate,
|
255 |
+
'learning_rate': learning_rate,
|
256 |
+
'discount_factor': discount_factor})
|
257 |
+
for _ in range(num_ants)]
|
258 |
+
|
259 |
+
# Display final statistics
|
260 |
+
st.write("## Final Statistics")
|
261 |
+
st.write(f"Total Food Collected: {total_food_collected}")
|
262 |
+
st.write(f"Average Food per Iteration: {total_food_collected / iterations if iterations > 0 else 0}")
|
263 |
+
|
264 |
+
# Display heatmap of pheromone concentration
|
265 |
+
st.write("## Pheromone Concentration Heatmap")
|
266 |
+
fig, ax = plt.subplots(figsize=(10, 10))
|
267 |
+
heatmap = ax.imshow(np.sum(pheromone_grid, axis=2), cmap='hot', interpolation='nearest')
|
268 |
+
plt.colorbar(heatmap)
|
269 |
+
st.pyplot(fig)
|
270 |
+
|
271 |
+
# Display ant role distribution
|
272 |
+
roles = [ant.role for ant in ants]
|
273 |
+
role_counts = {role: roles.count(role) for role in set(roles)}
|
274 |
+
st.write("## Ant Role Distribution")
|
275 |
+
st.bar_chart(role_counts)
|
276 |
+
|
277 |
+
# Display network graph of ant communication
|
278 |
+
st.write("## Ant Communication Network")
|
279 |
+
G = nx.Graph()
|
280 |
+
for ant in ants:
|
281 |
+
G.add_node(ant.position)
|
282 |
+
for nearby_ant in ant.nearby_ants:
|
283 |
+
G.add_edge(ant.position, nearby_ant.position)
|
284 |
+
|
285 |
+
fig, ax = plt.subplots(figsize=(10, 10))
|
286 |
+
pos = nx.spring_layout(G)
|
287 |
+
nx.draw(G, pos, with_labels=False, node_size=30, node_color='skyblue', edge_color='gray', ax=ax)
|
288 |
+
st.pyplot(fig)
|