GALib-Parallel
Header-only C++ library for parallel evolutionary algorithms
Loading...
Searching...
No Matches
AlgorithmBuilder.h
Go to the documentation of this file.
1#ifndef ALGORITHM_BUILDER_H
2#define ALGORITHM_BUILDER_H
3
4#pragma once
5
6#include <iostream>
7#include <memory>
8#include <yaml-cpp/yaml.h>
9
17#ifdef GALIB_WITH_CUDA
19#endif
20
22
23#ifdef GALIB_HAS_MPI
28#endif
29
31
32#ifdef GALIB_HAS_MPI
40#include <mpi.h>
41#endif
42
43namespace galib::utils {
53 template <typename GeneType>
55 private:
56 struct Defaults {
57 static constexpr double MUTATION_RATE = 0.05;
58 static constexpr double CROSSOVER_RATE = 0.8;
59 static constexpr std::size_t MAX_GENERATIONS = 100;
60 static constexpr bool USE_ELITISM = true;
61 static constexpr std::size_t THREADS = 1;
62
63 struct Standard {
64 static constexpr bool USE_CUDA = false;
65 };
66
67 struct Cellular {
68 static constexpr std::size_t ROWS = 10;
69 static constexpr std::size_t COLS = 10;
70 static constexpr bool USE_LOCAL_ELITISM = true;
71 };
72
73 struct DE {
74 static constexpr double F_WEIGHT = 0.8;
75 static constexpr double CR_RATE = 0.9;
76 };
77
78 struct Island {
79 static constexpr std::size_t MAX_GENERATIONS = 1000;
80 static constexpr std::size_t MIGRATION_INTERVAL = 50;
81 static constexpr std::size_t MIGRATION_SIZE = 5;
82 static constexpr double IMMIGRATION_QUOTA = 0.1;
83 static constexpr std::size_t BUFFER_CAPACITY = 10;
84 static constexpr const char* TOPOLOGY = "fully_connected";
85 };
86 };
87
88 public:
101#ifdef GALIB_HAS_MPI
102 static std::unique_ptr<Algorithm<GeneType>> build(
103 const std::string& config_path,
105 MPI_Comm mpi_comm = MPI_COMM_NULL
106 ) {
107#else
108 static std::unique_ptr<Algorithm<GeneType>> build(
109 const std::string& config_path,
111 ) {
112#endif
113 YAML::Node full_config = YAML::LoadFile(config_path);
114 const auto node = full_config["algorithm"];
115 if (!node) {
116 throw std::invalid_argument("Missing 'algorithm' section in config: " + config_path);
117 }
118
119 const auto type = node["type"].as<std::string>("standard");
120 std::unique_ptr<Algorithm<GeneType>> algo;
121
122 if (type == "standard") {
123 algo = buildStandardGA(full_config, ff);
124 } else if (type == "cellular") {
125 algo = buildCellularGA(full_config, ff);
126 } else if (type == "differential_evolution") {
127 algo = buildDifferentialEvolutionGA(full_config, ff);
128 } else if (type == "island") {
129#ifdef GALIB_HAS_MPI
130 algo = buildIslandGA(full_config, ff, mpi_comm);
131#else
132 throw std::runtime_error("Island GA requested but library was built without MPI support.");
133#endif
134 } else {
135 throw std::invalid_argument("Unknown algorithm type: " + type);
136 }
137
138 // Setup Logging
139 if (full_config["output"]) {
140 const auto out = full_config["output"];
141
142 // Console Logging
143 if (out["console"] && out["console"]["enabled"].as<bool>(false)) {
144 auto interval = out["console"]["interval"].as<std::size_t>(1);
145 algo->enableConsoleLogging(interval);
146 }
147
148 // File Logging
149 if (out["file"] && out["file"]["enabled"].as<bool>(false)) {
150 auto path = out["file"]["path"].as<std::string>("evolution.csv");
151 auto interval = out["file"]["interval"].as<std::size_t>(1);
152 algo->enableFileLogging(path, interval);
153 }
154 }
155
156 return algo;
157 }
158
165 static std::unique_ptr<Algorithm<GeneType>> buildStandardGA(
166 const YAML::Node& config,
168 ) {
169 const auto node = config["algorithm"];
170 StandardGAParams params;
171 params.mutation_rate = node["mutation_rate"].as<double>(Defaults::MUTATION_RATE);
172 params.crossover_rate = node["crossover_rate"].as<double>(Defaults::CROSSOVER_RATE);
173 params.max_generations = node["max_generations"].as<std::size_t>(Defaults::MAX_GENERATIONS);
174 params.use_elitism = node["use_elitism"].as<bool>(Defaults::USE_ELITISM);
175 params.threads = node["threads"].as<std::size_t>(Defaults::THREADS);
176
177 bool use_cuda = Defaults::Standard::USE_CUDA;
178 if (node["standard"]) {
179 use_cuda = node["standard"]["use_cuda"].as<bool>(Defaults::Standard::USE_CUDA);
180 }
181
182 auto selection = internal::OperatorBuilder<GeneType>::buildSelection(node["selection"]);
183 auto mutation = internal::OperatorBuilder<GeneType>::buildMutation(node["mutation"], ff.getLowerBound(), ff.getUpperBound());
184 auto crossover = internal::OperatorBuilder<GeneType>::buildCrossover(node["crossover"]);
185
186#ifdef GALIB_WITH_CUDA
187 if (use_cuda) {
188 return std::make_unique<cuda::StandardGACUDA<GeneType>>(
189 ff,
190 std::move(selection),
191 std::move(mutation),
192 std::move(crossover),
193 params.mutation_rate,
194 params.crossover_rate,
195 params.max_generations,
196 params.use_elitism
197 );
198 }
199#else
200 if (use_cuda) {
201 std::cerr << "[GALib] Warning: use_cuda=true but library was built without CUDA support."
202 " Falling back to StandardGA (CPU).\n";
203 }
204#endif
205
206 return std::make_unique<StandardGA<GeneType>>(
207 ff,
208 std::move(selection),
209 std::move(mutation),
210 std::move(crossover),
211 params.mutation_rate,
212 params.crossover_rate,
213 params.max_generations,
214 params.use_elitism,
215 params.threads
216 );
217 }
218
225 static std::unique_ptr<Algorithm<GeneType>> buildCellularGA(
226 const YAML::Node& config,
228 ) {
229 const auto node = config["algorithm"];
230 CellularGAParams params;
231 params.mutation_rate = node["mutation_rate"].as<double>(Defaults::MUTATION_RATE);
232 params.crossover_rate = node["crossover_rate"].as<double>(Defaults::CROSSOVER_RATE);
233 params.max_generations = node["max_generations"].as<std::size_t>(Defaults::MAX_GENERATIONS);
234 params.threads = node["threads"].as<std::size_t>(Defaults::THREADS);
235
236 if (node["cellular"]) {
237 params.use_local_elitism = node["cellular"]["use_local_elitism"].as<bool>(Defaults::Cellular::USE_LOCAL_ELITISM);
238 }
239
240 auto selection = internal::OperatorBuilder<GeneType>::buildLocalSelection(node["selection"]);
241 auto mutation = internal::OperatorBuilder<GeneType>::buildMutation(node["mutation"], ff.getLowerBound(), ff.getUpperBound());
242 auto crossover = internal::OperatorBuilder<GeneType>::buildCrossover(node["crossover"]);
243
244 return std::make_unique<CellularGA<GeneType>>(
245 ff,
246 std::move(selection),
247 std::move(mutation),
248 std::move(crossover),
249 params.mutation_rate,
250 params.crossover_rate,
251 params.max_generations,
252 params.threads,
253 params.use_local_elitism
254 );
255 }
256
263 static std::unique_ptr<Algorithm<GeneType>> buildDifferentialEvolutionGA(
264 const YAML::Node& config,
266 ) {
267 const auto node = config["algorithm"];
268 DEParams params;
269 params.crossover_rate = node["crossover_rate"].as<double>(Defaults::DE::CR_RATE);
270 params.max_generations = node["max_generations"].as<std::size_t>(Defaults::MAX_GENERATIONS);
271 params.threads = node["threads"].as<std::size_t>(Defaults::THREADS);
272
273 if (node["differential_evolution"]) {
274 params.f_weight = node["differential_evolution"]["f_weight"].as<double>(Defaults::DE::F_WEIGHT);
275 }
276
277 return std::make_unique<DifferentialEvolutionGA<GeneType>>(
278 ff,
279 params.f_weight,
280 params.crossover_rate,
281 params.max_generations,
282 params.threads
283 );
284 }
285
293#ifdef GALIB_HAS_MPI
294 static std::unique_ptr<Algorithm<GeneType>> buildIslandGA(
295 const YAML::Node& config,
297 MPI_Comm mpi_comm
298 ) {
299 const auto node = config["algorithm"];
300 IslandGAParams island_config;
301 island_config.max_generations = node["max_generations"].as<std::size_t>(Defaults::Island::MAX_GENERATIONS);
302 island_config.mutation_rate = node["mutation_rate"].as<double>(Defaults::MUTATION_RATE);
303 island_config.crossover_rate = node["crossover_rate"].as<double>(Defaults::CROSSOVER_RATE);
304 island_config.num_threads = node["threads"].as<std::size_t>(0);
305
306 std::size_t buffer_capacity = Defaults::Island::BUFFER_CAPACITY;
307 std::string topology_type = Defaults::Island::TOPOLOGY;
308
309 if (node["island"]) {
310 island_config.migration_interval = node["island"]["migration_interval"].as<std::size_t>(
312 island_config.migration_size = node["island"]["migration_size"].as<std::size_t>(Defaults::Island::MIGRATION_SIZE);
313 island_config.immigration_quota = node["island"]["immigration_quota"].as<double>(Defaults::Island::IMMIGRATION_QUOTA);
314 buffer_capacity = node["island"]["buffer_capacity"].as<std::size_t>(Defaults::Island::BUFFER_CAPACITY);
315 topology_type = node["island"]["topology"].as<std::string>(Defaults::Island::TOPOLOGY);
316 }
317
318 auto selection = internal::OperatorBuilder<GeneType>::buildSelection(node["selection"]);
319 auto mutation = internal::OperatorBuilder<GeneType>::buildMutation(node["mutation"], ff.getLowerBound(), ff.getUpperBound());
320 auto crossover = internal::OperatorBuilder<GeneType>::buildCrossover(node["crossover"]);
321
322 std::unique_ptr<DemeReplacer<GeneType>> replacer;
323 if (node["island"] && node["island"]["replacer"]) {
324 replacer = internal::OperatorBuilder<GeneType>::buildDemeReplacer(node["island"]["replacer"]);
325 } else {
326 replacer = std::make_unique<WorstReplacer<GeneType>>();
327 }
328
329 std::unique_ptr<DemeSelector<GeneType>> selector;
330 if (node["island"] && node["island"]["selector"]) {
331 selector = internal::OperatorBuilder<GeneType>::buildDemeSelector(node["island"]["selector"]);
332 } else {
333 selector = std::make_unique<ElitismSelector<GeneType>>();
334 }
335
336 auto serializer = std::make_unique<internal::BinarySerializer<GeneType>>();
337 std::size_t max_payload = serializer->getSerializedSize(island_config.migration_size, ff.size());
338 auto comm = std::make_unique<internal::MpiCommunicator<GeneType>>(*serializer, max_payload, mpi_comm);
339 auto buffer = std::make_unique<internal::CircularBuffer<GeneType>>(buffer_capacity, island_config.migration_size);
340
341 std::unique_ptr<const Topology> topology;
342 int world_size;
343 MPI_Comm_size(mpi_comm, &world_size);
344
345 if (topology_type == "fully_connected") {
346 topology = std::make_unique<FullyConnectedTopology>(world_size);
347 } else if (topology_type == "one_way_ring") {
348 topology = std::make_unique<OneWayRingTopology>(world_size);
349 } else if (topology_type == "bidirectional_ring") {
350 topology = std::make_unique<BidirectionalRingTopology>(world_size);
351 } else {
352 throw std::invalid_argument("Unknown topology type: " + topology_type);
353 }
354
355 bool elitism = node["use_elitism"].as<bool>(true);
356
357 return std::make_unique<IslandGA<GeneType>>(
358 ff,
359 std::move(selection),
360 std::move(mutation),
361 std::move(crossover),
362 std::move(replacer),
363 std::move(selector),
364 std::move(buffer),
365 std::move(comm),
366 std::move(topology),
367 std::move(serializer),
368 island_config,
369 elitism
370 );
371 }
372#endif
373 };
374} // namespace galib::utils
375
376#endif // ALGORITHM_BUILDER_H
Abstract base class for objective functions to be minimized.
virtual GeneType getLowerBound(std::size_t dimension) const =0
Gets the lower bound for a specific dimension.
virtual GeneType getUpperBound(std::size_t dimension) const =0
Gets the upper bound for a specific dimension.
virtual std::size_t size() const =0
Returns the dimensionality of the optimization problem.
static std::unique_ptr< Crossover< GeneType > > buildCrossover(const YAML::Node &node)
Builds a crossover operator.
static std::unique_ptr< Mutation< GeneType > > buildMutation(const YAML::Node &node, GeneType lb=0, GeneType ub=0)
Builds a mutation operator.
static std::unique_ptr< DemeSelector< GeneType > > buildDemeSelector(const YAML::Node &node)
Builds a deme selector policy (for Island GA).
static std::unique_ptr< LocalSelection< GeneType > > buildLocalSelection(const YAML::Node &node)
Builds a local selection operator (for Cellular GA).
static std::unique_ptr< DemeReplacer< GeneType > > buildDemeReplacer(const YAML::Node &node)
Builds a deme replacer policy (for Island GA).
static std::unique_ptr< Selection< GeneType > > buildSelection(const YAML::Node &node)
Builds a selection operator.
A high-level factory for instantiating GA algorithms from YAML configurations.
static std::unique_ptr< Algorithm< GeneType > > build(const std::string &config_path, FitnessFunction< GeneType > &ff, MPI_Comm mpi_comm=MPI_COMM_NULL)
Unified entry point to build an algorithm from a config file.
static std::unique_ptr< Algorithm< GeneType > > buildCellularGA(const YAML::Node &config, FitnessFunction< GeneType > &ff)
Builds a CellularGA instance.
static std::unique_ptr< Algorithm< GeneType > > buildStandardGA(const YAML::Node &config, FitnessFunction< GeneType > &ff)
Builds a StandardGA instance.
static std::unique_ptr< Algorithm< GeneType > > buildDifferentialEvolutionGA(const YAML::Node &config, FitnessFunction< GeneType > &ff)
Builds a DifferentialEvolutionGA instance.
static std::unique_ptr< Algorithm< GeneType > > buildIslandGA(const YAML::Node &config, FitnessFunction< GeneType > &ff, MPI_Comm mpi_comm)
Builds an IslandGA instance.
double mutation_rate
Probability of mutation occurring for each individual [0.0, 1.0].
bool use_elitism
Whether to preserve the best individual across generations.
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).
Configuration parameters specific to the cellular genetic algorithm.
bool use_local_elitism
Keeps the best individual among the current cell and its offspring.
Parameters for the Differential Evolution Genetic Algorithm.
Definition DEParams.h:15
double f_weight
Definition DEParams.h:16
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.
Parameters for the Standard (Generational) Genetic Algorithm.