Пример #1
0
    def optimize(self):
        current_population = self._generate_population()
        gene_tracker = GeneTracker(current_population[0].nodes_count)
        all_species = [Species(current_population[0])]

        best_fitness = None
        best_genome = None

        current_generation = 0
        while current_generation < self.max_generations:
            fitness = self._calculate_fitness(current_population)
            all_species = self._determine_species(current_population, fitness,
                                                  all_species)
            species_fitness = [species.fitness for species in all_species]

            next_population = []
            best_fitness, best_genome = self._save_best(
                best_fitness, best_genome, all_species, next_population)
            self._fill_population(gene_tracker, all_species, species_fitness,
                                  next_population)
            [species.clear() for species in all_species]

            current_population = next_population
            current_generation += 1
            if self.print_progress:
                print(
                    f"generation: {current_generation}, best fitness: {best_fitness}"
                )

        return build_network(best_genome)
    def speciate(self, config, population, generation):

        assert isinstance(population, dict)

        self.genome_to_species = {}

        # Add all the genomes to the right species set.
        for gid, genome in iteritems(population):

            num_states = len(genome.states)

            if num_states not in self.species:
                self.species[num_states] = Species(num_states, generation)

            self.species[num_states].members[gid] = genome
            self.genome_to_species[gid] = num_states

        # Remove empty species sets.
        empty = []
        for sid, species in iteritems(self.species):
            if not species.members:
                empty.append(sid)

        for sid in empty:
            self.species.pop(sid)
            self.reporters.info('Removing species with {0} states from species set'.format(sid))

        self.reporters.info('Speciated into {0} species'.format(len(self.species)))
Пример #3
0
    def _speciate(self, population):
        """
        Place genomes into species by genetic similarity.

        Note that this method assumes the current representatives of the species are from the old
        generation, and that after speciation has been performed, the old representatives should be
        dropped and replaced with representatives from the new generation.  If you violate this
        assumption, you should make sure other necessary parts of the code are updated to reflect
        the new behavior.
        """
        for individual in population:
            # Find the species with the most similar representative.
            min_distance = None
            closest_species = None
            for s in self.species:
                distance = individual.distance(s.representative)
                if distance < self.config.compatibility_threshold \
                    and (min_distance is None or distance < min_distance):
                    closest_species = s
                    min_distance = distance

            if closest_species:
                closest_species.add(individual)
            else:
                # No species is similar enough, create a new species for this individual.
                self.species.append(
                    Species(individual, self.species_indexer.get_next()))

        # Only keep non-empty species.
        self.species = [s for s in self.species if s.members]

        # Select a random current member as the new representative.
        for s in self.species:
            s.representative = random.choice(s.members)
Пример #4
0
    def speciate(self, genome, generation):
        """
        Places Genome into proper species - index
        :param genome: Genome be speciated
        :param generation: Number of generation this speciation is occuring at
        :return: None
        """
        for species in self.species:
            if Species.species_distance(genome, species.model_genome) <= self.Config.SPECIATION_THRESHOLD:
                genome.species = species.id
                species.members.append(genome)
                return

        # Did not match any current species. Create a new one
        new_species = Species(len(self.species), genome, generation)
        genome.species = new_species.id
        new_species.members.append(genome)
        self.species.append(new_species)
Пример #5
0
    def __init__(self, seed_creature=None, n_pops=150):
        """Create a population for NEAT.

        Arguments:
            seed_creature: The creature that will be used to creature the
                           initial generation.
            n_pops: How many creatures should be in the population.
        """
        self.n_pops = n_pops
        self.species = set()

        if seed_creature:
            self.creatures = [seed_creature.copy() for _ in range(n_pops)]

            genesis_species = Species()
            genesis_species.assign_members(self.creatures)
            self.species.add(genesis_species)
        else:
            self.creatures = []
Пример #6
0
    def _mutate_topology(self, genotype: Genotype, species: Species = None):
        species_template = (
            {}, {}) if species is None else species.get_mutate_template()

        add_node = np.random.uniform() < self.prop_top_add_node
        if add_node:
            genotype.mutate_add_node(self.config, self.innovation_map,
                                     species_template)

        if np.random.uniform() < self.prop_top_add_edge and not add_node:
            genotype.mutate_add_edge(self.config, self.innovation_map,
                                     species_template)
