Пример #1
0
 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")
Пример #2
0
 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)
Пример #3
0
    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
Пример #4
0
    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
Пример #5
0
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)
Пример #6
0
    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)
Пример #7
0
 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())