Esempio n. 1
0
    def mutate_base_genome(self,
                           genome: Genome,
                           mutation_record: MutationRecords,
                           add_node_chance: float,
                           add_connection_chance: float,
                           allow_disabling_connections: bool = True):
        """performs base neat genome mutations, as well as node and genome property mutations"""
        mutation_report = MutationReport()

        if random.random() < add_node_chance:
            if self.add_node_mutation(genome, mutation_record):
                mutation_report.nodes_added += 1

        if random.random() < add_connection_chance:
            if self.add_connection_mutation(genome, mutation_record):
                mutation_report.connections_created += 1

        if allow_disabling_connections:
            """randomly deactivates and reactivates connections"""
            for connection in genome.connections.values():
                orig_conn = copy.deepcopy(connection)
                result = connection.mutate(
                )  # this is the call which enables/disables connections

                if result.check_mutated():
                    """the connection was mutated"""
                    # If mutation made the genome invalid then undo it
                    if not genome.validate():
                        """
                            disabling the connection lead to a disconnected graph
                            or enabling the connection lead to a cycle
                            - undoing this mutation
                        """
                        genome.connections[orig_conn.id] = orig_conn
                    else:
                        """mutation is valid"""
                        if connection.enabled():
                            mutation_report.connections_enabled += 1
                        else:
                            mutation_report.connections_disabled += 1

        for node in genome.nodes.values():
            """mutates node properties"""
            mutation_report += node.mutate()

        for mutagen in genome.get_all_mutagens():
            """mutates the genome level properties"""
            report = mutagen.mutate()
            if report is None:
                raise Exception("none report returned from mutating " +
                                mutagen.name + ", " + repr(mutagen))
            mutation_report += report

        return mutation_report
Esempio n. 2
0
    def test_and_add_connection(self, genome: Genome,
                                mutation_record: MutationRecords,
                                from_node: Node, to_node: Node):
        """
            Adds a connection between to nodes if possible
            creates a copy genome, adds the node, checks for cycles in the copy
            if no cycles, the connection is added to the original genome

            :returns whether or not the candidate connection was added to the original genome
        """

        copy_genome = copy.deepcopy(genome)

        # Validation
        if from_node.id == to_node.id:
            return False

        candidate_connection = (from_node.id, to_node.id)

        if candidate_connection in genome.connected_nodes:
            # this connection is already in the genome
            return False
        # else:
        #     print("candidate conn", candidate_connection, " not in connections:", genome.connected_nodes,
        #           "nodes:", genome.nodes.keys(), "connections:", genome.connections)

        if from_node.node_type == NodeType.OUTPUT:
            return False

        if to_node.node_type == NodeType.INPUT:
            return False

        # Adding to global mutation dictionary
        if mutation_record.exists(candidate_connection, True):
            mutation_id = mutation_record.connection_mutations[
                candidate_connection]
        else:
            mutation_id = mutation_record.add_mutation(candidate_connection,
                                                       True)

        # Adding new mutation
        mutated_conn = Connection(mutation_id, from_node.id, to_node.id)

        copy_genome.add_connection(mutated_conn)
        if copy_genome.has_cycle():
            # the candidate connection creates a cycle
            return False

        # by now the candidate connection is valid
        genome.add_connection(mutated_conn)

        return True
Esempio n. 3
0
    def test_and_add_node_on_connection(self, genome: Genome,
                                        mutation_record: MutationRecords,
                                        connection: Connection):

        n_mutations_on_conn = genome.n_mutations_on_connection(
            mutation_record, connection.id)
        mutation_id = (connection.id, n_mutations_on_conn)

        if mutation_record.exists(mutation_id, False):
            # this node mutation has occurred before
            # ie: this connection has had a node placed already

            # the id of the original node which was placed on this connection
            mutated_node_id = mutation_record.node_mutations[mutation_id]
            if mutated_node_id in genome.nodes:
                raise Exception(
                    "node id already in genome, but searched for unique node id"
                )

            # the id of the connection which bridges to the new node
            into_node_connection_id = mutation_record.connection_mutations[(
                connection.from_node_id, mutated_node_id)]
            # the id of the connection which bridges from the new node
            out_of_node_connection_id = mutation_record.connection_mutations[(
                mutated_node_id, connection.to_node_id)]
        else:
            # if this mutation hasn't occurred before if should not be in any genome

            mutated_node_id = mutation_record.add_mutation(mutation_id, False)

            if mutated_node_id in genome.nodes:  # this connection has already created a new node
                raise Exception(
                    "tried to mutate a node onto connection " +
                    str(connection.id) + " mutation (node id) given value " +
                    str(mutated_node_id) +
                    " but this value is already present in the genome: " +
                    repr(genome.nodes.keys()) + "\nmutation record: " +
                    repr(mutation_record))

            into_node_connection_id = mutation_record.add_mutation(
                (connection.from_node_id, mutated_node_id), True)
            out_of_node_connection_id = mutation_record.add_mutation(
                (mutated_node_id, connection.to_node_id), True)

        TypeNode = type(list(genome.nodes.values())
                        [0])  # node could be a blueprint, module or da node
        # multiple node objects share the same id. indicating they are topologically the same
        if TypeNode == BlueprintNode and config.blueprint_node_type_switch_chance > random.random(
        ):
            # node switch type applies to new nodes being added to the blueprint
            TypeNode = ModuleNode

        mutated_node = TypeNode(mutated_node_id, NodeType.HIDDEN)

        genome.add_node(mutated_node)

        mutated_from_conn = Connection(into_node_connection_id,
                                       connection.from_node_id,
                                       mutated_node_id)
        mutated_to_conn = Connection(out_of_node_connection_id,
                                     mutated_node_id, connection.to_node_id)

        genome.add_connection(mutated_from_conn)
        genome.add_connection(mutated_to_conn)

        connection.enabled.set_value(False)

        return True