Пример #7
0
    def _determine_species(self, current_population, fitness, all_species):
        new_all_species = []
        for i in range(len(current_population)):
            genome = current_population[i]
            genome_fitness = fitness[i]

            for species in all_species:
                if calculate_genomes_distance(genome, species.representative,
                                              self.c1, self.c2, self.c3,
                                              self.n) < self.dt:
                    species.add_genome(genome, genome_fitness)
                    break
            else:
                new_species = Species()
                new_species.add_genome(genome, genome_fitness)
                new_all_species.append(new_species)

        all_species = [
            species for species in all_species if species.count != 0
        ]
        all_species.extend(new_all_species)
        [species.finalize() for species in all_species]
        return all_species
Пример #8
0
    def speciate(self):
        """Place creatures in the creatures into a species, or create a new
        species if no suitable species exists.
        """
        print('Segregating Communities...', end='')
        # Adding these lines slows down convergence a lot.
        for species in self.species:
            species.members.clear()

        for creature in self.creatures:
            for species in self.species:
                if creature.distance(species.representative) < \
                        Species.compatibility_threshold:
                    species.add(creature)

                    break
            else:
                new_species = Species()
                new_species.add(creature)
                new_species.representative = creature

                self.species.add(new_species)

        self.species = set(filter(lambda s: len(s) > 0, self.species))
Пример #9
0
    def from_json(config):
        """Load an instance of the NEAT algorithm from JSON.

        Arguments:
            config: the JSON dictionary loaded from file.

        Returns: an instance of the NEAT algorithm.
        """
        population = Population(None, config['n_pops'])
        population.species = set(
            Species.from_json(s_config) for s_config in config['species'])

        population.creatures = []

        for species in population.species:
            population.creatures += species.members

        return population
Пример #10
0
    def gen_species(self):
        s: Species
        for s in self.species.data:
            s.reset()

        c: Client
        for c in self.clients.data:
            if c.species:
                continue

            found = False
            for s in self.species.data:
                if s.put(c):  # successfully found matching species
                    found = True
                    break

            if not found:
                self.species.add(Species(c))

        for s in self.species.data:
            s.evaluate_score()
Пример #11
0
    def test_json(self):
        """Test whether a species can be saved to and loaded from JSON."""
        species = Species()
        species.assign_members([Creature(4, 1) for _ in range(100)])
        species.allotted_offspring_quota = 93

        dump = json.dumps(species.to_json())
        species_load = Species.from_json(json.loads(dump))

        self.assertEqual(len(species), len(species_load))
        self.assertTrue(
            species.representative.distance(species_load.representative) < 1e-8
        )
        self.assertEqual(species.name, species_load.name)
        self.assertEqual(species.id, species_load.id)
        self.assertEqual(species.allotted_offspring_quota,
                         species_load.allotted_offspring_quota)
        self.assertTrue(
            species.champion.distance(species_load.champion) < 1e-8)
Пример #12
0
    def speciate(self):

        # Randomly assign representative genome
        [species.assign_genome() for species in self.species]

        # Clear species
        [species.clear() for species in self.species]

        # Assign organisms to species
        for organism in self.population:

            for species in self.species:

                if species.is_compatible_with(organism):
                    species.add_organism(organism)
                    break

            # Create new species
            else: self.species.append(Species(self.config, organism))

        # Remove any species without organisms
        self.species = [species for species in self.species if species.organisms]
Пример #13
0
    def speciate(self):
        """
		Seperates the organisms in the population into species
		"""

        # We first remove the organisms attached to each species from the last generation,
        # but we do not remove the the representative of each species from the last generation
        for species in self.species:
            species.organisms.clear()

        for organism in self.organisms:
            # For each organism we try to find a species whose representative is close enough
            found_species = False
            for s_idx, species in enumerate(self.species):
                # Calculate how compatible the species representative is to our organism
                compat_dist = organism.genome.get_compatibility_distance(
                    species.representative)
                if compat_dist < SPECIATION_THRESHOLD:
                    # If it is below the threshold, then the organism belongs to this species
                    found_species = True
                    species.organisms.append(organism)
                    organism.species = s_idx
                    break

            if not found_species:
                # If this organism is different enough such that it is not compatible with any of
                # the existing species, we create a new species with the organism as the
                # representative
                organism.species = len(self.species)
                self.species.append(
                    Species(organism, self.global_species_counter))

        # Remove species which have no organisms in this population. We iterate backwards so we can
        # delete from the list while iterating
        for i in range(len(self.species) - 1, -1, -1):
            if len(self.species[i].organisms) == 0:
                self.species.pop(i)
