def __init__(self, conditions: Conditions, simulation: Simulation, load_file=None, screen=None): """ The Neat Application runs the Neat algorithm on a simulation, using the given conditions :param conditions: The conditions to use when running the algorithm :param simulation: The simulation Neat will be running :param load_file: A file to load previous data from """ self.simulation: Simulation = simulation self.conditions: Conditions = conditions self.past: List[Generation] = [] self.screen = screen self.log_file = "scores/score_%d.csv" % time() if load_file is None: gene_pool = GenePool(0, simulation.get_data_size() + 1, {}) genomes = self.start_genomes(gene_pool, conditions) population = Population([]) population.add_all_genomes(genomes, conditions) population.clear_empty_species() self.current_generation: Generation = Generation( 0, population, gene_pool.next()) else: raise NotImplementedError("Saving is coming soon")
def load(string) -> Generation: generation_count, string = remove_tag("generation_count", string) population_str, string = remove_tag("population", string) gene_pool_str, string = remove_tag("gene_pool", string) population = Population.load(population_str) gene_pool = GenePool.load(gene_pool_str) return Generation(int(generation_count), population, gene_pool)
def add_connection(self, gene_pool: GenePool, conditions: Conditions) -> Genome: """ Creates a new genome with a randomly generated connection :param gene_pool: The GenePool which provides the innovation number for the new connection :param conditions: The Conditions which control how the range for the weights of the new connection :return: Returns a copy of the current genome, but with a new connection """ conditions.new_connection_count += 1 endings = [] starts = self.start_nodes + self.middle_nodes start_node = None while (not endings) and starts: start_node = random.choice(starts) starts.remove(start_node) endings = list( filter( lambda end_node: gene_pool.get_depth( end_node) > gene_pool.get_depth(start_node), self.middle_nodes + self.start_nodes)) used_genes = (filter(lambda gene: gene.in_node == start_node, self.genes)) for gene in used_genes: if gene.out_node in endings: endings.remove(gene.out_node) if endings: end_node = random.choice(endings) new_gene = Gene( random.random() * (conditions.gene_max_weight - conditions.gene_min_weight) + conditions.gene_min_weight, start_node, end_node, 0, gene_pool=gene_pool) new_genes = list(map(Gene.copy, self.genes)) new_genes.append(new_gene) return Genome(new_genes, self.input_size, self.output_size, gene_pool) else: conditions.new_connection_count -= 1 # raise NetworkFullError("add connection") # print("""Network full""") return self
def start_genomes(self, gene_pool: GenePool, conditions: Conditions) -> List[Genome]: """ Creates the starter genomes :param conditions: The conditions to use when creating new genes :param gene_pool: The gene pool to update with the starter genomes :return: A list of starter genomes """ in_size = self.simulation.get_data_size() out_size = self.simulation.get_controls_size() for in_ in range(1, in_size + 1): gene_pool.node_depths[in_] = conditions.app_start_node_depth for out_ in range(0, -out_size, -1): gene_pool.node_depths[out_] = conditions.app_end_node_depth starter_genomes = [] starter_genes = [] for in_ in range(1, in_size + 1): for out_ in range(0, -out_size, -1): gene = Gene(random.random() * (self.conditions.gene_max_weight - self.conditions.gene_min_weight) + self.conditions.gene_min_weight, in_, out_, 0, gene_pool=gene_pool) starter_genes.append(gene) for i in range(self.conditions.population_size): new_genes = [gene.copy() for gene in starter_genes] for gene in new_genes: gene.weight = (random.random() * (self.conditions.gene_max_weight - self.conditions.gene_min_weight) + self.conditions.gene_min_weight) starter_genomes.append( Genome(new_genes, in_size, out_size, gene_pool)) return starter_genomes
def process_genes(genes: List[Gene], input_size: int, output_size: int, gene_pool: GenePool) \ -> Tuple[np.array, np.array, int, List[int]]: """ Processes Genes to produce a weight Adjacency matrix and an Enabled matrix, as well as the nodes that are not input or output nodes :param genes: The Genes to convert :param input_size: The number of input nodes :param output_size: The number of output nodes :param gene_pool: The GenePool which has data on the depth of nodes, which creates the ordering :return: A Tuple containing, Weight Adjacency Matrix, Enabled Adjacency Matrix, Number of middle nodes, and the list of middle nodes """ # print("PROCESSING In:", '\n\t'.join([str(gene) for gene in genes])) # print("PROCESSING In:", input_size, output_size) nodes = set() middles = set() for connection_gene in genes: nodes.add(connection_gene.in_node) nodes.add(connection_gene.out_node) if connection_gene.in_node > input_size: middles.add(connection_gene.in_node) if connection_gene.out_node > 0: middles.add(connection_gene.out_node) list(map(nodes.add, range(1, input_size + 1))) list(map(nodes.add, range(0, -output_size, -1))) # print("PROCESSING In:", # list(map(nodes.add, range(1, input_size + 1))), # list(map(nodes.add, range(0, -output_size, -1)))) nodes = list(nodes) nodes_with_depth = list( map(lambda node: (gene_pool.get_depth(node), node), nodes)) nodes.sort() nodes_with_depth.sort() node_indices = {} for i in range(len(nodes_with_depth)): node_indices[nodes_with_depth[i][1]] = i middle_size = len(middles) enabled_matrix = np.zeros( (input_size + middle_size, middle_size + output_size), dtype=bool) weight_matrix = np.zeros( (input_size + middle_size, middle_size + output_size)) for gene in genes: if gene.enabled: start = node_indices[gene.in_node] end = node_indices[gene.out_node] - input_size enabled_matrix[start][end] = True weight_matrix[start][end] = gene.weight # print("PROCESSING OUT:", weight_matrix.shape, enabled_matrix.shape, middle_size, list(middles), middles) return weight_matrix, enabled_matrix, middle_size, list(middles)
def add_node(self, gene_pool: GenePool, conditions: Conditions) -> Genome: """ Creates a new genome. Splits a randomly selected connection into two connections which share a new node The connection which starts at the original connections in_node is the in connection The connection which ends at the original connections out_node is the out connection Of the new connections, the in connection will have a weight of one The out connection will have the original connections weight The original connection is disabled :param conditions: Added for the node count :param gene_pool: The GenePool which provides the innovation numbers for the new connections, and the node number for the new node :return: Returns a copy of the current genome, but with a connection split with a new node added """ conditions.new_node_count += 1 splitting_gene: Gene = random.choice(self.genes) new_node = gene_pool.get_node_number(splitting_gene) in_gene = Gene(1.0, splitting_gene.in_node, new_node, 0, gene_pool=gene_pool) out_gene = Gene(splitting_gene.weight, new_node, splitting_gene.out_node, 0, gene_pool=gene_pool) new_genes = [] for gene in self.genes: if gene == splitting_gene: disabled_gene = gene.copy() disabled_gene.enabled = False new_genes.append(disabled_gene) else: new_genes.append(gene.copy()) new_genes.append(in_gene) new_genes.append(out_gene) return Genome(new_genes, self.input_size, self.output_size, gene_pool)
def __init__(self, weight: float, in_node: int, out_node: int, innovation_number: int, enabled: bool = True, gene_pool: GenePool.GenePool = None): """ The gene class represents a connection gene, a connection between two nodes in a Network :param weight: The weight is the strength of the connection :param in_node: The in node is the node the connection starts at :param out_node: The out node the node the connection ends at :param innovation_number: The innovation number is a number used to track gene's history :param enabled: The enabled bit is used to identify if a connection is active """ self.weight: float = weight self.in_node: int = in_node self.out_node: int = out_node self.innovation_number: int = innovation_number self.enabled: bool = enabled if gene_pool: self.innovation_number = gene_pool.get_innovation_number( self.to_structure_gene())