def get_graph_of(genome: Genome,
                 sub_graph=False,
                 cluster_style="filled",
                 cluster_colour="lightgrey",
                 node_style="filled",
                 node_colour: Any = "white",
                 label="",
                 node_shape="",
                 start_node_shape="Mdiamond",
                 end_node_shape="Msquare",
                 node_names="",
                 graph_title_prefix="",
                 graph_title_suffix="",
                 append_graph: Digraph = None,
                 exclude_unconnected_nodes=True,
                 exclude_non_fully_connected_nodes=True,
                 **kwargs) -> Digraph:
    """
    :param genome:
    :param sub_graph: boolean, if this graph should be made a cluster, intended to be a sub_graph of another graph
    :param cluster_style:
    :param cluster_colour:
    :param node_style:
    :param node_colour:
    :param label:
    :param node_names: the name prefix for each of the nodes in this genome
    :param append_graph:the existing graph to be drawn into, instead of into a new graph
    :param exclude_unconnected_nodes:
    :param exclude_non_fully_connected_nodes:
    :return:
    """

    if genome is None:
        raise Exception("null genome passed to grapher")

    if append_graph is None:
        if graph_title_prefix == "":
            name = ("cluster_" if sub_graph else "") + "genome_i" + str(
                genome.id) + graph_title_suffix
        else:
            name = graph_title_prefix + "i" + str(
                genome.id) + "_genome" + graph_title_suffix

        g = Digraph(name=name)
        # print("created graph ", g)
    else:
        g = append_graph

    if exclude_non_fully_connected_nodes:
        connection_set = set(genome.get_fully_connected_connections())
    else:
        """all connections"""
        connection_set = set(genome.connections.values())

    if exclude_unconnected_nodes:
        """all nodes which are attached to an enabled connection"""
        node_set = set([
            genome.nodes[connection.from_node_id]
            for connection in connection_set if connection.enabled()
        ] + [
            genome.nodes[connection.to_node_id]
            for connection in connection_set if connection.enabled()
        ])
    else:
        """all nodes"""
        node_set = set(genome.nodes.values())

    for node in node_set:
        shape = start_node_shape if (
            node.is_input_node() and start_node_shape != "") else (
                end_node_shape if (node.is_output_node()
                                   and end_node_shape != "") else node_shape)

        if "sample_map" in kwargs and kwargs["sample_map"] is not None:
            meta_data = get_node_metadata(node,
                                          sample_map=kwargs["sample_map"])
        else:
            meta_data = get_node_metadata(node)

        _node_colour = node_colour if isinstance(node_colour,
                                                 str) else node_colour(node)
        # print(node, node_colour )

        if shape != "":
            # print("using shape ",shape)
            g.node(name=node_names + "_v " + str(node.id),
                   shape=shape,
                   label=meta_data,
                   fillcolor=_node_colour,
                   style="filled")
        else:
            g.node(name=node_names + "_v " + str(node.id),
                   label=meta_data,
                   fillcolor=_node_colour,
                   style="filled")

        # print("created node: ", (node_names + ": " + str(node.id)) , " id: ", node.id )

    # print("graph after nodes added: " , g)

    for conn in connection_set:
        if not conn.enabled():
            continue

        g.edge((node_names + "_v " + str(conn.from_node_id)),
               (node_names + "_v " + str(conn.to_node_id)))

    # print("graph after edges added: " , g)

    if sub_graph:
        g.attr(style=cluster_style, color=cluster_colour)
        # print("changed subgraph style")
    # g.node_attr.update(style=node_style, color=node_colour)

    g.attr(label=label)

    return g