Пример #14
0
    def speciate(self):
        compatibility_threshold = self.config.genome_params.compatibility_threshold
        species = []

        # assign members to each of the species
        population = deepcopy(self.population)
        for s in self.species:
            for idx, p in enumerate(population):
                distance = p.compatibility_distance(s.champion, p)
                if distance < compatibility_threshold:
                    population.pop(idx)
                    s.update(p)
            species.append(s)

        # divide newborn population or unspeciated individuals into species
        for p in population:
            distances = []
            for s in species:
                distance = p.compatibility_distance(s.champion, p)
                if distance < compatibility_threshold:
                    distances.append((distance, s))

            # check how py checks for empty list |if distances|,
            # might be slow if it computes len first
            if distances:
                _, s = min(distances, key=lambda d: d[0])
                s.update(p)
            else:
                s = Species()
                s.update(p)
                species.append(s)

        # this is obviously dumb here, will change
        for s in species:
            s.update_avg_fitness()
        self.species.clear()
        self.species = species
Пример #15
0
    def run(self):
        for generation in range(1, self.Config.NUMBER_OF_GENERATIONS):
            # Get Fitness of Every Genome
            for genome in self.population:
                genome.fitness = max(0, self.Config.fitness_fn(genome))

            best_genome = utils.get_best_genome(self.population)

            # Reproduce
            all_fitnesses = []
            remaining_species = []

            for species, is_stagnant in Species.stagnation(
                    self.species, generation):
                if is_stagnant:
                    self.species.remove(species)
                else:
                    all_fitnesses.extend(g.fitness for g in species.members)
                    remaining_species.append(species)

            min_fitness = min(all_fitnesses)
            max_fitness = max(all_fitnesses)

            fit_range = max(1.0, (max_fitness - min_fitness))
            for species in remaining_species:
                # Set adjusted fitness
                avg_species_fitness = np.mean(
                    [g.fitness for g in species.members])
                species.adjusted_fitness = (avg_species_fitness -
                                            min_fitness) / fit_range

            adj_fitnesses = [s.adjusted_fitness for s in remaining_species]
            adj_fitness_sum = sum(adj_fitnesses)

            # Get the number of offspring for each species
            new_population = []
            for species in remaining_species:
                if species.adjusted_fitness > 0:
                    size = max(
                        2,
                        int((species.adjusted_fitness / adj_fitness_sum) *
                            self.Config.POPULATION_SIZE))
                else:
                    size = 2

                # sort current members in order of descending fitness
                cur_members = species.members
                cur_members.sort(key=lambda g: g.fitness, reverse=True)
                species.members = []  # reset

                # save top individual in species
                new_population.append(cur_members[0])
                size -= 1

                # Only allow top x% to reproduce
                purge_index = int(self.Config.PERCENTAGE_TO_SAVE *
                                  len(cur_members))
                purge_index = max(2, purge_index)
                cur_members = cur_members[:purge_index]

                for i in range(size):
                    parent_1 = random.choice(cur_members)
                    parent_2 = random.choice(cur_members)

                    child = crossover(parent_1, parent_2, self.Config)
                    mutate(child, self.Config)
                    new_population.append(child)

            # Set new population
            self.population = new_population
            Population.current_gen_innovation = []

            # Speciate
            for genome in self.population:
                self.speciate(genome, generation)

            if best_genome.fitness >= self.Config.FITNESS_THRESHOLD:
                return best_genome, generation

            # Generation Stats
            if self.Config.VERBOSE:
                logger.info(f'Finished Generation {generation}')
                logger.info(f'Best Genome Fitness: {best_genome.fitness}')
                logger.info(
                    f'Best Genome Length {len(best_genome.connection_genes)}\n'
                )

        return None, None
    def speciate(self, config, population, generation):
        """
        Place genomes into species by genetic similarity.

        Note that this method assumes the current representatives of the species are from the old
        generation, and that after speciation has been performed, the old representatives should be
        dropped and replaced with representatives from the new generation.  If you violate this
        assumption, you should make sure other necessary parts of the code are updated to reflect
        the new behavior.
        """
        assert isinstance(population, dict)

        compatibility_threshold = self.species_set_config.compatibility_threshold

        # Find the best representatives for each existing species.
        unspeciated = set(iterkeys(population))
        distances = GenomeDistanceCache(config.genome_config)
        new_representatives = {}
        new_members = {}
        for sid, s in iteritems(self.species):
            candidates = []
            for gid in unspeciated:
                g = population[gid]
                d = distances(s.representative, g)
                candidates.append((d, g))

            # The new representative is the genome closest to the current representative.
            if len(candidates) > 0:
                ignored_rdist, new_rep = min(candidates, key=lambda x: x[0])
                new_rid = new_rep.key
                new_representatives[sid] = new_rid
                new_members[sid] = [new_rid]
                unspeciated.remove(new_rid)

        # Partition population into species based on genetic similarity.
        while unspeciated:
            gid = unspeciated.pop()
            g = population[gid]

            # Find the species with the most similar representative.
            candidates = []
            for sid, rid in iteritems(new_representatives):
                rep = population[rid]
                d = distances(rep, g)
                if d < compatibility_threshold:
                    candidates.append((d, sid))

            if len(candidates) > 0:
                ignored_sdist, sid = min(candidates, key=lambda x: x[0])
                new_members[sid].append(gid)
            else:
                # No species is similar enough, create a new species, using
                # this genome as its representative.
                sid = next(self.indexer)
                new_representatives[sid] = gid
                new_members[sid] = [gid]

        # Update species collection based on new speciation.
        self.genome_to_species = {}
        for sid, rid in iteritems(new_representatives):
            s = self.species.get(sid)
            if s is None:
                s = Species(sid, generation)
                self.species[sid] = s

            members = new_members[sid]
            for gid in members:
                self.genome_to_species[gid] = sid

            member_dict = dict((gid, population[gid]) for gid in members)
            s.update(population[rid], member_dict)

        gdmean = mean(itervalues(distances.distances))
        gdstdev = stdev(itervalues(distances.distances))
        self.reporters.info(
            'Mean genetic distance {0:.3f}, standard deviation {1:.3f}'.format(gdmean, gdstdev))
