Exemple #1
0
def get_circular2(cfg: Config):
    """
    Genome with circular connections, not connected to the output genome.
    Configuration:
       0   1
       
     2---3
     |   |
    -1  -2  -3
    """
    # Create a dummy genome
    genome = Genome(
        key=2,
        num_outputs=cfg.genome.num_outputs,
        bot_config=cfg.bot,
    )
    # Reset the nodes
    genome.nodes[0] = OutputNodeGene(key=0, cfg=cfg.genome)  # OutputNode 0
    genome.nodes[0].bias = 0
    genome.nodes[1] = OutputNodeGene(key=1, cfg=cfg.genome)  # OutputNode 1
    genome.nodes[1].bias = 0
    genome.nodes[2] = SimpleNodeGene(key=2, cfg=cfg.genome)  # Hidden node
    genome.nodes[2].bias = 0
    genome.nodes[3] = SimpleNodeGene(key=3, cfg=cfg.genome)  # Hidden node
    genome.nodes[3].bias = 0

    # Reset the connections
    genome.connections = dict()
    for key in [(-1, 2), (2, 3), (3, 2), (-2, 3)]:
        genome.connections[key] = ConnectionGene(key=key, cfg=cfg.genome)
        genome.connections[key].weight = 1
        genome.connections[key].enabled = True

    return genome
Exemple #2
0
def get_valid1(cfg: Config):
    """
    Simple network with only one input and one output used.
    Configuration:
         0   1
             |
             |
             |
    -1  -2  -3
    """
    # Create a dummy genome
    genome = Genome(
        key=1,
        num_outputs=cfg.genome.num_outputs,
        bot_config=cfg.bot,
    )
    # Reset the nodes
    genome.nodes[0] = OutputNodeGene(key=0, cfg=cfg.genome)  # OutputNode 0
    genome.nodes[0].bias = 0
    genome.nodes[1] = OutputNodeGene(key=1, cfg=cfg.genome)  # OutputNode 1
    genome.nodes[1].bias = 0
    genome.nodes[2] = SimpleNodeGene(key=2, cfg=cfg.genome)  # Hidden node
    genome.nodes[2].bias = 0

    # Reset the connections
    genome.connections = dict()
    for key in [(-3, 1)]:
        genome.connections[key] = ConnectionGene(key=key, cfg=cfg.genome)
        genome.connections[key].weight = 1
        genome.connections[key].enabled = True

    return genome
Exemple #3
0
def get_invalid5(cfg: Config):
    """
    Genome with connections between the hidden nodes and to one output node.
    Configuration:
       0   1
           |
        2--3
     
    -1  -2  -3
    """
    # Create a dummy genome
    genome = Genome(
        key=4,
        num_outputs=cfg.genome.num_outputs,
        bot_config=cfg.bot,
    )
    # Reset the nodes
    genome.nodes[0] = OutputNodeGene(key=0, cfg=cfg.genome)  # OutputNode 0
    genome.nodes[0].bias = 0
    genome.nodes[1] = OutputNodeGene(key=1, cfg=cfg.genome)  # OutputNode 1
    genome.nodes[1].bias = 0
    genome.nodes[2] = SimpleNodeGene(key=2, cfg=cfg.genome)  # Hidden node
    genome.nodes[2].bias = 0
    genome.nodes[3] = SimpleNodeGene(key=3, cfg=cfg.genome)  # Hidden node
    genome.nodes[3].bias = 0

    # Reset the connections
    genome.connections = dict()
    for key in [(2, 3), (3, 2), (3, 1)]:
        genome.connections[key] = ConnectionGene(key=key, cfg=cfg.genome)
        genome.connections[key].weight = 1
        genome.connections[key].enabled = True

    return genome
