import logging
import random
from typing import List
import torch
import torch.nn.functional as F
from gridmind.algorithms.evolutionary_rl.neuroevolution.neuro_agent import NeuroAgent
[docs]class Selection:
[docs] logger = logging.getLogger(__name__)
@staticmethod
[docs] def _convert_none_to_numeric(population: List[NeuroAgent]):
"""Set fitness to -inf if None"""
for i in range(len(population)):
if population[i].fitness is None:
Selection.logger.debug(
f"Agent {population[i].name} has no fitness, setting to -inf"
)
population[i].fitness = float("-inf")
return population
@staticmethod
[docs] def fitness_proportionate_selection(
population: List[NeuroAgent], num_selection: int = 1
):
population = Selection._convert_none_to_numeric(population)
fitnesses = [p.fitness for p in population]
fitnesses = F.softmax(
torch.tensor(fitnesses, dtype=torch.float32), dim=0
).tolist()
for i in range(1, len(fitnesses)):
fitnesses[i] = fitnesses[i] + fitnesses[i - 1]
selected = []
for j in range(num_selection):
selected.append(Selection._select_one(population, fitnesses))
return selected
@staticmethod
[docs] def _select_one(population, fitnesses):
n = random.random()
for i in range(1, len(fitnesses)):
if fitnesses[i - 1] < n and fitnesses[i] >= n:
return population[i]
return population[0]
@staticmethod
[docs] def truncation_selection(population: List[NeuroAgent], num_selection: int = 1):
population = Selection._convert_none_to_numeric(population)
population_sorted = sorted(population, key=lambda x: x.fitness, reverse=True)
new_population = population_sorted[:num_selection]
return new_population
@staticmethod
[docs] def random_selection(population: List[NeuroAgent], num_selection: int = 1):
selected = random.sample(population, num_selection)
return selected
if __name__ == "__main__":
[docs] agents = [
NeuroAgent(fitness=None),
NeuroAgent(fitness=1.0),
NeuroAgent(fitness=3.0),
]
s = Selection.fitness_proportionate_selection(agents, 10)
print([a.name for a in s])