Пример #17
0
    def run(self, pool=None, shared_data=None):
        allgenfitnesses = []
        for generation in range(1, self.Config.NUMBER_OF_GENERATIONS):
            # ****** BYME: Neuro-evolution accures here *******
            # Get Fitness of Every Genome
            if pool != None:
                ll = len(self.population)
                args = zip(list(it.repeat(self.Config.fitness_fn, ll)), \
                 self.population, list(it.repeat(shared_data, ll)), \
                 list(it.repeat(generation, ll)), range(ll))
                fitnesses = list(pool.map(pool_func, args))
                for genome, fitness in zip(self.population, fitnesses):
                    genome.fitness = fitness
            else:
                for genome in tqdm(self.population):
                    genome.fitness = max(0, self.Config.fitness_fn(genome))

            allfitnesses_onegen = [g.fitness for g in self.population]
            allfitnesses_onegen.sort()
            allgenfitnesses.append(allfitnesses_onegen)

            best_genome = utils.get_best_genome(self.population)
            draw_net(best_genome, view=False, \
             filename="./images/solution-best-g%d"%(generation), show_disabled=True)

            # Reproduce
            all_fitnesses = []
            remaining_species = []

            for species, is_stagnant in Species.stagnation(
                    self.species, generation):
                if is_stagnant:
                    self.species.remove(species)
                else:
                    all_fitnesses.extend(g.fitness for g in species.members)
                    remaining_species.append(species)

            min_fitness = min(all_fitnesses)
            max_fitness = max(all_fitnesses)

            fit_range = max(1.0, (max_fitness - min_fitness))
            for species in remaining_species:
                # Set adjusted fitness
                avg_species_fitness = np.mean(
                    [g.fitness for g in species.members])
                species.adjusted_fitness = (avg_species_fitness -
                                            min_fitness) / fit_range

            adj_fitnesses = [s.adjusted_fitness for s in remaining_species]
            adj_fitness_sum = sum(adj_fitnesses)

            # Get the number of offspring for each species
            new_population = []
            for species in remaining_species:
                if species.adjusted_fitness > 0:
                    size = max(
                        2,
                        int((species.adjusted_fitness / adj_fitness_sum) *
                            self.Config.POPULATION_SIZE))
                else:
                    size = 2

                # sort current members in order of descending fitness
                cur_members = species.members
                cur_members.sort(key=lambda g: g.fitness, reverse=True)
                species.members = []  # reset

                # save top individual in species
                new_population.append(cur_members[0])
                size -= 1

                # Only allow top x% to reproduce
                purge_index = int(self.Config.PERCENTAGE_TO_SAVE *
                                  len(cur_members))
                purge_index = max(2, purge_index)
                cur_members = cur_members[:purge_index]

                for i in range(size):
                    parent_1 = random.choice(cur_members)
                    parent_2 = random.choice(cur_members)

                    child = crossover(parent_1, parent_2, self.Config)
                    mutate(child, self.Config)
                    new_population.append(child)

            # Set new population
            self.population = new_population
            Population.current_gen_innovation = []

            # Speciate
            for genome in self.population:
                self.speciate(genome, generation)

            if best_genome.fitness >= self.Config.FITNESS_THRESHOLD:
                o = open("allgenfitnesses.txt", "w")
                for allfitnesses_onegen in allgenfitnesses:
                    o.write(str(allfitnesses_onegen) + "\n")
                o.close()
                return best_genome, generation

            # Generation Stats
            if self.Config.VERBOSE:
                print('Finished Generation', generation)
                print('Best Genome Fitness:', best_genome.fitness)
                if hasattr(best_genome, "avgloss"):
                    print('Best Genome Loss:', best_genome.avgloss)
                print('Best Genome Length', len(best_genome.connection_genes))
                print()

        o = open("allgenfitnesses.txt", "w")
        for allfitnesses_onegen in allgenfitnesses:
            o.write(str(allfitnesses_onegen) + "\n")
        o.close()

        return None, None