Exemple #4
0
def get_pruned2(cfg: Config):
    """
    Genome with partially valid connections and nodes (dangling node on other hidden node).
    Configuration:
       0   1
      /
     2---3>
     |
    -1  -2  -3
    """
    # Create a dummy genome
    genome = Genome(
        key=2,
        num_outputs=cfg.genome.num_outputs,
        bot_config=cfg.bot,
    )
    # Reset the nodes
    genome.nodes[0] = OutputNodeGene(key=0, cfg=cfg.genome)  # OutputNode 0
    genome.nodes[0].bias = 0
    genome.nodes[1] = OutputNodeGene(key=1, cfg=cfg.genome)  # OutputNode 1
    genome.nodes[1].bias = 0
    genome.nodes[2] = SimpleNodeGene(key=2, cfg=cfg.genome)  # Hidden node
    genome.nodes[2].bias = 0
    genome.nodes[3] = SimpleNodeGene(key=3, cfg=cfg.genome)  # Hidden node
    genome.nodes[3].bias = 0

    # Reset the connections
    genome.connections = dict()
    for key in [(-1, 2), (2, 0), (2, 3), (3, 3)]:
        genome.connections[key] = ConnectionGene(key=key, cfg=cfg.genome)
        genome.connections[key].weight = 1
        genome.connections[key].enabled = True

    return genome
Exemple #5
0
def get_invalid3(cfg: Config):
    """
    Genome without connections to the input nodes.
    Configuration:
       0   1
           |
           2>
          
    -1  -2  -3
    """
    # Create a dummy genome
    genome = Genome(
        key=3,
        num_outputs=cfg.genome.num_outputs,
        bot_config=cfg.bot,
    )
    # Reset the nodes
    genome.nodes[0] = OutputNodeGene(key=0, cfg=cfg.genome)  # OutputNode 0
    genome.nodes[0].bias = 0
    genome.nodes[1] = OutputNodeGene(key=1, cfg=cfg.genome)  # OutputNode 1
    genome.nodes[1].bias = 0
    genome.nodes[2] = SimpleNodeGene(key=2, cfg=cfg.genome)  # Hidden node
    genome.nodes[2].bias = 0

    # Reset the connections
    genome.connections = dict()
    for key in [(2, 2), (2, 1)]:
        genome.connections[key] = ConnectionGene(key=key, cfg=cfg.genome)
        genome.connections[key].weight = 1
        genome.connections[key].enabled = True

    return genome
Exemple #6
0
def get_invalid4(cfg: Config):
    """
    Genome with connection from start to recurrent node, and from another recurrent node to the output.
    Configuration:
       0   1
           |
     2>    3>
     |
    -1  -2  -3
    """
    # Create a dummy genome
    genome = Genome(
        key=4,
        num_outputs=cfg.genome.num_outputs,
        bot_config=cfg.bot,
    )
    # Reset the nodes
    genome.nodes[0] = OutputNodeGene(key=0, cfg=cfg.genome)  # OutputNode 0
    genome.nodes[0].bias = 0
    genome.nodes[1] = OutputNodeGene(key=1, cfg=cfg.genome)  # OutputNode 1
    genome.nodes[1].bias = 0
    genome.nodes[2] = SimpleNodeGene(key=2, cfg=cfg.genome)  # Hidden node
    genome.nodes[2].bias = 0
    genome.nodes[3] = SimpleNodeGene(key=3, cfg=cfg.genome)  # Hidden node
    genome.nodes[3].bias = 0

    # Reset the connections
    genome.connections = dict()
    for key in [(-1, 2), (2, 2), (3, 3), (3, 1)]:
        genome.connections[key] = ConnectionGene(key=key, cfg=cfg.genome)
        genome.connections[key].weight = 1
        genome.connections[key].enabled = True

    return genome
