GALib-Parallel
Header-only C++ library for parallel evolutionary algorithms
Loading...
Searching...
No Matches
IslandGA.h
Go to the documentation of this file.
1#ifndef ISLAND_GA_H
2#define ISLAND_GA_H
3
4#pragma once
5
6#include "core/Population.h"
11
20
21#include <iostream>
22#include <random>
23#include <utility>
24#include <algorithm>
25#include <omp.h>
26
27
28namespace galib {
38 template <typename GeneType = double>
39 class IslandGA : public Algorithm<GeneType> {
40 private:
41 FitnessFunction<GeneType>& fitness_function_m;
42
43 std::unique_ptr<Selection<GeneType>> selection_m;
44 std::unique_ptr<Mutation<GeneType>> mutation_m;
45 std::unique_ptr<Crossover<GeneType>> crossover_m;
46 std::unique_ptr<DemeReplacer<GeneType>> deme_replacer_m;
47 std::unique_ptr<DemeSelector<GeneType>> deme_selector_m;
48
49 std::unique_ptr<internal::MigrationBuffer<GeneType>> migration_buffer_m;
50 std::unique_ptr<internal::Communicator<GeneType>> communicator_m;
51 std::unique_ptr<const Topology> topology_m;
52 std::unique_ptr<internal::Serializer<GeneType>> serializer_m;
53
54 const IslandGAParams config_m;
55
56 bool use_elitism_m;
57
58 void evaluatePopulation(Population<GeneType>& population) {
59 const std::size_t population_size = population.size();
60 const int n_threads = (config_m.num_threads > 0) ? static_cast<int>(config_m.num_threads) : omp_get_max_threads();
61
62 #pragma omp parallel for schedule(dynamic) num_threads(n_threads)
63 for (std::size_t i = 0; i < population_size; ++i) {
64 double score = fitness_function_m.evaluate(population[i].getGenotype());
65 population[i].setFitness(score);
66 }
67 }
68
69 void generateNextGeneration(const Population<GeneType>& current_population, Population<GeneType>& new_population) {
70 const std::size_t population_size = current_population.size();
71 std::size_t elitism_offset = 0;
72 const int n_threads = (config_m.num_threads > 0) ? static_cast<int>(config_m.num_threads) : omp_get_max_threads();
73
74 if (use_elitism_m) {
75 new_population[0] = current_population.getBestIndividual();
76 elitism_offset = 1;
77 }
78
79 #pragma omp parallel for schedule(static) num_threads(n_threads)
80 for (std::size_t i = elitism_offset; i < population_size; i += 2) {
81 thread_local static std::random_device tl_rd;
82 thread_local static std::mt19937_64 tl_gen(tl_rd());
83 thread_local static std::uniform_real_distribution<double> tl_dist(0.0, 1.0);
84
85 const Individual<GeneType>& parent1 = selection_m->select(current_population);
86 const Individual<GeneType>& parent2 = selection_m->select(current_population);
87
88 if (tl_dist(tl_gen) < config_m.crossover_rate) {
89 auto children = crossover_m->crossover(parent1, parent2);
90 new_population[i] = std::move(children.first);
91 if (i + 1 < population_size) {
92 new_population[i + 1] = std::move(children.second);
93 }
94 } else {
95 new_population[i] = parent1;
96 if (i + 1 < population_size) {
97 new_population[i + 1] = parent2;
98 }
99 }
100
101 mutation_m->mutate(new_population[i], config_m.mutation_rate);
102 if (i + 1 < population_size) {
103 mutation_m->mutate(new_population[i + 1], config_m.mutation_rate);
104 }
105 }
106 }
107
108 void initializeCommunication() {
109 communicator_m->startReceiving(*migration_buffer_m);
110 }
111
112 void finalizeCommunication() {
113 communicator_m->stopReceiving();
114 }
115
116 void handleIncomingMigrants(Population<GeneType>& population) {
117 communicator_m->update();
118
119 std::vector<Individual<GeneType>> incoming_migrants;
120
121 migration_buffer_m->popAll(incoming_migrants);
122 if (incoming_migrants.empty()) return;
123
124 const std::size_t max_immigrants = static_cast<std::size_t>(
125 static_cast<double>(population.size()) * config_m.immigration_quota
126 );
127
128 if (incoming_migrants.size() > max_immigrants) {
129 std::ranges::nth_element(
130 incoming_migrants,
131 incoming_migrants.begin() + max_immigrants,
132 std::less{}
133 );
134
135 incoming_migrants.resize(max_immigrants);
136 }
137
138 deme_replacer_m->replaceDeme(population, std::move(incoming_migrants));
139 }
140
141 void handleOutgoingMigrants(const Population<GeneType>& population, const std::size_t generation_idx) {
142 if ((generation_idx + 1) % config_m.migration_interval != 0) return;
143
144 auto outgoing_migrants = deme_selector_m->selectDeme(population, config_m.migration_size);
145
146 const std::vector<std::size_t> neighbors = topology_m->getLinks(communicator_m->getRank()).neighbors_out;
147
148
149 communicator_m->broadcast(outgoing_migrants, neighbors);
150 }
151
152 void synchronizeGlobalBest(Population<GeneType>& population) {
153 const Individual<GeneType> global_best = communicator_m->allReduceBest(population.getBestIndividual());
154
155 if (global_best.getFitness() < population.getBestIndividual().getFitness()) {
156 population[0] = std::move(global_best);
157 }
158 }
159
160 public:
179 std::unique_ptr<Selection<GeneType>> sel,
180 std::unique_ptr<Mutation<GeneType>> mu,
181 std::unique_ptr<Crossover<GeneType>> cs,
182 std::unique_ptr<DemeReplacer<GeneType>> replacer,
183 std::unique_ptr<DemeSelector<GeneType>> selector,
184 std::unique_ptr<internal::MigrationBuffer<GeneType>> buffer,
185 std::unique_ptr<internal::Communicator<GeneType>> comm,
186 std::unique_ptr<const Topology> topology,
187 std::unique_ptr<internal::Serializer<GeneType>> serializer,
188 const IslandGAParams& config,
189 const bool elitism = true
190 ) : fitness_function_m(ff),
191 selection_m(std::move(sel)),
192 mutation_m(std::move(mu)),
193 crossover_m(std::move(cs)),
194 deme_replacer_m(std::move(replacer)),
195 deme_selector_m(std::move(selector)),
196 migration_buffer_m(std::move(buffer)),
197 communicator_m(std::move(comm)),
198 topology_m(std::move(topology)),
199 serializer_m(std::move(serializer)),
200 config_m(config),
201 use_elitism_m(elitism) {}
202
212 void run(Population<GeneType>& population) override {
213 if (population.empty()) { return; }
214
215 const std::size_t population_size = population.size();
216 const std::size_t num_genes = population.getNumGenes();
217
218 Population<GeneType> new_population(population_size, num_genes);
219
220 evaluatePopulation(population);
221
222 initializeCommunication();
223
224 for (std::size_t generation_idx = 0; generation_idx < config_m.max_generations; ++generation_idx) {
225 this->notifyLoggers(generation_idx, population);
226
227 handleIncomingMigrants(population);
228
229 generateNextGeneration(population, new_population);
230
231 std::swap(population, new_population);
232
233 evaluatePopulation(population);
234
235 handleOutgoingMigrants(population, generation_idx);
236 }
237
238 this->notifyLoggers(config_m.max_generations, population);
239
240 finalizeCommunication();
241
242 synchronizeGlobalBest(population);
243 }
244 };
245}
246
247#endif // ISLAND_GA_H
Abstract base class for all optimization algorithms.
Definition Algorithm.h:22
void notifyLoggers(std::size_t gen, const Population< GeneType > &pop)
Notifies all active loggers to record the current state.
Definition Algorithm.h:32
Interface for crossover (recombination) operators.
Definition Crossover.h:20
Interface for integrating incoming migrants into the local population.
Interface for choosing individuals to migrate out of the island.
Abstract base class for objective functions to be minimized.
virtual double evaluate(const std::vector< GeneType > &phenotype) const =0
Evaluates the fitness of a candidate solution.
Represents a single candidate solution in the population.
Definition Individual.h:23
double getFitness() const
Gets the current fitness score.
Definition Individual.h:77
Implementation of the Island Model Genetic Algorithm (Coarse-Grained Parallel GA).
Definition IslandGA.h:39
IslandGA(FitnessFunction< GeneType > &ff, std::unique_ptr< Selection< GeneType > > sel, std::unique_ptr< Mutation< GeneType > > mu, std::unique_ptr< Crossover< GeneType > > cs, std::unique_ptr< DemeReplacer< GeneType > > replacer, std::unique_ptr< DemeSelector< GeneType > > selector, std::unique_ptr< internal::MigrationBuffer< GeneType > > buffer, std::unique_ptr< internal::Communicator< GeneType > > comm, std::unique_ptr< const Topology > topology, std::unique_ptr< internal::Serializer< GeneType > > serializer, const IslandGAParams &config, const bool elitism=true)
Constructs an IslandGA instance.
Definition IslandGA.h:177
void run(Population< GeneType > &population) override
Runs the evolutionary loop for this island.
Definition IslandGA.h:212
Interface for mutation operators.
Definition Mutation.h:19
A container for a group of individuals in the genetic algorithm.
Definition Population.h:24
bool empty() const
Checks if the population is empty.
Definition Population.h:74
std::size_t getNumGenes() const
Returns the number of genes for individuals in this population.
Definition Population.h:80
const Individual< GeneType > & getBestIndividual() const
Finds and returns the individual with the best fitness score.
Definition Population.h:112
std::size_t size() const
Returns the total number of individuals in the population.
Definition Population.h:68
Interface for selection operators.
Definition Selection.h:20
Interface for network communication between islands.
Interface for a thread-safe buffer that stores incoming migrants.
Interface for converting individuals to/from byte streams for network transport.
Definition Serializer.h:19
double mutation_rate
Probability of mutation occurring for each individual [0.0, 1.0].
std::size_t max_generations
Maximum number of generations to evolve.
double crossover_rate
Probability of crossover between parents [0.0, 1.0].
std::size_t num_threads
Number of OpenMP threads to use. 0 means use system default (omp_get_max_threads).
Parameters for the Island Model Genetic Algorithm.
std::size_t migration_size
Number of individuals to send per migration event.
double immigration_quota
Maximum fraction of the population that can be replaced by migrants in one step.
std::size_t migration_interval
Number of generations between migration events.