Пример #18
0
    def speciate(self):
        self.archive.reset_stats()
        self._adjust_fitness()

        for species in self.species:
            species.reset(self.species, self.population)

        self.species = [
            s for s in self.species if s.representative is not None
        ]
        representatives = [
            s.representative for s in self.species
            if s.representative is not None
        ]

        for genotype in self.population:
            if genotype in representatives:
                continue

            genotype.curr_orig_species_id = genotype.species_id
            species_found = False
            best_species = -1
            best_diffs = None
            for i in range(len(self.species)):
                is_compatible, diffs = self.species[i].calculate_compatibility(
                    genotype)
                if is_compatible:
                    species_found = True
                    if best_diffs is None or diffs < best_diffs:
                        best_diffs = diffs
                        best_species = i

            if species_found:
                self.species[best_species].add_member(genotype, best_diffs)
            else:
                self.species.append(
                    Species(genotype, self._next_species_id(),
                            self.config.compatibility_max_diffs,
                            self.config.species_elitism))

        self.species = [
            species for species in self.species if not species.is_empty()
        ]

        # For Console Observer
        self.specs_before = len(self.species)
        self.comp_before = self._get_interspecies_disjoints()

        for species in self.species:
            species.evaluate_fitness()

        best_member_species = self.species[np.argsort(
            [s.get_best_member().score for s in self.species])[-1]]
        best_member = best_member_species.get_best_member()
        print("BEST MEMBER", best_member, best_member_species)

        # Remove Extinct
        # if len(self.species) >= self.config.species_max:
        #     self.species = [species for species in self.species if not species.is_extinct() or species == best_member_species]

        # Limit max species
        new_species = [best_member_species]  # type: List[Species]
        # species_score = np.argsort([s.fitness * best_member.calculate_compatibility(s.representative, self.config.compatibility_max_diffs) for s in self.species])[::-1]

        # if len(self.species) < self.config.species_max:
        #     valid_species = [s for s in self.species]
        # else:
        #     valid_species = [s for s in self.species if
        #                      s.calculate_compatibility(best_member_species.representative)[1] < self.config.compatibility_max_diffs * 3 and
        #                      s.get_best_member().fitness >= best_member_species.get_best_member().fitness * 0.5]

        valid_species = [
            s for s in self.species
            if s.calculate_compatibility(best_member_species.representative)[1]
            < self.config.compatibility_species_max_diffs
            and s.get_best_member().fitness >=
            best_member_species.get_best_member().fitness * 0.75
        ]

        species_score = np.argsort([
            s.get_best_member().fitness + s.fitness for s in valid_species
        ])[::-1]

        for i in species_score:
            if self.species[i] not in new_species:
                new_species.append(self.species[i])

            if len(new_species) == self.config.species_max:
                break

        self.species = new_species

        speices_id = {s.id for s in self.species}
        unspeciated_genotypes = [
            g for g in self.population if g.species_id not in speices_id
        ]

        for genotype in unspeciated_genotypes:
            genotype.species_id = genotype.curr_orig_species_id
            species_found = False
            best_species = None
            best_diffs = None
            for i in range(len(self.species)):
                is_compatible, diffs = self.species[i].calculate_compatibility(
                    genotype)
                if is_compatible:
                    species_found = True
                    if best_species is None or diffs < best_diffs:
                        best_diffs = diffs
                        best_species = i
            if species_found:
                self.species[best_species].add_member(genotype, best_diffs)

        # if len(self.species) >= self.config.species_max and self._get_interspecies_disjoints() > self.config.compatibility_species_max_diffs:

        # if len(self.species) >= self.config.species_max:
        #     representatives = [s.representative for s in self.species]
        #     representatives_sizes = [len(r.edges) for r in representatives]
        #     avg_size = sum(representatives_sizes) / len(representatives_sizes)
        #
        #     best_member_species = self.species[np.argsort([s.get_best_member().score for s in self.species])[-1]]
        #
        #     self.species = [s for s in self.species
        #                     if s.calculate_compatibility(best_member_species.representative)[1] < self.config.compatibility_max_diffs * 3]

        # if self._get_interspecies_disjoints() > max(avg_size * 0.2, self.config.compatibility_max_diffs * 3):
        #     pop_test = []
        #     for s in self.species:
        #         pop_test.extend(s.get_elite())
        #
        #     results, avg_results, seeds = self.get_evaluator().test(pop_test, self.test_seed, evals=True, allow_penalty=False)
        #
        #     avg_results = [avg_results[i] + pop_test[i].score for i in range(len(pop_test))]
        #     best_genotype = pop_test[avg_results.index(max(avg_results))]
        #
        #     best_member_species = Species(best_genotype, self._next_species_id(), self.config.compatibility_max_diffs, self.config.species_elitism)
        #
        #     self.species = [best_member_species]
        #     self.archive.add(self.population)
        #     best_member_species.restart_to_best(self.config, best_member_species.get_best_member(), self.population)
        #     self.get_evaluator().calculate_score(self.population, self.seed)
        #     self._adjust_fitness()
        #     self.mutaiton_manager.after_merge = True

        for species in self.species:
            species.evaluate_fitness()
            species.sort_members_by_score()

        speciated_genotypes = []
        [
            speciated_genotypes.append(g) for s in self.species
            for g in s.members if g not in speciated_genotypes
        ]

        # Add unspeciated genotypes
        self.archive.add(
            [g for g in self.population if g not in speciated_genotypes])

        # For Console Observer
        self.compatibility = [
            g.species_diff for s in self.species for g in s.members
        ]
        self.comp_after = self._get_interspecies_disjoints()
        self.specs_after = len(self.species)
