VoxelEngine/labirinth_ai/Population.py

104 lines
3.9 KiB
Python
Raw Permalink Normal View History

2022-08-12 15:48:30 +02:00
import random
import numpy as np
2022-11-14 11:18:57 +01:00
from labirinth_ai.Models import EvolutionModel
2022-08-12 15:48:30 +02:00
from labirinth_ai.Models.Genotype import NeatLike
def fib(n):
if n == 0:
return [1]
elif n < 0:
return [0]
else:
return [fib(n - 1)[0] + fib(n - 2)[0]] + fib(n - 1)
class Population:
2022-11-14 11:18:57 +01:00
def __init__(self, subject_class, world, subject_number, do_evolve=True):
2022-08-12 15:48:30 +02:00
self.subjects = []
self.world = world
for _ in range(subject_number):
px, py = self.world.generate_free_coordinates()
self.subjects.append(subject_class(px, py, genotype_class=NeatLike))
self.subject_number = subject_number
self.subject_class = subject_class
2022-11-14 11:18:57 +01:00
self.do_evolve = do_evolve
2022-08-12 15:48:30 +02:00
def select(self):
ranked = list(self.subjects)
ranked.sort(key=lambda subject: subject.accumulated_rewards, reverse=True)
return ranked[:int(self.subject_number / 2)]
@classmethod
def scatter(cls, n, buckets):
out = np.zeros(buckets)
if n == 0:
return out
fib_number = 0
fibs = fib(fib_number)
while np.sum(fibs) <= n and len(fibs) <= buckets:
fib_number += 1
fibs = fib(fib_number)
fib_number -= 1
fibs = fib(fib_number)
for bucket in range(buckets):
if bucket < len(fibs):
out[bucket] += fibs[bucket]
else:
break
return out + cls.scatter(n - np.sum(fibs), buckets)
def evolve(self):
2022-11-14 11:18:57 +01:00
if self.do_evolve:
if len(self.subjects) > 1:
# get updated weights from the models
for subject in self.subjects:
subject.model.update_genes_with_weights()
# crossbreed the current pop
best_subjects = self.select()
distribution = list(self.scatter(self.subject_number - int(self.subject_number / 2), int(self.subject_number / 2)))
new_subjects = list(best_subjects)
for index, offspring_num in enumerate(distribution):
for _ in range(int(offspring_num)):
parent_1 = best_subjects[index]
parent_2 = best_subjects[random.randint(index + 1, len(best_subjects) - 1)]
new_genes = parent_1.model.genes.cross(parent_2.model.genes,
parent_1.accumulated_rewards, parent_2.accumulated_rewards)
# position doesn't matter, since mutation will set it
new_subject = self.subject_class(0, 0, new_genes)
new_subject.history = parent_1.history
new_subject.samples = parent_1.samples + parent_2.samples
new_subjects.append(new_subject)
assert len(new_subjects) == self.subject_number, 'All generations should have constant size!'
else:
new_subjects = self.subjects
# mutate the pop
mutated_subjects = []
innovation_num = max(map(lambda subject: max(map(lambda connection: connection.innvovation_num,
subject.model.genes.connections
)
2022-08-12 15:48:30 +02:00
)
2022-11-14 11:18:57 +01:00
, new_subjects))
for subject in new_subjects:
subject.accumulated_rewards = 0
2022-08-12 15:48:30 +02:00
2022-11-14 11:18:57 +01:00
innovation_num = subject.model.genes.mutate(innovation_num)
2022-08-12 15:48:30 +02:00
2022-11-14 11:18:57 +01:00
px, py = self.world.generate_free_coordinates()
new_subject = self.subject_class(px, py, subject.model.genes)
new_subject.history = subject.history
new_subject.samples = subject.samples
mutated_subjects.append(new_subject)
2022-08-12 15:48:30 +02:00
2022-11-14 11:18:57 +01:00
self.subjects = mutated_subjects