Exemple #7
0
def get_genome2(cfg: Config):
    """
    Genome with all biases set to 0, only simple hidden nodes used, all connections enabled with weight 1.
    Configuration:
        0   1
       /    |
      2     \
     /       |
    -1  -2  -3
    """
    # Create a dummy genome
    genome = Genome(
            key=2,
            num_outputs=cfg.genome.num_outputs,
            bot_config=cfg.bot,
    )
    # Reset the nodes
    genome.nodes[0] = OutputNodeGene(key=0, cfg=cfg.genome)  # OutputNode 0
    genome.nodes[0].bias = 0
    genome.nodes[1] = OutputNodeGene(key=1, cfg=cfg.genome)  # OutputNode 1
    genome.nodes[1].bias = 0
    genome.nodes[2] = SimpleNodeGene(key=2, cfg=cfg.genome)  # Hidden node
    genome.nodes[2].bias = 0
    
    # Reset the connections
    genome.connections = dict()
    for key in [(-1, 2), (2, 0), (-3, 1)]:
        genome.connections[key] = ConnectionGene(key=key, cfg=cfg.genome)
        genome.connections[key].weight = 1
        genome.connections[key].enabled = True
    
    return genome
Exemple #8
0
def get_valid2(cfg: Config):
    """
    Network with a recurrent connection (at node 2).
    Configuration:
         0   1
        /
       2>
      / \
    -1  -2  -3
    """
    # Create a dummy genome
    genome = Genome(
        key=2,
        num_outputs=cfg.genome.num_outputs,
        bot_config=cfg.bot,
    )
    # Reset the nodes
    genome.nodes[0] = OutputNodeGene(key=0, cfg=cfg.genome)  # OutputNode 0
    genome.nodes[0].bias = 0
    genome.nodes[1] = OutputNodeGene(key=1, cfg=cfg.genome)  # OutputNode 1
    genome.nodes[1].bias = 0
    genome.nodes[2] = SimpleNodeGene(key=2, cfg=cfg.genome)  # Hidden node
    genome.nodes[2].bias = 0

    # Reset the connections
    genome.connections = dict()
    for key in [(-1, 2), (-2, 2), (2, 0), (2, 2)]:
        genome.connections[key] = ConnectionGene(key=key, cfg=cfg.genome)
        genome.connections[key].weight = 1
        genome.connections[key].enabled = True

    return genome
def get_topology22222(gid: int, cfg: Config):
    """
    Create a uniformly and randomly sampled genome of fixed topology:
    Sigmoid with bias 1.5 --> Actuation default of 95,3%
      (key=0, bias=1.5)      (key=1, bias=?)
                     ____ /   /
                   /         /
               SRU-X        /
                |     _____/
                |   /
              (key=-1)
    """
    # Create an initial dummy genome with fixed configuration
    genome = Genome(
        key=gid,
        num_outputs=cfg.genome.num_outputs,
        bot_config=cfg.bot,
    )

    # Setup the parameter-ranges
    conn_range = cfg.genome.weight_max_value - cfg.genome.weight_min_value
    bias_range = cfg.genome.bias_max_value - cfg.genome.bias_min_value
    rnn_range = cfg.genome.rnn_max_value - cfg.genome.rnn_min_value

    # Create the nodes
    genome.nodes[0] = OutputNodeGene(key=0, cfg=cfg.genome)  # OutputNode 0
    genome.nodes[0].bias = 1.5  # Drive with 0.953 actuation by default
    genome.nodes[1] = OutputNodeGene(key=1, cfg=cfg.genome)  # OutputNode 1
    genome.nodes[1].bias = random(
    ) * bias_range + cfg.genome.bias_min_value  # Uniformly sampled bias
    genome.nodes[2] = ConvexSruNodeGene(key=2, cfg=cfg.genome,
                                        input_keys=[-1])  # Hidden node
    genome.nodes[2].bias = 0  # Bias is irrelevant for GRU-node

    # Create the connections
    genome.connections = dict()

    # input2gru
    key = (-1, 2)
    genome.connections[key] = ConnectionGene(key=key, cfg=cfg.genome)
    genome.connections[key].weight = 1  # Simply forward distance
    genome.connections[key].enabled = True

    # gru2output - Uniformly sampled
    key = (2, 1)
    genome.connections[key] = ConnectionGene(key=key, cfg=cfg.genome)
    genome.connections[key].weight = 3  # Enforce capabilities of full spectrum
    genome.connections[key].enabled = True

    # input2output - Uniformly sampled
    key = (-1, 1)
    genome.connections[key] = ConnectionGene(key=key, cfg=cfg.genome)
    genome.connections[
        key].weight = random() * conn_range + cfg.genome.weight_min_value
    genome.connections[key].enabled = True

    genome.update_rnn_nodes(config=cfg.genome)
    return genome
