def test_create_offspring_pairs(self):
        agent_id_generator = AgentIDGeneratorSingleCore()
        config = NeatConfig()

        rnd = np.random.RandomState(1)
        # First 10 random values
        # rnd.randint(3) = 1
        # rnd.randint(3) = 0
        # rnd.randint(3) = 0
        # rnd.randint(3) = 1
        # rnd.randint(3) = 1
        # rnd.randint(3) = 0
        # rnd.randint(3) = 0
        # rnd.randint(3) = 1

        tuple_list = ss.create_offspring_pairs(self.species1, 4,
                                               agent_id_generator,
                                               self.generation, rnd, config)

        # Test tuples
        agent_id_generator = AgentIDGeneratorSingleCore()
        self.assertEqual(4, len(tuple_list))
        self.assertEqual((self.agent2.id, self.agent1.id,
                          agent_id_generator.get_agent_id()), tuple_list[0])
        self.assertEqual((self.agent1.id, self.agent2.id,
                          agent_id_generator.get_agent_id()), tuple_list[1])
        self.assertEqual((self.agent2.id, self.agent1.id,
                          agent_id_generator.get_agent_id()), tuple_list[2])
        self.assertEqual((self.agent1.id, self.agent2.id,
                          agent_id_generator.get_agent_id()), tuple_list[3])
    def _build_new_generation(self, generation: Generation,
                              innovation_number_generator: InnovationNumberGeneratorInterface,
                              species_id_generator: SpeciesIDGeneratorSingleCore,
                              agent_id_generator: AgentIDGeneratorSingleCore,
                              config: NeatConfig) -> Generation:
        # Notify callback
        self._notify_reporters_callback(lambda r: r.on_reproduction_start(generation))

        # Get the random generator for the new generation
        new_generation_seed = np.random.RandomState(generation.seed).randint(2 ** 24)
        rnd = np.random.RandomState(new_generation_seed)

        # Get the best agents, which will be copied later
        best_agents_genomes = gs.get_best_genomes_from_species(generation.species_list,
                                                               config.species_size_copy_best_genome)

        # Get allowed species for reproduction
        generation = ss.update_fitness_species(generation)
        species_list = ss.get_allowed_species_for_reproduction(generation,
                                                               config.species_stagnant_after_generations)

        # TODO handle error no species
        if len(species_list) <= 5:
            species_list = generation.species_list
        # assert len(species_list) >= 1

        # Calculate the adjusted fitness values
        min_fitness = min([a.fitness for a in generation.agents])
        max_fitness = max([a.fitness for a in generation.agents])
        species_list = ss.calculate_adjusted_fitness(species_list, min_fitness, max_fitness)

        # Remove the low performing genomes
        species_list = ss.remove_low_genomes(species_list, config.percentage_remove_low_genomes)

        # Calculate offspring for species
        off_spring_list = ss.calculate_amount_offspring(species_list, config.population_size - len(best_agents_genomes))

        # Calculate off spring combinations
        off_spring_pairs = []
        for species, amount_offspring in zip(species_list, off_spring_list):
            off_spring_pairs += ss.create_offspring_pairs(species, amount_offspring, agent_id_generator, generation,
                                                          rnd, config)

        # Notify innovation number generator, that a new generation is created
        innovation_number_generator.next_generation(generation.number)

        # Create a dictionary for easy access
        agent_dict = {agent.id: agent for agent in generation.agents}

        # Create new agents - fill initially with best agents
        new_agents = [Agent(agent_id_generator.get_agent_id(), genome) for genome in best_agents_genomes]

        # Create agents with crossover
        self._notify_reporters_callback(lambda r: r.on_compose_offsprings_start())
        for parent1_id, parent2_id, child_id in off_spring_pairs:
            parent1 = agent_dict[parent1_id]
            parent2 = agent_dict[parent2_id]

            child_seed = (parent1.genome.seed + parent2.genome.seed) % 2 ** 24
            rnd_child = np.random.RandomState(child_seed)

            # Perform crossover for to get the nodes and connections for the child
            if parent1.fitness > parent2.fitness:
                child_nodes, child_connections = rp.cross_over(parent1.genome, parent2.genome, rnd_child, config)
            else:
                child_nodes, child_connections = rp.cross_over(parent2.genome, parent1.genome, rnd_child, config)

            # Create child genome
            child_genome = Genome(child_seed, child_nodes, child_connections)

            # Mutate genome
            child_genome = rp.mutate_weights(child_genome, rnd_child, config)
            child_genome, _, _, _ = rp.mutate_add_node(child_genome, rnd_child, innovation_number_generator, config)
            child_genome, _ = rp.mutate_add_connection(child_genome, rnd_child, innovation_number_generator, config)

            child_agent = Agent(child_id, child_genome)
            new_agents.append(child_agent)

        # Notify callback end
        self._notify_reporters_callback(lambda r: r.on_compose_offsprings_end())

        # TODO convert back
        # Select new representative
        existing_species = [ss.select_new_representative(species, rnd) for species in generation.species_list]
        # Reset members and fitness
        existing_species = [ss.reset_species(species) for species in existing_species]
        # existing_species = [ss.reset_species(species) for species in generation.species_list]

        # Sort members into species
        new_species_list = ss.sort_agents_into_species(existing_species, new_agents, species_id_generator, config)
        logger.info("Species IDs: {}".format([s.id_ for s in new_species_list]))
        logger.info("Species Mem: {}".format([len(s.members) for s in new_species_list]))

        # Filter out empty species
        new_species_list = ss.get_species_with_members(new_species_list)

        # Create new generation, notify callback and return new generation
        new_generation = Generation(generation.number + 1, new_generation_seed, new_agents, new_species_list)
        self._notify_reporters_callback(lambda r: r.on_reproduction_end(new_generation))
        return new_generation