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
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
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