Пример #19
0
    def sort(self, population, species, pop_size, generation):

        ## Stagnation happens only on the child Q(t) population, before merging,
        # so the species have a chance to avoid stagnation if they're doing
        # generally fine
        # Filter out stagnated species genomes, collect the set of non-stagnated
        remaining_species = {}  # remaining species
        for stag_sid, stag_s, stagnant in self.stagnation.update(
                species, generation):
            # stagnant species: remove genomes from child population
            if stagnant:
                self.reporters.species_stagnant(stag_sid, stag_s)
                population = {
                    id: g
                    for id, g in population.items() if g not in stag_s.members
                }
            # non stagnant species: append species to parent species dictionary
            else:
                remaining_species[stag_sid] = stag_s

        # No genomes left.
        if not remaining_species:
            species.species = {}
            return {}

        ## NSGA-II : step 1 : merge and sort
        # Merge populations P(t)+Q(t) and sort by non-dominated fronts
        child_pop = [g for _, g in population.items()] + self.parent_pop

        # Merge parent P(t) species and child (Qt) species,
        # so all non-stagnated genomes are covered by species.species
        species.species = remaining_species
        for id, sp in self.parent_species.items():
            if (id in species.species):
                species.species[id].members.update(sp.members)
            else:
                species.species[id] = sp

        ## Non-Dominated Sorting (of P(t)+Q(t))
        # algorithm data
        S = {}  # genomes dominated by key genome
        n = {}  # counter of genomes dominating key genome
        F = []  # current dominance front
        self.fronts = []  # clear dominance fronts
        # calculate dominance of every genome to every other genome - O(MN²)
        for p in range(len(child_pop)):
            S[p] = []
            n[p] = 0
            for q in range(len(child_pop)):
                if (p == q): continue
                # p dominates q
                if (child_pop[p].fitness.dominates(child_pop[q].fitness)):
                    S[p].append(q)
                # q dominates p
                elif (child_pop[q].fitness.dominates(child_pop[p].fitness)):
                    n[p] += 1
            # if genome is non-dominated, set rank and add to front
            if (n[p] == 0):
                child_pop[p].fitness.rank = 0
                F.append(p)

        # assemble dominance fronts - O(N²)
        i = 0  # dominance front iterator
        while (len(F) > 0):
            # store front
            self.fronts.append([child_pop[f] for f in F])
            # new dominance front
            Q = []
            # for each genome in current front
            for p in F:
                # for each genome q dominated by p
                for q in S[p]:
                    # decrease dominate counter of q
                    n[q] -= 1
                    # if q reached new front
                    if n[q] == 0:
                        child_pop[q].fitness.rank = -(i + 1)
                        Q.append(q)
            # iterate front
            i += 1
            F = Q

        ## NSGA-II : step 2 : pareto selection
        # Create new parent population P(t+1) from the best fronts
        # Sort each front by Crowding Distance, to be used on Tournament
        self.parent_pop = []
        for front in self.fronts:
            ## Calculate crowd-distance of fitnesses
            # First set distance to zero
            for genome in front:
                genome.dist = 0
            # List of fitnesses to be used for distance calculation
            fitnesses = [f.fitness for f in front]
            # Iterate each fitness parameter (values)
            for m in range(len(fitnesses[0].values)):
                # Sort fitnesses by parameter
                fitnesses.sort(key=lambda f: f.values[m])
                # Get scale for normalizing values
                scale = (fitnesses[-1].values[m] - fitnesses[0].values[m])
                # Set edges distance to infinite, to ensure are picked by the next step
                # This helps keeping the population diverse
                fitnesses[0].dist = float('inf')
                fitnesses[-1].dist = float('inf')
                # Increment distance values for each fitness
                if (scale > 0):
                    for i in range(1, len(fitnesses) - 1):
                        fitnesses[i].dist += abs(
                            fitnesses[i + 1].values[0] -
                            fitnesses[i - 1].values[0]) / scale

            ## Sort front by crowd distance
            # In case distances are equal (mostly on 'inf' values), use the first value to sort
            front.sort(key=lambda g: (g.fitness.dist, g.fitness.values[0]),
                       reverse=True)

            ## Assemble new parent population P(t+1)
            # front fits entirely on the parent population, just append it
            if (len(self.parent_pop) + len(front) <= pop_size):
                self.parent_pop += front
                if (len(self.parent_pop) == pop_size): break
            # front exceeds parent population, append only what's necessary to reach pop_size
            else:
                self.parent_pop += front[:pop_size - len(self.parent_pop)]
                break

        ## NSGA-II : post step 2 : Clean Species
        # Remove the genomes that haven't passed the crowding-distance step
        # (The ones stagnated are already not on this dict)
        # Also rebuild SpeciesSet.genome_to_species
        species.genome_to_species = {}
        for _, sp in species.species.items():
            sp.members = {
                id: g
                for id, g in sp.members.items() if g in self.parent_pop
            }
            # map genome to species
            for id, g in sp.members.items():
                species.genome_to_species[id] = sp.key
        # Remove empty species
        species.species = {
            id: sp
            for id, sp in species.species.items() if len(sp.members) > 0
        }

        # self.parent_species should be a deepcopy of the species dictionary,
        # in order to avoid being modified by the species.speciate() method
        # the species in here are used to keep track of parent_genomes on next sort
        self.parent_species = {}
        for id, sp in species.species.items():
            self.parent_species[id] = Species(id, sp.created)
            self.parent_species[id].members = dict(sp.members)
            self.parent_species[id].representative = sp.representative

        ## NSGA-II : end : return parent population P(t+1) to be assigned to child population container Q(t+1)
        # this container will be used on the Tournament at NSGA2Reproduction.reproduce()
        # to create the real Q(t+1) population
        return {g.key: g for g in self.parent_pop}