def genome_distance(self, genome0, genome1): ''' Computes genome distance between two genomes ''' node_distance = 0.0 # Determine node distance if genome0.nodes or genome1.nodes: # Number of disjoing nodes between genomes disjoint_nodes = 0 # Count number of disjoint node genes between genomes for genome_1_node_key in iterkeys(genome1.nodes): if genome_1_node_key not in genome0.nodes: disjoint_nodes += 1 # Determine genetic distance between individual node genes for genome_0_node_key, genome_0_node in iteritems(genome0.nodes): genome_1_node = genome1.nodes.get(genome_0_node_key) if genome_1_node is None: disjoint_nodes += 1 else: # Homologous genes compute their own distance value. node_distance += self.node_gene_distance( genome_0_node, genome_1_node) # Find most number of nodes in either genome max_nodes = max(len(genome0.nodes), len(genome1.nodes)) # Determine final node genetic distance node_distance = (node_distance + (self.compatibility_disjoint_coefficient * disjoint_nodes)) / max_nodes # Determine connection gene distance connection_distance = 0.0 if genome0.connections or genome1.connections: disjoint_connections = 0 for genome_1_conn_key in iterkeys(genome1.connections): if genome_1_conn_key not in genome0.connections: disjoint_connections += 1 for genome_0_conn_key, genome_0_conn in iteritems( genome0.connections): genome_1_conn = genome1.connections.get(genome_0_conn_key) if genome_1_conn is None: disjoint_connections += 1 else: # Homologous genes compute their own distance value. connection_distance += self.connection_gene_distance( genome_0_conn, genome_1_conn) max_conn = max(len(genome0.connections), len(genome1.connections)) connection_distance = (connection_distance + (self.compatibility_disjoint_coefficient * disjoint_connections)) / max_conn distance = node_distance + connection_distance return distance
def mutate_add_connection(self, gen=None): ''' Mutation for adding a connection gene to the genome. gen -- optional argument for current generation mutation occurs ''' # Gather possible target nodes and source nodes if not self.nodes: return possible_targets = list(iterkeys(self.nodes)) target_key = choice(possible_targets) possible_sources = possible_targets + self.input_keys source_key = choice(possible_sources) # Determine if new connection creates cycles. Currently, only # supports feed forward networks if creates_cycle(self.connections, (source_key, target_key)): return # Ensure connection isn't duplicate if (source_key, target_key) in self.connections: self.connections[(source_key, target_key)].enabled = True return # Don't allow connections between two output nodes if source_key in self.output_keys and target_key in self.output_keys: return new_conn = self.create_connection(source_key, target_key)
def mutate_delete_node(self, gen=None): ''' Mutation for deleting a node gene to the genome. gen -- optional argument for current generation mutation occurs ''' available_nodes = [ k for k in iterkeys(self.nodes) if k not in self.output_keys ] if not available_nodes: return # Choose random node to delete del_key = np.random.choice(available_nodes) # Iterate through all connections and find connections to node conn_to_delete = set() for k, v in iteritems(self.connections): if del_key in v.key: conn_to_delete.add(v.key) for i in conn_to_delete: del self.connections[i] # Delete node key del self.nodes[del_key] return del_key
def speciate(self,population,generation): ''' Speciates a population. ''' # Compatibility threshold compatibility_threshold = self.threshold # Set of unspeciated members of the population unspeciated = set(iterkeys(population)) # Means of determining distances distances = GenomeDistanceCache() # New representatives and members of species new_representatives = {} new_members = {} # Traverse through set of species from last generation for sid, species in iteritems(self.species): # Candidates for current species representatives candidate_representatives = [] # Traverese genomes in the unspeciated and check their distance # from the current species representative for gid in unspeciated: genome = population[gid] genome_distance = distances(species.representative, genome) candidate_representatives.append((genome_distance, genome)) # The new representative for the current species is the # closest to the current representative _, new_rep = min(candidate_representatives, key=lambda x: x[0]) new_rid = new_rep.key new_representatives[sid] = new_rid new_members[sid] = [new_rid] unspeciated.remove(new_rid) # Partition the population in species based on genetic similarity while unspeciated: gid = unspeciated.pop() genome = population[gid] # Find the species with the most similar representative to the # current genome from the unspeciated set candidate_species = [] # Traverse species and their representatives for sid, rid in iteritems(new_representatives): representative = population[rid] # Determine current genome's distance from representative genome_distance = distances(representative, genome) # If it's below threshold, add it to list for adding to the species if genome_distance < compatibility_threshold: candidate_species.append((genome_distance, sid)) # Add current genome to the species its most genetically similar to if candidate_species: _, sid = min(candidate_species, key=lambda x: x[0]) new_members[sid].append(gid) else: # No species is similar enough so we create a mnew species with # the current genome as its representative sid = next(self.species_indexer) new_representatives[sid] = gid new_members[sid] = [gid] # Update species collection based on new speciation self.genome_to_species = {} for sid, rid in iteritems(new_representatives): # Add species if not existing in current species set s = self.species.get(sid) if s is None: s = Species(sid, generation) self.species[sid] = s # Collect and add members to current species members = new_members[sid] for gid in members: self.genome_to_species[gid] = sid # Update current species members and represenative member_dict = {gid:population[gid] for gid in members} s.update(population[rid], member_dict)