GALib-Parallel
Header-only C++ library for parallel evolutionary algorithms
Loading...
Searching...
No Matches
CellularGA.h
Go to the documentation of this file.
1#ifndef CELLULAR_GA_H
2#define CELLULAR_GA_H
3
4#pragma once
5
6#include "core/Population.h"
11
12#include <tbb/parallel_for.h>
13#include <tbb/blocked_range.h>
14#include <tbb/global_control.h>
15
16#include <memory>
17#include <random>
18#include <utility>
19#include <fstream>
20#include <filesystem>
21#include <string>
22#include <cmath>
23#include <stdexcept>
24
26
27namespace galib {
28
49template <typename GeneType = double>
50class CellularGA : public Algorithm<GeneType> {
51private:
52 FitnessFunction<GeneType>& fitness_function_m;
53 std::unique_ptr<LocalSelection<GeneType>> local_selection_m;
54 std::unique_ptr<Mutation<GeneType>> mutation_m;
55 std::unique_ptr<Crossover<GeneType>> crossover_m;
56
57 double mutation_rate_m;
58 double crossover_rate_m;
59 std::size_t max_generations_m;
60 std::size_t threads_m;
61 bool use_local_elitism_m;
62
63 std::size_t rows_m;
64 std::size_t cols_m;
65
66private:
67 static std::pair<std::size_t, std::size_t> inferGridShape(std::size_t population_size) {
68 if (population_size == 0) {
69 throw std::invalid_argument("Population size cannot be zero.");
70 }
71
72 std::size_t rows = static_cast<std::size_t>(std::sqrt(static_cast<long double>(population_size)));
73 while (rows > 1 && population_size % rows != 0) {
74 --rows;
75 }
76
77 return {rows, population_size / rows};
78 }
79
80 void evaluatePopulation(Population<GeneType>& population) {
81 const std::size_t population_size = population.size();
82
83 tbb::parallel_for(
84 tbb::blocked_range<std::size_t>(0, population_size),
85 [&](const tbb::blocked_range<std::size_t>& range) {
86 for (std::size_t i = range.begin(); i < range.end(); ++i) {
87 const double score = fitness_function_m.evaluate(population[i].getGenotype());
88 population[i].setFitness(score);
89 }
90 }
91 );
92 }
93
94 Individual<GeneType> evolveCell(
95 const Population<GeneType>& population,
96 std::size_t row,
97 std::size_t col
98 ) const {
99 thread_local static std::random_device rd;
100 thread_local static std::mt19937_64 gen(rd());
101 thread_local static std::uniform_real_distribution<double> dist(0.0, 1.0);
102
103 const Individual<GeneType>& current = population[row * cols_m + col];
104 const Individual<GeneType>& neighbor = local_selection_m->select(population, row, col, rows_m, cols_m);
105
108
109 if (dist(gen) < crossover_rate_m) {
110 auto children = crossover_m->crossover(current, neighbor);
111 child1 = std::move(children.first);
112 child2 = std::move(children.second);
113 } else {
114 child1 = current;
115 child2 = neighbor;
116 }
117
118 mutation_m->mutate(child1, mutation_rate_m);
119 mutation_m->mutate(child2, mutation_rate_m);
120
121 child1.setFitness(fitness_function_m.evaluate(child1.getGenotype()));
122 child2.setFitness(fitness_function_m.evaluate(child2.getGenotype()));
123
124 if (use_local_elitism_m) {
125 const Individual<GeneType>* best = &current;
126
127 if (child1.getFitness() < best->getFitness()) {
128 best = &child1;
129 }
130 if (child2.getFitness() < best->getFitness()) {
131 best = &child2;
132 }
133
134 return *best;
135 } else {
136 return (child1.getFitness() < child2.getFitness()) ? child1 : child2;
137 }
138 }
139
140public:
158 std::unique_ptr<LocalSelection<GeneType>> local_sel,
159 std::unique_ptr<Mutation<GeneType>> mu,
160 std::unique_ptr<Crossover<GeneType>> cs,
161 double m_rate,
162 double c_rate,
163 std::size_t max_gen,
164 std::size_t threads,
165 bool elitism = false
166 )
167 : fitness_function_m(ff),
168 local_selection_m(std::move(local_sel)),
169 mutation_m(std::move(mu)),
170 crossover_m(std::move(cs)),
171 mutation_rate_m(m_rate),
172 crossover_rate_m(c_rate),
173 max_generations_m(max_gen),
174 threads_m(threads),
175 rows_m(0),
176 cols_m(0),
177 use_local_elitism_m(elitism) {}
178
190 void run(Population<GeneType>& population) override {
191 if (population.empty()) {
192 return;
193 }
194
195 std::tie(rows_m, cols_m) = inferGridShape(population.size());
196
197 Population<GeneType> new_population(population.size(), population.getNumGenes());
198 std::unique_ptr<tbb::global_control> tbb_control;
199
200 if (threads_m > 0) {
201 tbb_control = std::make_unique<tbb::global_control>(
202 tbb::global_control::max_allowed_parallelism,
203 threads_m
204 );
205 }
206
207 evaluatePopulation(population);
208
209 for (std::size_t generation_idx = 0; generation_idx < max_generations_m; ++generation_idx) {
210 this->notifyLoggers(generation_idx, population);
211
212 tbb::parallel_for(
213 tbb::blocked_range<std::size_t>(0, population.size()),
214 [&](const tbb::blocked_range<std::size_t>& range) {
215 for (std::size_t i = range.begin(); i < range.end(); ++i) {
216 const std::size_t row = i / cols_m;
217 const std::size_t col = i % cols_m;
218
219 new_population[i] = evolveCell(population, row, col);
220 }
221 }
222 );
223
224 std::swap(population, new_population);
225 }
226
227 this->notifyLoggers(max_generations_m, population);
228 }
229 };
230
231} // namespace galib
232
233#endif // CELLULAR_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
Cellular genetic algorithm that evolves individuals on a local 2D neighbourhood.
Definition CellularGA.h:50
void run(Population< GeneType > &population) override
Runs the cellular evolutionary optimisation loop.
Definition CellularGA.h:190
CellularGA(FitnessFunction< GeneType > &ff, std::unique_ptr< LocalSelection< GeneType > > local_sel, std::unique_ptr< Mutation< GeneType > > mu, std::unique_ptr< Crossover< GeneType > > cs, double m_rate, double c_rate, std::size_t max_gen, std::size_t threads, bool elitism=false)
Constructs the cellular genetic algorithm with all required operators.
Definition CellularGA.h:156
Interface for crossover (recombination) operators.
Definition Crossover.h:20
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
std::vector< GeneType > & getGenotype()
Returns a reference to the genotype vector.
Definition Individual.h:53
void setFitness(double new_fitness)
Sets a new fitness score.
Definition Individual.h:83
Abstract interface for neighbourhood-based parent selection in a cellular GA.
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
std::size_t size() const
Returns the total number of individuals in the population.
Definition Population.h:68