|
import streamlit as st |
|
import numpy as np |
|
import random |
|
import matplotlib.pyplot as plt |
|
from scipy.spatial import distance |
|
from sklearn.cluster import KMeans |
|
import networkx as nx |
|
from collections import deque, Counter |
|
from scipy.signal import convolve2d |
|
|
|
|
|
GRID_SIZE = 200 |
|
FOOD_SOURCES = [(20, 20), (80, 80), (150, 150), (40, 160), (180, 30)] |
|
OBSTACLES = [(50, 50), (100, 100), (150, 50), (70, 130), (120, 80)] |
|
PHEROMONE_DECAY_RATE = 0.02 |
|
PHEROMONE_DIFFUSION_RATE = 0.05 |
|
MAX_ANTS = 100 |
|
MUTATION_RATE = 0.01 |
|
|
|
|
|
pheromone_grid = np.zeros((GRID_SIZE, GRID_SIZE, 3)) |
|
|
|
|
|
env_graph = nx.grid_2d_graph(GRID_SIZE, GRID_SIZE) |
|
|
|
|
|
for obstacle in OBSTACLES: |
|
env_graph.remove_node(obstacle) |
|
|
|
class HiveMind: |
|
def __init__(self): |
|
self.collective_memory = {} |
|
self.global_strategy = {} |
|
self.task_allocation = {} |
|
self.pheromone_importance = {'food': 0.5, 'danger': 0.3, 'exploration': 0.2} |
|
|
|
def update_collective_memory(self, ant_memories): |
|
for ant_memory in ant_memories: |
|
for position, info in ant_memory: |
|
if position not in self.collective_memory: |
|
self.collective_memory[position] = info |
|
else: |
|
old_info = self.collective_memory[position] |
|
new_info = tuple((old + new) / 2 for old, new in zip(old_info, info)) |
|
self.collective_memory[position] = new_info |
|
|
|
def update_global_strategy(self, ant_performances): |
|
best_ants = sorted(ant_performances, key=lambda x: x[1], reverse=True)[:5] |
|
self.global_strategy = { |
|
'exploration_rate': np.mean([ant.genome['exploration_rate'] for ant, _ in best_ants]), |
|
'learning_rate': np.mean([ant.genome['learning_rate'] for ant, _ in best_ants]), |
|
'discount_factor': np.mean([ant.genome['discount_factor'] for ant, _ in best_ants]) |
|
} |
|
|
|
def allocate_tasks(self, ants): |
|
ant_positions = [ant.position for ant in ants] |
|
clusters = KMeans(n_clusters=min(5, len(ants))).fit(ant_positions) |
|
for i, ant in enumerate(ants): |
|
cluster = clusters.labels_[i] |
|
if cluster not in self.task_allocation: |
|
self.task_allocation[cluster] = [] |
|
self.task_allocation[cluster].append(ant) |
|
|
|
def get_swarm_decision(self, decisions): |
|
return Counter(decisions).most_common(1)[0][0] |
|
|
|
class Ant: |
|
def __init__(self, position, genome, hivemind): |
|
self.position = position |
|
self.genome = genome |
|
self.hivemind = hivemind |
|
self.carrying_food = False |
|
self.energy = 100 |
|
self.memory = deque(maxlen=20) |
|
self.path_home = [] |
|
self.role = "explorer" |
|
self.communication_range = 10 |
|
self.q_table = {} |
|
self.performance = 0 |
|
self.cluster = None |
|
|
|
def perceive_environment(self, pheromone_grid, ants): |
|
self.food_pheromone = pheromone_grid[self.position[0], self.position[1], 0] |
|
self.danger_pheromone = pheromone_grid[self.position[0], self.position[1], 1] |
|
self.exploration_pheromone = pheromone_grid[self.position[0], self.position[1], 2] |
|
|
|
self.nearby_ants = [ant for ant in ants if distance.euclidean(self.position, ant.position) <= self.communication_range] |
|
|
|
def act(self, pheromone_grid): |
|
possible_actions = self.get_possible_actions() |
|
|
|
if random.random() < self.genome['exploration_rate']: |
|
action = random.choice(possible_actions) |
|
else: |
|
nearby_ants = [ant for ant in self.hivemind.task_allocation.get(self.cluster, []) |
|
if distance.euclidean(self.position, ant.position) <= self.communication_range] |
|
if nearby_ants: |
|
swarm_decisions = [ant.decide(pheromone_grid) for ant in nearby_ants] |
|
action = self.hivemind.get_swarm_decision(swarm_decisions) |
|
else: |
|
q_values = [self.get_q_value(action) for action in possible_actions] |
|
action = possible_actions[np.argmax(q_values)] |
|
|
|
reward = self.calculate_reward() |
|
self.update_q_table(action, reward) |
|
self.performance += reward |
|
|
|
return action |
|
|
|
def decide(self, pheromone_grid): |
|
possible_actions = self.get_possible_actions() |
|
q_values = [self.get_q_value(action) for action in possible_actions] |
|
return possible_actions[np.argmax(q_values)] |
|
|
|
def get_q_value(self, action): |
|
return self.q_table.get((self.position, action), 0) |
|
|
|
def update_q_table(self, action, reward): |
|
current_q = self.get_q_value(action) |
|
max_future_q = max([self.get_q_value(future_action) for future_action in self.get_possible_actions()]) |
|
|
|
new_q = (1 - self.genome['learning_rate']) * current_q + \ |
|
self.genome['learning_rate'] * (reward + self.genome['discount_factor'] * max_future_q) |
|
|
|
self.q_table[(self.position, action)] = new_q |
|
|
|
def calculate_reward(self): |
|
base_reward = -1 |
|
if self.carrying_food: |
|
base_reward += 10 |
|
if self.position in FOOD_SOURCES: |
|
base_reward += 20 |
|
if self.position in OBSTACLES: |
|
base_reward -= 10 |
|
|
|
pheromone_reward = ( |
|
self.hivemind.pheromone_importance['food'] * self.food_pheromone + |
|
self.hivemind.pheromone_importance['danger'] * -self.danger_pheromone + |
|
self.hivemind.pheromone_importance['exploration'] * self.exploration_pheromone |
|
) |
|
return base_reward + pheromone_reward |
|
|
|
def get_possible_actions(self): |
|
x, y = self.position |
|
possible_actions = [] |
|
for dx, dy in [(0, 1), (1, 0), (0, -1), (-1, 0)]: |
|
new_x, new_y = x + dx, y + dy |
|
if 0 <= new_x < GRID_SIZE and 0 <= new_y < GRID_SIZE and (new_x, new_y) not in OBSTACLES: |
|
possible_actions.append((new_x, new_y)) |
|
return possible_actions |
|
|
|
def update(self, pheromone_grid, ants): |
|
self.perceive_environment(pheromone_grid, ants) |
|
action = self.act(pheromone_grid) |
|
self.position = action |
|
|
|
self.energy -= 1 |
|
if self.energy <= 0: |
|
return False |
|
|
|
if self.carrying_food: |
|
pheromone_grid[self.position[0], self.position[1], 0] += 5 |
|
if self.position == (0, 0): |
|
self.carrying_food = False |
|
self.energy = min(100, self.energy + 50) |
|
return True |
|
|
|
if self.position in FOOD_SOURCES and not self.carrying_food: |
|
self.carrying_food = True |
|
pheromone_grid[self.position[0], self.position[1], 0] += 10 |
|
|
|
if self.position in OBSTACLES: |
|
pheromone_grid[self.position[0], self.position[1], 1] += 5 |
|
|
|
pheromone_grid[self.position[0], self.position[1], 2] += 1 |
|
|
|
self.memory.append((self.position, (self.food_pheromone, self.danger_pheromone, self.exploration_pheromone))) |
|
|
|
|
|
self.role = self.hivemind.task_allocation.get(self.cluster, ["explorer"])[0] |
|
|
|
|
|
if self.carrying_food and not self.path_home: |
|
self.path_home = nx.shortest_path(env_graph, self.position, (0, 0)) |
|
|
|
return True |
|
|
|
|
|
def diffuse_pheromones(pheromone_grid): |
|
kernel = np.array([[0.05, 0.1, 0.05], |
|
[0.1, 0.4, 0.1], |
|
[0.05, 0.1, 0.05]]) |
|
for i in range(3): |
|
pheromone_grid[:,:,i] = convolve2d(pheromone_grid[:,:,i], kernel, mode='same', boundary='wrap') |
|
|
|
|
|
def crossover(parent1, parent2): |
|
child = {} |
|
for key in parent1.keys(): |
|
if random.random() < 0.5: |
|
child[key] = parent1[key] |
|
else: |
|
child[key] = parent2[key] |
|
return child |
|
|
|
def mutate(genome): |
|
for key in genome.keys(): |
|
if random.random() < MUTATION_RATE: |
|
genome[key] += random.uniform(-0.1, 0.1) |
|
genome[key] = max(0, min(1, genome[key])) |
|
return genome |
|
|
|
|
|
def simulate(ants, hivemind): |
|
global pheromone_grid |
|
food_collected = 0 |
|
|
|
hivemind.allocate_tasks(ants) |
|
|
|
for ant in ants: |
|
if ant.update(pheromone_grid, ants): |
|
if ant.position == (0, 0) and not ant.carrying_food: |
|
food_collected += 1 |
|
|
|
pheromone_grid *= (1 - PHEROMONE_DECAY_RATE) |
|
diffuse_pheromones(pheromone_grid) |
|
|
|
hivemind.update_collective_memory([ant.memory for ant in ants]) |
|
hivemind.update_global_strategy([(ant, ant.performance) for ant in ants]) |
|
|
|
|
|
if len(ants) > MAX_ANTS: |
|
ants.sort(key=lambda x: x.performance, reverse=True) |
|
survivors = ants[:MAX_ANTS//2] |
|
new_ants = [] |
|
while len(new_ants) < MAX_ANTS//2: |
|
parent1, parent2 = random.sample(survivors, 2) |
|
child_genome = crossover(parent1.genome, parent2.genome) |
|
child_genome = mutate(child_genome) |
|
child_genome = {**child_genome, **hivemind.global_strategy} |
|
new_ant = Ant((random.randint(0, GRID_SIZE-1), random.randint(0, GRID_SIZE-1)), child_genome, hivemind) |
|
new_ants.append(new_ant) |
|
ants = survivors + new_ants |
|
|
|
return ants, food_collected |
|
|
|
|
|
def plot_environment(pheromone_grid, ants, cluster_centers): |
|
fig, ax = plt.subplots(figsize=(10, 10)) |
|
ax.imshow(np.sum(pheromone_grid, axis=2), cmap='viridis', alpha=0.7) |
|
|
|
for ant in ants: |
|
color = 'blue' if ant.role == 'explorer' else 'red' if ant.role == 'carrier' else 'green' |
|
ax.plot(ant.position[1], ant.position[0], 'o', color=color, markersize=4) |
|
|
|
for food_x, food_y in FOOD_SOURCES: |
|
ax.plot(food_y, food_x, 'go', markersize=10) |
|
|
|
for obstacle_x, obstacle_y in OBSTACLES: |
|
ax.plot(obstacle_y, obstacle_x, 'ro', markersize=10) |
|
|
|
for center in cluster_centers: |
|
ax.plot(center[1], center[0], 'mo', markersize=15, alpha=0.7) |
|
|
|
ax.set_xlim([0, GRID_SIZE]) |
|
ax.set_ylim([GRID_SIZE, 0]) |
|
return fig |
|
|
|
|
|
st.title("Advanced Ant Hivemind Simulation") |
|
|
|
|
|
st.sidebar.header("Simulation Parameters") |
|
num_ants = st.sidebar.slider("Number of Ants", 10, MAX_ANTS, 50) |
|
exploration_rate = st.sidebar.slider("Exploration Rate", 0.0, 1.0, 0.2) |
|
learning_rate = st.sidebar.slider("Learning Rate", 0.0, 1.0, 0.1) |
|
discount_factor = st.sidebar.slider("Discount Factor", 0.0, 1.0, 0.9) |
|
|
|
|
|
hivemind = HiveMind() |
|
ants = [Ant((random.randint(0, GRID_SIZE-1), random.randint(0, GRID_SIZE-1)), |
|
{'exploration_rate': exploration_rate, |
|
'learning_rate': learning_rate, |
|
'discount_factor': discount_factor}, |
|
hivemind) |
|
for _ in range(num_ants)] |
|
|
|
|
|
start_simulation = st.sidebar.button("Start Simulation") |
|
stop_simulation = st.sidebar.button("Stop Simulation") |
|
reset_simulation = st.sidebar.button("Reset Simulation") |
|
|
|
|
|
total_food_collected = 0 |
|
iterations = 0 |
|
|
|
|
|
if start_simulation: |
|
cluster_centers = np.array([[0, 0], [0, 0], [0, 0]]) |
|
|
|
progress_bar = st.progress(0) |
|
stats_placeholder = st.empty() |
|
plot_placeholder = st.empty() |
|
|
|
while not stop_simulation: |
|
ants, food_collected = simulate(ants, hivemind) |
|
total_food_collected += food_collected |
|
iterations += 1 |
|
|
|
if iterations % 10 == 0: |
|
cluster_centers = hivemind.allocate_tasks(ants) |
|
|
|
if iterations % 5 == 0: |
|
progress_bar.progress(min(iterations / 1000, 1.0)) |
|
stats_placeholder.write(f"Iterations: {iterations}, Total Food Collected: {total_food_collected}") |
|
fig = plot_environment(pheromone_grid, ants, cluster_centers) |
|
plot_placeholder.pyplot(fig) |
|
plt.close(fig) |
|
|
|
if reset_simulation: |
|
pheromone_grid = np.zeros((GRID_SIZE, GRID_SIZE, 3)) |
|
hivemind = HiveMind() |
|
ants = [Ant((random.randint(0, GRID_SIZE-1), random.randint(0, GRID_SIZE-1)), |
|
{'exploration_rate': exploration_rate, |
|
'learning_rate': learning_rate, |
|
'discount_factor': discount_factor}, |
|
hivemind) |
|
for _ in range(num_ants)] |
|
total_food_collected = 0 |
|
iterations = 0 |
|
|
|
|
|
if iterations > 0: |
|
st.write("## Final Statistics") |
|
st.write(f"Total Food Collected: {total_food_collected}") |
|
st.write(f"Average Food per Iteration: {total_food_collected / iterations}") |
|
|
|
|
|
st.write("## Pheromone Concentration Heatmap") |
|
fig, ax = plt.subplots(figsize=(10, 10)) |
|
heatmap = ax.imshow(np.sum(pheromone_grid, axis=2), cmap='hot', interpolation='nearest') |
|
plt.colorbar(heatmap) |
|
st.pyplot(fig) |
|
|
|
|
|
roles = [ant.role for ant in ants] |
|
role_counts = {role: roles.count(role) for role in set(roles)} |
|
st.write("## Ant Role Distribution") |
|
st.bar_chart(role_counts) |
|
|
|
|
|
st.write("## Ant Communication Network") |
|
G = nx.Graph() |
|
for ant in ants: |
|
G.add_node(ant.position) |
|
for nearby_ant in ant.nearby_ants: |
|
G.add_edge(ant.position, nearby_ant.position) |
|
|
|
fig, ax = plt.subplots(figsize=(10, 10)) |
|
pos = nx.spring_layout(G) |
|
nx.draw(G, pos, with_labels=False, node_size=30, node_color='skyblue', edge_color='gray', ax=ax) |
|
st.pyplot(fig) |
|
|
|
|
|
st.write("## Hivemind Collective Memory") |
|
memory_grid = np.zeros((GRID_SIZE, GRID_SIZE)) |
|
for pos, info in hivemind.collective_memory.items(): |
|
memory_grid[pos[0], pos[1]] = np.mean(info) |
|
|
|
fig, ax = plt.subplots(figsize=(10, 10)) |
|
heatmap = ax.imshow(memory_grid, cmap='viridis', interpolation='nearest') |
|
plt.colorbar(heatmap) |
|
st.pyplot(fig) |
|
|
|
|
|
st.write("## Global Strategy Evolution") |
|
strategy_df = pd.DataFrame(hivemind.global_strategy, index=[0]) |
|
st.line_chart(strategy_df) |
|
|
|
|
|
st.write("## Ant Performance Distribution") |
|
performances = [ant.performance for ant in ants] |
|
fig, ax = plt.subplots() |
|
ax.hist(performances, bins=20) |
|
ax.set_xlabel('Performance') |
|
ax.set_ylabel('Number of Ants') |
|
st.pyplot(fig) |
|
|
|
|
|
st.write("## Task Allocation") |
|
task_df = pd.DataFrame.from_dict(hivemind.task_allocation, orient='index') |
|
task_df = task_df.applymap(lambda x: len(x) if x else 0) |
|
st.bar_chart(task_df) |
|
|
|
|
|
st.write(""" |
|
## About this Simulation |
|
|
|
This advanced ant hivemind simulation demonstrates several key concepts in swarm intelligence and collective behavior: |
|
|
|
1. **Collective Decision Making**: Ants make decisions based on both individual and swarm intelligence. |
|
2. **Adaptive Strategies**: The hivemind evolves its strategy based on the performance of the best ants. |
|
3. **Distributed Task Allocation**: Ants are dynamically assigned to different tasks based on their location and the colony's needs. |
|
4. **Emergent Behavior**: Complex colony-level behaviors emerge from simple individual ant rules. |
|
5. **Information Sharing**: Ants share information through pheromones and direct communication. |
|
6. **Collective Memory**: The hivemind maintains a collective memory of the environment. |
|
|
|
This simulation showcases how simple agents, when working together with the right rules, can exhibit complex and intelligent behavior at the group level. |
|
""") |