def get_topology2(cfg, random_init: bool = False):
    """
    Simple genome with only two connections:
        0   1
       /    |
      2    /
       \  /
       -1
    """
    # Create an initial dummy genome with fixed configuration
    genome = Genome(
        key=0,
        num_outputs=cfg.genome.num_outputs,
        bot_config=cfg.bot,
    )

    # Create the nodes
    genome.nodes[0] = OutputNodeGene(key=0, cfg=cfg.genome)  # OutputNode 0
    genome.nodes[0].bias = 0  # Drive with 0.5 actuation by default
    genome.nodes[1] = OutputNodeGene(key=1, cfg=cfg.genome)  # OutputNode 1
    genome.nodes[1].bias = 0  # Drive with 0.5 actuation by default
    genome.nodes[2] = GruNodeGene(key=2,
                                  cfg=cfg.genome,
                                  input_keys=[-1],
                                  input_keys_full=[-1])  # Hidden node
    genome.nodes[2].bias = 0  # Bias is irrelevant for GRU-node

    if not random_init:
        genome.nodes[2].bias_h = np.zeros((3, ))
        genome.nodes[2].weight_xh_full = np.zeros((3, 1))
        genome.nodes[2].weight_hh = np.zeros((3, 1))

    # Create the connections
    genome.connections = dict()

    # Input-GRU
    key = (-1, 2)
    genome.connections[key] = ConnectionGene(key=key, cfg=cfg.genome)
    genome.connections[key].weight = 1  # Simply forward distance
    genome.connections[key].enabled = True

    # GRU-Output
    key = (2, 0)
    genome.connections[key] = ConnectionGene(key=key, cfg=cfg.genome)
    genome.connections[
        key].weight = 3  # Increase magnitude to be a value between -3..3 (possible to steer left wheel)
    genome.connections[key].enabled = True

    # GRU-Output
    key = (-1, 1)
    genome.connections[key] = ConnectionGene(key=key, cfg=cfg.genome)
    genome.connections[key].weight = -1
    genome.connections[key].enabled = True

    genome.update_rnn_nodes(config=cfg.genome)
    return genome
Exemple #11
0
 def create_new(self, config: Config, num_genomes):
     """Create a new (random initialized) population."""
     new_genomes = dict()
     for i in range(num_genomes):
         key = next(self.genome_indexer)
         g = Genome(key,
                    num_outputs=config.genome.num_outputs,
                    bot_config=config.bot)
         g.configure_new(config.genome)
         new_genomes[key] = g
     return new_genomes
Exemple #12
0
def get_deep_genome3(cfg: Config):
    """
    Genome with all biases set to 0, only simple hidden nodes used, all connections enabled with weight 1.
    Configuration:
        0       1
        |       |
        16      |
        |  \    |
        15  |   |
        |   |   |
        14  |   |
        |   |   |
       -1  -2  -3
    """
    # Create a dummy genome
    genome = Genome(
            key=3,
            num_outputs=cfg.genome.num_outputs,
            bot_config=cfg.bot,
    )
    # Reset the nodes
    genome.nodes[0] = OutputNodeGene(key=0, cfg=cfg.genome)  # OutputNode 0
    genome.nodes[0].bias = 0
    genome.nodes[1] = OutputNodeGene(key=1, cfg=cfg.genome)  # OutputNode 1
    genome.nodes[1].bias = 0
    for k in [14, 15, 16]:
        genome.nodes[k] = SimpleNodeGene(key=k, cfg=cfg.genome)  # Hidden node
        genome.nodes[k].bias = 0
    
    # Reset the connections
    genome.connections = dict()
    for key in [(-1, 14), (14, 15), (15, 16), (16, 0), (-2, 16), (-3, 1)]:
        genome.connections[key] = ConnectionGene(key=key, cfg=cfg.genome)
        genome.connections[key].weight = 1
        genome.connections[key].enabled = True
    
    return genome
