def configure_crossover(self, genome1, genome2, config): """ Configure a new genome by crossover from two parent genomes. """ assert isinstance(genome1.fitness, (int, float)) assert isinstance(genome2.fitness, (int, float)) if genome1.fitness > genome2.fitness: parent1, parent2 = genome1, genome2 else: parent1, parent2 = genome2, genome1 # Inherit connection genes for key, cg1 in iteritems(parent1.connections): cg2 = parent2.connections.get(key) if cg2 is None: # Excess or disjoint gene: copy from the fittest parent. self.connections[key] = cg1.copy() else: # Homologous gene: combine genes from both parents. self.connections[key] = cg1.crossover(cg2) # Inherit node genes parent1_set = parent1.nodes parent2_set = parent2.nodes for key, ng1 in iteritems(parent1_set): ng2 = parent2_set.get(key) assert key not in self.nodes if ng2 is None: # Extra gene: copy from the fittest parent self.nodes[key] = ng1.copy() else: # Homologous gene: combine genes from both parents. self.nodes[key] = ng1.crossover(ng2)
def post_evaluate(self, config, population, species, best_genome): self.most_fit_genomes.append(copy.deepcopy(best_genome)) # Store the fitnesses of the members of each currently active species. species_stats = {} #species_cross_validation_stats = {} for sid, s in iteritems(species.species): species_stats[sid] = dict((k, v.fitness) for k, v in iteritems(s.members)) ##species_cross_validation_stats[sid] = dict((k, v.cross_fitness) for ## k, v in iteritems(s.members)) self.generation_statistics.append(species_stats)
def distance(self, other, config): """ Returns the genetic distance between this genome and the other. This distance value is used to compute genome compatibility for speciation. """ # Compute node gene distance component. node_distance = 0.0 if self.nodes or other.nodes: disjoint_nodes = 0 for k2 in iterkeys(other.nodes): if k2 not in self.nodes: disjoint_nodes += 1 for k1, n1 in iteritems(self.nodes): n2 = other.nodes.get(k1) if n2 is None: disjoint_nodes += 1 else: # Homologous genes compute their own distance value. node_distance += n1.distance(n2, config) max_nodes = max(len(self.nodes), len(other.nodes)) node_distance = (node_distance + config.compatibility_disjoint_coefficient * disjoint_nodes) / max_nodes # Compute connection gene differences. connection_distance = 0.0 if self.connections or other.connections: disjoint_connections = 0 for k2 in iterkeys(other.connections): if k2 not in self.connections: disjoint_connections += 1 for k1, c1 in iteritems(self.connections): c2 = other.connections.get(k1) if c2 is None: disjoint_connections += 1 else: # Homologous genes compute their own distance value. connection_distance += c1.distance(c2, config) max_conn = max(len(self.connections), len(other.connections)) connection_distance = (connection_distance + config.compatibility_disjoint_coefficient * disjoint_connections) / max_conn distance = node_distance + connection_distance return distance
def create(genome, config, time_constant): """ Receives a genome and returns its phenotype (a CTRNN). """ genome_config = config.genome_config required = required_for_output(genome_config.input_keys, genome_config.output_keys, genome.connections) # Gather inputs and expressed connections. node_inputs = {} for cg in itervalues(genome.connections): if not cg.enabled: continue i, o = cg.key if o not in required and i not in required: continue if o not in node_inputs: node_inputs[o] = [(i, cg.weight)] else: node_inputs[o].append((i, cg.weight)) node_evals = {} for node_key, inputs in iteritems(node_inputs): node = genome.nodes[node_key] activation_function = genome_config.activation_defs.get(node.activation) aggregation_function = genome_config.aggregation_function_defs.get(node.aggregation) node_evals[node_key] = CTRNNNodeEval(time_constant, activation_function, aggregation_function, node.bias, node.response, inputs) return CTRNN(genome_config.input_keys, genome_config.output_keys, node_evals)
def advance(self, inputs, advance_time, time_step=None): """ Advance the simulation by the given amount of time, assuming that inputs are constant at the given values during the simulated time. """ final_time_seconds = self.time_seconds + advance_time # Use half of the max allowed time step if none is given. if time_step is None: # pragma: no cover time_step = 0.5 * self.get_max_time_step() if len(self.input_nodes) != len(inputs): raise RuntimeError("Expected {0} inputs, got {1}".format(len(self.input_nodes), len(inputs))) while self.time_seconds < final_time_seconds: dt = min(time_step, final_time_seconds - self.time_seconds) ivalues = self.values[self.active] ovalues = self.values[1 - self.active] self.active = 1 - self.active for i, v in zip(self.input_nodes, inputs): ivalues[i] = v ovalues[i] = v for node_key, ne in iteritems(self.node_evals): node_inputs = [ivalues[i] * w for i, w in ne.links] s = ne.aggregation(node_inputs) z = ne.activation(ne.bias + ne.response * s) ovalues[node_key] += dt / ne.time_constant * (-ovalues[node_key] + z) self.time_seconds += dt ovalues = self.values[1 - self.active] return [ovalues[i] for i in self.output_nodes]
def create_circuit(genome, config): libraries_path = '/home/alan/ngspice/libraries' # os.path.join(os.path.dirname(os.path.dirname(__file__)), 'libraries') spice_library = SpiceLibrary(libraries_path) circuit = Circuit('NEAT') circuit.include(spice_library['1N4148']) Vbase = circuit.V('base', 'input1', circuit.gnd, 2) Vcc = circuit.V('cc', 'input2', circuit.gnd, 5) Vgnd = circuit.V('gnd', 'input3', circuit.gnd, 0) #circuit.R('test1', 'node0', circuit.gnd, 1e6) #circuit.R('test2', 'node0', 'input1', 1e6) ridx = 1 xidx = 1 for key, c in iteritems(genome.connections): if c.component == 'resistor': pin0, pin1 = get_pins(key) R = 10**c.value circuit.R(ridx, pin1, pin0, R) ridx += 1 elif c.component == 'diode': pin0, pin1 = get_pins(key) circuit.X(xidx, '1N4148', pin1, pin0) xidx += 1 return circuit
def __str__(self): s = "Key: {0}\nFitness: {1}\nNodes:".format(self.key, self.fitness) for k, ng in iteritems(self.nodes): s += "\n\t{0} {1!s}".format(k, ng) s += "\nConnections:" connections = list(self.connections.values()) connections.sort() for c in connections: s += "\n\t" + str(c) return s
def __str__(self): s = "Nodes:" for k, ng in iteritems(self.nodes): s += "\n\t{0} {1!s}".format(k, ng) s += "\nConnections:" connections = list(self.connections.values()) connections.sort() for c in connections: s += "\n\t" + str(c) return s
def mutate_delete_node(self, config): # Do nothing if there are no non-output nodes. available_nodes = [(k, v) for k, v in iteritems(self.nodes) if k not in config.output_keys] if not available_nodes: return -1 del_key, del_node = choice(available_nodes) connections_to_delete = set() for k, v in iteritems(self.connections): if del_key in v.key: connections_to_delete.add(v.key) for key in connections_to_delete: del self.connections[key] del self.nodes[del_key] return del_key
def update(self, species_set, generation): """ Required interface method. Updates species fitness history information, checking for ones that have not improved in max_stagnation generations, and - unless it would result in the number of species dropping below the configured species_elitism parameter if they were removed, in which case the highest-fitness species are spared - returns a list with stagnant species marked for removal. """ species_data = [] for sid, s in iteritems(species_set.species): if s.fitness_history: prev_fitness = max(s.fitness_history) else: prev_fitness = -sys.float_info.max s.fitness = self.species_fitness_func(s.get_fitnesses()) s.fitness_history.append(s.fitness) s.adjusted_fitness = None if prev_fitness is None or s.fitness > prev_fitness: s.last_improved = generation species_data.append((sid, s)) # Sort in ascending fitness order. species_data.sort(key=lambda x: x[1].fitness) result = [] species_fitnesses = [] num_non_stagnant = len(species_data) for idx, (sid, s) in enumerate(species_data): # Override stagnant state if marking this species as stagnant would # result in the total number of species dropping below the limit. # Because species are in ascending fitness order, less fit species # will be marked as stagnant first. stagnant_time = generation - s.last_improved is_stagnant = False if num_non_stagnant > self.stagnation_config.species_elitism: is_stagnant = stagnant_time >= self.stagnation_config.max_stagnation if (len(species_data) - idx) <= self.stagnation_config.species_elitism: is_stagnant = False if is_stagnant: num_non_stagnant -= 1 result.append((sid, s, is_stagnant)) species_fitnesses.append(s.fitness) return result
def __init__(self, inputs, outputs, node_evals): self.input_nodes = inputs self.output_nodes = outputs self.node_evals = node_evals self.values = [{}, {}] for v in self.values: for k in inputs + outputs: v[k] = 0.0 for node, ne in iteritems(self.node_evals): v[node] = 0.0 for i, w in ne.links: v[i] = 0.0 self.active = 0 self.time_seconds = 0.0
def run(self, fitness_function, n=None): """ Runs NEAT's genetic algorithm for at most n generations. If n is None, run until solution is found or extinction occurs. The user-provided fitness_function must take only two arguments: 1. The population as a list of (genome id, genome) tuples. 2. The current configuration object. The return value of the fitness function is ignored, but it must assign a Python float to the `fitness` member of each genome. The fitness function is free to maintain external state, perform evaluations in parallel, etc. It is assumed that fitness_function does not modify the list of genomes, the genomes themselves (apart from updating the fitness member), or the configuration object. """ if self.config.no_fitness_termination and (n is None): raise RuntimeError( "Cannot have no generational limit with no fitness termination" ) k = 0 while n is None or k < n: k += 1 self.reporters.start_generation(self.generation) # Evaluate all genomes using the user-provided function. fitness_function(list(iteritems(self.population)), self.config) # Gather and report statistics. best = None for g in itervalues(self.population): if best is None or g.fitness > best.fitness: best = g self.reporters.post_evaluate(self.config, self.population, self.species, best) # Track the best genome ever seen. if self.best_genome is None or best.fitness > self.best_genome.fitness: self.best_genome = best if not self.config.no_fitness_termination: # End if the fitness threshold is reached. fv = self.fitness_criterion( g.fitness for g in itervalues(self.population)) if fv >= self.config.fitness_threshold: self.reporters.found_solution(self.config, self.generation, best) break # Create the next generation from the current generation. self.population = self.reproduction.reproduce( self.config, self.species, self.config.pop_size, self.generation) # Check for complete extinction. if not self.species.species: self.reporters.complete_extinction() # If requested by the user, create a completely new population, # otherwise raise an exception. if self.config.reset_on_extinction: self.population = self.reproduction.create_new( self.config.genome_type, self.config.genome_config, self.config.pop_size) else: raise CompleteExtinctionException() # Divide the new population into species. self.species.speciate(self.config, self.population, self.generation) self.reporters.end_generation(self.config, self.population, self.species) self.generation += 1 if self.config.no_fitness_termination: self.reporters.found_solution(self.config, self.generation, self.best_genome) return self.best_genome
def __init__(self, name, **default_dict): self.name = name for n, default in iteritems(default_dict): self._config_items[n] = [self._config_items[n][0], default] for n in iterkeys(self._config_items): setattr(self, n + "_name", self.config_item_name(n))
def speciate(self, config, population, generation): """ Place genomes into species by genetic similarity. Note that this method assumes the current representatives of the species are from the old generation, and that after speciation has been performed, the old representatives should be dropped and replaced with representatives from the new generation. If you violate this assumption, you should make sure other necessary parts of the code are updated to reflect the new behavior. """ assert isinstance(population, dict) compatibility_threshold = self.species_set_config.compatibility_threshold # Find the best representatives for each existing species. unspeciated = set(iterkeys(population)) distances = GenomeDistanceCache(config.genome_config) new_representatives = {} new_members = {} for sid, s in iteritems(self.species): candidates = [] for gid in unspeciated: g = population[gid] d = distances(s.representative, g) candidates.append((d, g)) # The new representative is the genome closest to the current representative. ignored_rdist, new_rep = min(candidates, 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 population into species based on genetic similarity. while unspeciated: gid = unspeciated.pop() g = population[gid] # Find the species with the most similar representative. candidates = [] for sid, rid in iteritems(new_representatives): rep = population[rid] d = distances(rep, g) if d < compatibility_threshold: candidates.append((d, sid)) if candidates: ignored_sdist, sid = min(candidates, key=lambda x: x[0]) new_members[sid].append(gid) else: # No species is similar enough, create a new species, using # this genome as its representative. sid = next(self.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): s = self.species.get(sid) if s is None: s = Species(sid, generation) self.species[sid] = s members = new_members[sid] for gid in members: self.genome_to_species[gid] = sid member_dict = dict((gid, population[gid]) for gid in members) s.update(population[rid], member_dict) gdmean = mean(itervalues(distances.distances)) gdstdev = stdev(itervalues(distances.distances)) self.reporters.info( 'Mean genetic distance {0:.3f}, standard deviation {1:.3f}'.format( gdmean, gdstdev))