Source code for pyneat.species

"""Divides a population into species based on genetic distance.
"""
from itertools import count
import math
import statistics

from neat.config import ConfigParameter, DefaultClassConfig, Config


[docs]class GenomeDistanceCache(object): def __init__(self, config): self.distances = {} self.hits = 0 self.misses = 0 def __call__(self, genome0, genome1): g0 = genome0.key g1 = genome1.key d = self.distances.get((g0, g1)) if d is None: # Distance is not already computed. d = genome0.distance(genome1) self.distances[g0, g1] = d self.distances[g1, g0] = d self.misses += 1 else: self.hits += 1 return d
[docs]class Species: """Encapsulates all information about a particular species. Attributes: key (int): A unique identifier for the species. created (int): The generation in which the species was created. last_improved (int): The last generation where the fitness of the species improved. members (dict): A dictionary of {genome ID: genome} pairs for each genome in the species. representative (Genome): The genome that is the representative of the species, against which new genomes will be compared to see if they belong in this species. fitness (float): The species fitness. adjusted_fitness (float): The sum of the adjusted fitnesses for each genome in the species. fitness_history (:list:`float`): All previous fitness values. One for each generation this species has survived for. """ def __init__(self, key, generation): """Create a new Species object. Args: key (int): A unique identifier for the species. generation (int): The current generation. """ self.key = key self.created = generation self.last_improved = generation self.members = {} self.representative = None self.fitness = None self.adjusted_fitness = None self.fitness_history = [] def __eq__(self, other): """Check to see if another species is equal to this one. Args: other (Species): The other species to compare. Returns: bool: True if the two species are equal, False otherwise. """ self_attr = (self.key, self.created, self.last_improved, self.members, self.representative, self.fitness, self.adjusted_fitness, self.fitness_history) other_attr = (other.key, other.created, other.last_improved, other.members, other.representative, other.fitness, other.adjusted_fitness, other.fitness_history) return self_attr == other_attr
[docs] def update(self, representative, members): """Replace the current individuals with a new set of individuals. Args: representative (Genome): The genome that is the new representative for this species. members (dict): A dictionary of genome ID and genome pairs of the new members of the species. """ self.representative = representative.copy() self.members = members
[docs] def get_fitnesses(self): """Get the fitnesses of each genome that belongs to this species. Returns: list: The fitness of each genome that belongs to this species. """ return [m.fitness for m in self.members.values()]
[docs]class SpeciesSet(DefaultClassConfig): """Encapsulates the speciation scheme. Attributes: species_set_config (DefaultClassConfig): The speciation configuration. reporters (ReporterSet): The set of reporters that log events. species_key_generator (generator): Keeps track of the next species ID. species (dict): A dictionary of species ID, species pairs. genome_to_species (dict): A dictionary of genome ID, species ID pairs. """ def __init__(self, config, reporters): """Creates a new SpeciesSet object. Args: config (DefaultClassConfig): The speciation configuration. reporters (ReporterSet): The set of reporters that log events. """ self.species_set_config = config self.reporters = reporters self.species_key_generator = count(0) self.species = {} self.genome_to_species = {}
[docs] @classmethod def parse_config(cls, param_dict): """Parse the speciation parameter values Args: param_dict (dict): A dictionary of parameter values. Returns: DefaultClassConfig: The speciation configuration. """ return DefaultClassConfig(param_dict, [ConfigParameter('compatibility_threshold', float)])
[docs] def speciate(self, config, population, generation): """Speciate the population. Args: config (Config): The global NEAT configuration. population (dict): A dictionary of genome ID, genome pairs. generation (int): The current generation. """ unspeciated = set(population) new_members = {k: [] for k in self.species} # species ID: [genome IDs] # Partition the new population into species while unspeciated: genome_key = unspeciated.pop() genome = population[genome_key] found = False for species_key, species in self.species.items(): # Add genome to the first species it is compatible with distance = genome.distance(species.representative) if distance < config.species_set_config.compatibility_threshold: # Genome belongs to this existing species if new_members[species_key]: new_members[species_key].append(genome_key) else: new_members[species_key] = [genome_key] found = True break if not found: # Genome belongs to a new species species_key = next(self.species_key_generator) species = Species(species_key, generation) self.species[species_key] = species new_members[species_key] = [genome_key] species.update(genome, {}) # Update the representatives and members of each species self.genome_to_species = {} for species_key, members in new_members.items(): species = self.species.get(species_key) members = new_members[species_key] for genome_key in members: self.genome_to_species[genome_key] = species_key if members: # Update the species if there are any members representative = population[members[0]] member_dict = {key: population[key] for key in new_members[species_key]} species.update(representative, member_dict) # Remove species without any members self.species = {k: s for k, s in self.species.items() if s.members}
[docs] def get_species_id(self, genome_key): """Get the species ID of the species the given individual belongs to. Args: genome_key (int): The unique key of the genome to check. Returns: int: The key of the species the individual belongs to. """ return self.genome_to_species[genome_key]
[docs] def get_species(self, genome_key): """Get the species to given individual belongs to. Args: genome_key (int): The unique key of the genome to check. Returns: Species: The species the individual belongs to. """ species_key = self.genome_to_species[genome_key] return self.species[species_key]