Exemple #13
0
    def reproduce(self,
                  config: Config,
                  species: DefaultSpecies,
                  generation: int,
                  logger=None):
        """Handles creation of genomes, either from scratch or by sexual or asexual reproduction from parents."""
        # Check is one of the species has become stagnant (i.e. must be removed)
        remaining_fitness = []
        remaining_species = []
        for stag_sid, stag_s, stagnant in self.stagnation.update(
                config=config, species_set=species, gen=generation):
            # If specie is stagnant, then remove
            if stagnant:
                self.reporters.species_stagnant(stag_sid,
                                                stag_s,
                                                logger=logger)

            # Add the specie to remaining_species and save each of its members' fitness
            else:
                remaining_fitness.extend(m.fitness
                                         for m in itervalues(stag_s.members))
                remaining_species.append(stag_s)

        # If no species is left then force hard-reset
        if not remaining_species:
            species.species = dict()
            return dict()

        # Calculate the adjusted fitness, normalized by the minimum fitness across the entire population
        for specie in remaining_species:
            # Adjust a specie's fitness in a fitness sharing manner. A specie's fitness gets normalized by the number of
            #  members it has, this to ensure that a better performing specie does not takes over the population
            # A specie's fitness is determined by its most fit genome
            specie_fitness = max([m.fitness for m in specie.members.values()])
            specie_size = len(specie.members)
            specie.adjusted_fitness = specie_fitness / max(
                specie_size, config.population.min_specie_size)

        # Minimum specie-size is defined by the number of elites and the minimal number of genomes in a population
        spawn_amounts = self.compute_spawn(
            adjusted_fitness=[s.adjusted_fitness for s in remaining_species],
            previous_sizes=[len(s.members) for s in remaining_species],
            pop_size=config.population.pop_size,
            min_species_size=max(config.population.min_specie_size,
                                 config.population.genome_elitism))

        # Setup the next generation by filling in the new species with their elites and offspring
        new_population = dict()
        species.species = dict()
        for spawn_amount, specie in zip(spawn_amounts, remaining_species):
            # If elitism is enabled, each species will always at least gets to retain its elites
            spawn_amount = max(spawn_amount, config.population.genome_elitism)
            assert spawn_amount > 0

            # Get all the specie's old (evaluated) members
            old_members = list(iteritems(
                specie.members))  # Temporarily save members of last generation
            specie.members = dict()  # Reset members
            species.species[specie.key] = specie

            # Sort members in order of descending fitness (i.e. most fit members in front)
            old_members.sort(reverse=True, key=lambda x: x[1].fitness)

            # Make sure that all the specie's elites are added to the new generation
            if config.population.genome_elitism > 0:
                # Add the specie's elites to the global population
                for i, m in old_members[:config.population.genome_elitism]:
                    new_population[i] = m
                    spawn_amount -= 1

                # Add the specie's past elites as well if requested
                for i in range(
                        min(len(specie.elite_list),
                            config.population.genome_elite_stagnation - 1)):
                    gid, g = specie.elite_list[-(i + 1)]
                    if gid not in new_population:  # Only add genomes not yet present in the population
                        new_population[gid] = g
                        spawn_amount -= 1

                # Update the specie's elite_list
                specie.elite_list.append(old_members[0])

            # Check if the specie has the right to add more genomes to the population
            if spawn_amount <= 0: continue

            # Only use the survival threshold fraction to use as parents for the next generation, use at least all the
            #  elite of a population as parents
            reproduction_cutoff = max(
                round(config.population.parent_selection * len(old_members)),
                config.population.genome_elitism)

            # Since asexual reproduction, at least one parent must be chosen
            reproduction_cutoff = max(reproduction_cutoff, 1)
            parents = old_members[:reproduction_cutoff]

            # Add the elites again to the parent-set such that these have a greater likelihood of being chosen
            parents += old_members[:config.population.genome_elitism]

            # Fill the specie with offspring based, which is a mutation of the chosen parent
            while spawn_amount > 0:
                spawn_amount -= 1

                # Init genome dummy (values are overwritten later)
                gid = next(self.genome_indexer)
                child: Genome = Genome(gid,
                                       num_outputs=config.genome.num_outputs,
                                       bot_config=config.bot)

                # Choose the parents, note that if the parents are not distinct, crossover will produce a genetically
                #  identical clone of the parent (but with a different ID)
                p1_id, p1 = choice(parents)
                child.connections = copy.deepcopy(p1.connections)
                child.nodes = copy.deepcopy(p1.nodes)

                # Mutate the child
                child.mutate(config.genome)

                # Ensure that the child is connected
                while len(child.get_used_connections()) == 0:
                    child.mutate_add_connection(config.genome)

                # Add the child to the global population
                new_population[gid] = child

        return new_population
