-
Notifications
You must be signed in to change notification settings - Fork 0
/
fish abm model with predator. 3D Generations - 10 runs_GA.py
345 lines (263 loc) · 13.5 KB
/
fish abm model with predator. 3D Generations - 10 runs_GA.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
import numpy as np
import random
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
# Parameters
NUM_FISH = 20
PREDATOR_PERCEPTION_RADIUS = 70
PREDATOR_MAX_SPEED = 10
PREDATOR_CATCH_RANGE = 15
PREDATOR_REFLEX = 1.1
PREDATOR_VELCITY_SCALE = 0.5
WIDTH, HEIGHT, DEPTH = 100, 100, 100
STEPS = 100
class Genome:
def __init__(self, perception_radius, attraction_dist, repulsion_dist, max_speed, speed_boost, acc_throttle):
self.perception_radius = perception_radius
self.attraction_dist = attraction_dist
self.repulsion_dist = repulsion_dist
self.max_speed = max_speed
self.speed_boost = speed_boost
self.acc_throttle = acc_throttle
def create_initial_population(size):
population = []
for _ in range(size):
genome = Genome(
perception_radius=random.uniform(10, 25),
attraction_dist=random.uniform(1, 20),
repulsion_dist=random.uniform(1, 20),
max_speed=random.uniform(1, 2.5),
speed_boost=random.uniform(1.1, 2.5),
acc_throttle=random.uniform(1.1, 3.5)
)
population.append(genome)
return population
# roulette wheel selection
def select(population, fitness_scores):
total_fitness = sum(fitness_scores)
selection_probabilities = [fitness / total_fitness for fitness in fitness_scores]
selected_population = []
for _ in range(len(population) // 2):
selected_population.append(population[np.random.choice(len(population), p=selection_probabilities)])
return selected_population
def crossover(parent1, parent2):
# Randomly select two crossover points
crossover_points = sorted(random.sample(range(6), 2))
# Create offspring by combining genes from parents
child1_genes = [
parent1.perception_radius, parent1.attraction_dist, parent1.repulsion_dist,
parent1.max_speed, parent1.speed_boost, parent1.acc_throttle
]
child2_genes = [
parent2.perception_radius, parent2.attraction_dist, parent2.repulsion_dist,
parent2.max_speed, parent2.speed_boost, parent2.acc_throttle
]
# Swap the genes between the crossover points
child1_genes[crossover_points[0]:crossover_points[1]], child2_genes[crossover_points[0]:crossover_points[1]] = \
child2_genes[crossover_points[0]:crossover_points[1]], child1_genes[crossover_points[0]:crossover_points[1]]
# Create children from the gene lists
child1 = Genome(*child1_genes)
child2 = Genome(*child2_genes)
return child1, child2
def mutate(genome, mutation_rate=0.1):
if random.random() < mutation_rate:
genome.perception_radius *= random.uniform(0.9, 1.1)
genome.attraction_dist *= random.uniform(0.9, 1.1)
genome.repulsion_dist *= random.uniform(0.9, 1.1)
genome.max_speed *= random.uniform(0.9, 1.1)
genome.speed_boost *= random.uniform(0.9, 1.1)
genome.acc_throttle *= random.uniform(0.9, 1.1)
def calculate_cohesion(fishes):
positions = np.array([fish.position for fish in fishes])
center_of_mass = np.mean(positions, axis=0)
distances = np.linalg.norm(positions - center_of_mass, axis=1)
return np.mean(distances)
def calculate_separation(fishes):
distances = []
for fish in fishes:
nearest_neighbor = fish.find_nearest_neighbor(fishes)
if nearest_neighbor:
distance = np.linalg.norm(fish.position - nearest_neighbor.position)
distances.append(distance)
return np.mean(distances) if distances else 0
def calculate_alignment(fishes):
alignments = []
for fish in fishes:
nearest_neighbor = fish.find_nearest_neighbor(fishes)
if nearest_neighbor:
alignment = np.dot(fish.velocity / np.linalg.norm(fish.velocity),
nearest_neighbor.velocity / np.linalg.norm(nearest_neighbor.velocity))
alignments.append(alignment)
return np.mean(alignments) if alignments else 0
class Predator:
def __init__(self):
self.position = np.random.rand(3) * np.array([WIDTH, HEIGHT, DEPTH])
self.velocity = np.random.randn(3) * PREDATOR_VELCITY_SCALE
def update_position(self):
self.position += self.velocity
self.position = self.position % np.array([WIDTH, HEIGHT, DEPTH])
def hunt(self, fishes):
distances = [np.linalg.norm(fish.position - self.position) for fish in fishes]
if distances:
nearest_fish = fishes[np.argmin(distances)]
distance_to_nearest = np.min(distances)
if distance_to_nearest < PREDATOR_PERCEPTION_RADIUS:
acceleration_vector = (nearest_fish.position - self.position) / distance_to_nearest
self.velocity += acceleration_vector * PREDATOR_REFLEX
# Limiting the predator's speed
if np.linalg.norm(self.velocity) > PREDATOR_MAX_SPEED:
self.velocity = self.velocity / np.linalg.norm(self.velocity) * PREDATOR_MAX_SPEED
if distance_to_nearest < PREDATOR_CATCH_RANGE: # Predator catches the fish
return nearest_fish
return None
class Fish:
def __init__(self, x, y, z, vx, vy, vz, genome):
self.position = np.array([x, y, z])
self.velocity = np.array([vx, vy, vz])
self.genome = genome
def update_position(self):
self.position += self.velocity
# Boundary conditions
self.position = self.position % np.array([WIDTH, HEIGHT, DEPTH])
def apply_behaviors(self, fishes, predator):
nearest_neighbor = self.find_nearest_neighbor(fishes)
if nearest_neighbor is not None:
self.apply_attraction(nearest_neighbor)
self.apply_repulsion(nearest_neighbor)
self.react_to_predator(predator)
def react_to_predator(self, predator):
if np.linalg.norm(predator.position - self.position) < self.genome.perception_radius:
# Increase speed away from the predator
escape_direction = self.position - predator.position
self.velocity += escape_direction / np.linalg.norm(escape_direction) * (self.genome.max_speed / self.genome.acc_throttle)
# Limit speed
if np.linalg.norm(self.velocity) > self.genome.max_speed * self.genome.speed_boost:
self.velocity = self.velocity / np.linalg.norm(self.velocity) * self.genome.max_speed * self.genome.speed_boost
def find_nearest_neighbor(self, fishes):
distances = [np.linalg.norm(fish.position - self.position) for fish in fishes if fish != self]
if distances:
nearest_neighbor = fishes[np.argmin(distances)]
if np.min(distances) < self.genome.perception_radius:
return nearest_neighbor
return None
def apply_attraction(self, neighbor):
if np.linalg.norm(neighbor.position - self.position) > self.genome.attraction_dist:
self.velocity += (neighbor.position - self.position) / self.genome.attraction_dist
def apply_repulsion(self, neighbor):
if np.linalg.norm(neighbor.position - self.position) < self.genome.repulsion_dist:
self.velocity -= (neighbor.position - self.position) / self.genome.repulsion_dist
# Limiting the speed
if np.linalg.norm(self.velocity) > self.genome.max_speed:
self.velocity = self.velocity / np.linalg.norm(self.velocity) * self.genome.max_speed
def run_simulation_with_genome(genome):
fishes = [
Fish(
np.random.rand() * WIDTH,
np.random.rand() * HEIGHT,
np.random.rand() * DEPTH,
np.random.randn(),
np.random.randn(),
np.random.randn(),
genome
) for _ in range(NUM_FISH)
]
predator = Predator()
for _ in range(STEPS):
caught_fish = predator.hunt(fishes)
if caught_fish:
fishes.remove(caught_fish)
for fish in fishes:
fish.apply_behaviors(fishes, predator)
fish.update_position()
return len(fishes)
# ... Selection, Crossover, Mutation Functions ...
# Updated function to run the genetic algorithm, collect data, and print progress messages
def run_genetic_algorithm_for_analysis_with_logging(runs=1, generations=30, population_size=20):
# Data structures to store traits and survival rates for each generation across all runs
traits_data = {
'perception_radius': [[] for _ in range(generations)],
'attraction_dist': [[] for _ in range(generations)],
'repulsion_dist': [[] for _ in range(generations)],
'max_speed': [[] for _ in range(generations)],
'speed_boost': [[] for _ in range(generations)],
'acc_throttle': [[] for _ in range(generations)],
'survival_rate': [[] for _ in range(generations)]
}
best_genomes = [] # To store the final best genome of each run for the correlation analysis
for run in range(runs):
population = create_initial_population(population_size)
for generation in range(generations):
# Calculate survival rate
survival_rates = [run_simulation_with_genome(genome) / NUM_FISH * 100 for genome in population]
avg_survival_rate = np.mean(survival_rates)
traits_data['survival_rate'][generation].append(avg_survival_rate)
# Record traits for this generation
for trait in traits_data.keys():
if trait != 'survival_rate':
traits_data[trait][generation].append(np.mean([getattr(genome, trait) for genome in population]))
# Selection, Crossover, Mutation
parents = select(population, survival_rates) # Assuming survival rate is used as fitness score
next_generation = []
for _ in range(len(population) // 2):
parent1, parent2 = random.sample(parents, 2)
offspring1, offspring2 = crossover(parent1, parent2)
mutate(offspring1)
mutate(offspring2)
next_generation.extend([offspring1, offspring2])
population = next_generation
# Logging progress
print(f"Run {run + 1}/{runs}, Generation {generation + 1}/{generations} completed.")
# Calculating average values for each trait for each generation across all runs
average_traits_over_generations = {
trait: [np.mean(generation_values) for generation_values in traits_data[trait]]
for trait in traits_data
}
# Identify the generation with the highest average survival rate
best_generation_index = np.argmax(average_traits_over_generations['survival_rate'])
# Extract the average traits for the best generation
best_generation_traits = {trait: average_traits_over_generations[trait][best_generation_index] for trait in traits_data if trait != 'survival_rate'}
best_generation_avg_survival_rate = average_traits_over_generations['survival_rate'][best_generation_index]
return average_traits_over_generations, best_generation_traits, best_generation_avg_survival_rate
# Running the modified genetic algorithm to collect data for analysis with logging
average_traits_data, best_generation_traits, best_generation_avg_survival_rate = run_genetic_algorithm_for_analysis_with_logging(runs=10, generations=10, population_size=16)
print("Average Traits Data:" + str(average_traits_data))
# Plotting the distribution of each trait and survival rate across generations
# Plotting traits
# Number of subplots required (6 traits + 1 survival rate)
num_plots = len(average_traits_data)
num_rows = (num_plots // 3) + (num_plots % 3 > 0) # Calculate number of rows needed
# Create subplots
fig, axs = plt.subplots(num_rows, 3, figsize=(15, num_rows * 5))
fig.suptitle('Average Traits and Survival Rate Across Generations', fontsize=16)
# Flatten the axes array for easy indexing
axs = axs.flatten()
# Plot each trait in a separate subplot
for i, (trait, values) in enumerate(average_traits_data.items()):
axs[i].plot(values, label=trait.replace('_', ' ').title())
axs[i].set_title(trait.replace('_', ' ').title())
axs[i].set_xlabel('Generation')
axs[i].set_ylabel('Average Value')
axs[i].legend()
# Hide any unused subplots
for j in range(i + 1, len(axs)):
axs[j].set_visible(False)
plt.tight_layout(pad=4.0)
plt.show()
# Calculate the average for each trait per generation
avg_traits_per_generation = {
trait: [np.mean(generation_values) if generation_values else np.nan for generation_values in average_traits_data[trait]]
for trait in average_traits_data
}# Converting to DataFrame for correlation analysis
df_traits_per_generation = pd.DataFrame(average_traits_data)
# Calculate the correlation matrix
correlation_matrix = df_traits_per_generation.corr()
# Generate the correlation heatmap
plt.figure(figsize=(12, 10))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', square=True)
plt.title('Correlation Heatmap of Average Traits and Survival Rate Per Generation')
plt.show()
print("Best Generation's Average Traits:")
for trait, value in best_generation_traits.items():
print(f"{trait}: {value}")
print(f"Average Survival Rate: {best_generation_avg_survival_rate}%")