def get_topology(pop_name, gid: int, cfg: Config):
    """
    Create a uniformly and randomly sampled genome of fixed topology:
    Sigmoid with bias 1.5 --> Actuation default of 95,3%
      (key=0, bias=1.5)   (key=1, bias=?)
                     ____ /   /
                   /         /
                GRU         /
                |     _____/
                |   /
              (key=-1)
    """
    # Create an initial dummy genome with fixed configuration
    genome = Genome(
        key=gid,
        num_outputs=cfg.genome.num_outputs,
        bot_config=cfg.bot,
    )

    # Setup the parameter-ranges
    conn_range = cfg.genome.weight_max_value - cfg.genome.weight_min_value
    bias_range = cfg.genome.bias_max_value - cfg.genome.bias_min_value
    rnn_range = cfg.genome.rnn_max_value - cfg.genome.rnn_min_value

    # Create the output nodes
    genome.nodes[0] = OutputNodeGene(key=0, cfg=cfg.genome)  # OutputNode 0
    genome.nodes[0].bias = 1.5  # Drive with 0.953 actuation by default
    genome.nodes[1] = OutputNodeGene(key=1, cfg=cfg.genome)  # OutputNode 1
    if pop_name in [P_BIASED]:
        genome.nodes[1].bias = normal(
            1.5,
            .1)  # Initially normally distributed around bias of other output
    else:
        genome.nodes[1].bias = random(
        ) * bias_range + cfg.genome.bias_min_value  # Uniformly sampled bias

    # Setup the recurrent unit
    if pop_name in [P_GRU_NR]:
        genome.nodes[2] = GruNoResetNodeGene(key=2,
                                             cfg=cfg.genome,
                                             input_keys=[-1],
                                             input_keys_full=[-1])  # Hidden
        genome.nodes[2].bias_h = rand_arr(
            (2, )) * bias_range + cfg.genome.bias_min_value
        genome.nodes[2].weight_xh_full = rand_arr(
            (2, 1)) * rnn_range + cfg.genome.weight_min_value
        genome.nodes[2].weight_hh = rand_arr(
            (2, 1)) * rnn_range + cfg.genome.weight_min_value
    else:
        genome.nodes[2] = GruNodeGene(key=2,
                                      cfg=cfg.genome,
                                      input_keys=[-1],
                                      input_keys_full=[-1])  # Hidden node
        genome.nodes[2].bias_h = rand_arr(
            (3, )) * bias_range + cfg.genome.bias_min_value
        genome.nodes[2].weight_xh_full = rand_arr(
            (3, 1)) * rnn_range + cfg.genome.weight_min_value
        genome.nodes[2].weight_hh = rand_arr(
            (3, 1)) * rnn_range + cfg.genome.weight_min_value
    genome.nodes[2].bias = 0  # Bias is irrelevant for GRU-node

    # Create the connections
    genome.connections = dict()

    # input2gru - Uniformly sampled on the positive spectrum
    key = (-1, 2)
    genome.connections[key] = ConnectionGene(key=key, cfg=cfg.genome)
    if pop_name in [P_BIASED]:
        genome.connections[
            key].weight = 6  # Maximize connection, GRU can always lower values flowing through
    else:
        genome.connections[
            key].weight = random() * conn_range + cfg.genome.weight_min_value
    genome.connections[key].enabled = True

    # gru2output - Uniformly sampled on the positive spectrum
    key = (2, 1)
    genome.connections[key] = ConnectionGene(key=key, cfg=cfg.genome)
    genome.connections[
        key].weight = random() * conn_range + cfg.genome.weight_min_value
    if pop_name in [P_BIASED]:
        genome.connections[key].weight = abs(
            genome.connections[key].weight)  # Always positive!
    genome.connections[key].enabled = True

    # input2output - Uniformly sampled
    key = (-1, 1)
    genome.connections[key] = ConnectionGene(key=key, cfg=cfg.genome)
    genome.connections[
        key].weight = random() * conn_range + cfg.genome.weight_min_value
    if pop_name in [P_BIASED]:
        genome.connections[key].weight = -abs(
            genome.connections[key].weight)  # Always negative!
    genome.connections[key].enabled = True

    # Enforce the topology constraints
    enforce_topology(pop_name=pop_name, genome=genome)

    genome.update_rnn_nodes(config=cfg.genome)
    return genome
def get_topology1(gid: int, cfg: Config):
    """
    Create a uniformly and randomly sampled genome of fixed topology:
      (key=0, bias=1.5)  (key=1, bias=0)
                     ____ /   /
                   /         /
                GRU         /
                |     _____/
                |   /
              (key=-1)
    """
    # Create an initial dummy genome with fixed configuration
    genome = Genome(
        key=gid,
        num_outputs=cfg.genome.num_outputs,
        bot_config=cfg.bot,
    )

    # Create the nodes
    genome.nodes[0] = OutputNodeGene(key=0, cfg=cfg.genome)  # OutputNode 0
    genome.nodes[0].bias = 1.5  # Drive with full actuation by default
    genome.nodes[1] = OutputNodeGene(key=1, cfg=cfg.genome)  # OutputNode 1
    genome.nodes[1].bias = 0  # Drive with 0.5 actuation by default
    genome.nodes[2] = GruNodeGene(key=2,
                                  cfg=cfg.genome,
                                  input_keys=[-1],
                                  input_keys_full=[-1])  # Hidden node
    genome.nodes[2].bias = 0  # Bias is irrelevant for GRU-node

    # Setup the parameter-ranges
    conn_range = cfg.genome.weight_max_value - cfg.genome.weight_min_value
    bias_range = cfg.genome.bias_max_value - cfg.genome.bias_min_value
    rnn_range = cfg.genome.rnn_max_value - cfg.genome.rnn_min_value

    # Uniformly sample the genome's GRU-component
    genome.nodes[2].bias_h = rand_arr(
        (3, )) * bias_range + cfg.genome.bias_min_value
    genome.nodes[2].weight_xh_full = rand_arr(
        (3, 1)) * rnn_range + cfg.genome.weight_min_value
    genome.nodes[2].weight_hh = rand_arr(
        (3, 1)) * rnn_range + cfg.genome.weight_min_value

    # Create the connections
    genome.connections = dict()

    # input2gru
    key = (-1, 2)
    genome.connections[key] = ConnectionGene(key=key, cfg=cfg.genome)
    genome.connections[key].weight = 1  # Simply forward distance
    genome.connections[key].enabled = True

    # gru2output - Uniformly sampled
    key = (2, 1)
    genome.connections[key] = ConnectionGene(key=key, cfg=cfg.genome)
    genome.connections[
        key].weight = random() * conn_range + cfg.genome.weight_min_value
    genome.connections[key].enabled = True

    # input2output - Uniformly sampled
    key = (-1, 1)
    genome.connections[key] = ConnectionGene(key=key, cfg=cfg.genome)
    genome.connections[
        key].weight = random() * conn_range + cfg.genome.weight_min_value
    genome.connections[key].enabled = True

    genome.update_rnn_nodes(config=cfg.genome)
    return genome