Loading [MathJax]/extensions/tex2jax.js
numerical-collection-cpp 0.10.0
A collection of algorithms in numerical analysis implemented in C++
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages Concepts
real_value_genetic_optimizer.h
Go to the documentation of this file.
1/*
2 * Copyright 2025 MusicScience37 (Kenta Kabashima)
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
20#pragma once
21
22#include <algorithm>
23#include <cmath>
24#include <cstdint>
25#include <iterator>
26#include <limits>
27#include <random>
28#include <string_view>
29#include <utility>
30
31#include <Eigen/Core>
32
44
45namespace num_collect::opt {
46
49 logging::log_tag_view("num_collect::opt::real_value_genetic_optimizer");
50
58template <concepts::objective_function ObjectiveFunction,
59 index_type BitsPerDimension = 10> // NOLINT(*-magic-numbers)
61
69template <concepts::multi_variate_objective_function ObjectiveFunction,
70 index_type BitsPerDimension>
71class real_value_genetic_optimizer<ObjectiveFunction, BitsPerDimension>
72 : public optimizer_base<
73 real_value_genetic_optimizer<ObjectiveFunction, BitsPerDimension>> {
74public:
76 using this_type =
78
80 using objective_function_type = ObjectiveFunction;
81
83 using variable_type = typename objective_function_type::variable_type;
84
86 using variable_scalar_type = typename variable_type::Scalar;
87
89 using value_type = typename objective_function_type::value_type;
90
92 using random_number_generator_type = std::mt19937;
93
100 const ObjectiveFunction& obj_fun = ObjectiveFunction())
102 obj_fun_(obj_fun) {}
103
109 void change_objective_function(const objective_function_type& obj_fun) {
110 obj_fun_ = obj_fun;
111 }
112
119 void init(const variable_type& lower, const variable_type& upper) {
120 NUM_COLLECT_PRECONDITION(lower.size() == upper.size(), this->logger(),
121 "Lower and upper limits must have the same size.");
122 lower_ = lower;
123 upper_ = upper;
124 width_ = upper_ - lower_;
125 dim_ = lower_.size();
126
127 opt_value_ = std::numeric_limits<value_type>::max();
128 iterations_ = 0;
129 evaluations_ = 0;
130
131 // Allocate memory. (This value is not used.)
132 buffer_variable_ = lower_;
133
134 binary_population_.reserve(population_size_);
135 std::generate_n(
136 std::back_inserter(binary_population_), population_size_, [this]() {
137 binary_variable_type binary_var(dim_);
138 std::generate(binary_var.begin(), binary_var.end(), [this]() {
139 return random_number_generator_() & binary_mask;
140 });
141 return binary_var;
142 });
143
144 population_values_.resize(population_size_);
145 std::transform(binary_population_.begin(), binary_population_.end(),
146 population_values_.begin(),
147 [this](const auto& binary_var) { return evaluate_on(binary_var); });
148
149 // Allocate memory. (This value is not used.)
150 prev_binary_population_ = binary_population_;
151 buffer_probabilities_.resize(population_size_);
152 }
153
157 void iterate() {
158 // Convert to positive values for probabilities.
159 // The smaller the value, the higher the probability.
160 buffer_probabilities_ = (-population_values_).array().exp();
161
162 // Calculate cumulative probabilities.
163 for (index_type i = 1; i < population_size_; ++i) {
164 buffer_probabilities_[i] += buffer_probabilities_[i - 1];
165 }
166 const variable_scalar_type sum =
167 buffer_probabilities_(population_size_ - 1);
168 buffer_probabilities_ /= sum;
169 buffer_probabilities_(population_size_ - 1) =
170 static_cast<variable_scalar_type>(1);
171
172 // Select parents.
173 std::swap(binary_population_, prev_binary_population_);
174 std::generate(
175 binary_population_.begin(), binary_population_.end(), [this]() {
176 const variable_scalar_type probability =
177 probability_distribution_(random_number_generator_);
178 const auto iter =
179 std::lower_bound(buffer_probabilities_.begin(),
180 buffer_probabilities_.end(), probability);
181 const index_type index = iter - buffer_probabilities_.begin();
182 NUM_COLLECT_DEBUG_ASSERT(index < population_size_);
183 return prev_binary_population_[index];
184 });
185
186 // Crossover.
187 for (index_type i = 0; i < population_size_; i += 2) {
188 const bool do_crossover =
189 crossover_distribution_(random_number_generator_);
190 if (!do_crossover) {
191 continue;
192 }
193 for (index_type d = 0; d < dim_; ++d) {
194 const binary_scalar_type mask =
195 random_number_generator_() & binary_mask;
196 const binary_scalar_type mask_inv = (~mask) & binary_mask;
197 const binary_scalar_type first_child_scalar =
198 (binary_population_[i][d] & mask) |
199 (binary_population_[i + 1][d] & mask_inv);
200 const binary_scalar_type second_child_scalar =
201 (binary_population_[i][d] & mask_inv) |
202 (binary_population_[i + 1][d] & mask);
203 binary_population_[i][d] = first_child_scalar;
204 binary_population_[i + 1][d] = second_child_scalar;
205 }
206 }
207
208 // Mutate.
209 for (auto& binary_variable : binary_population_) {
210 for (auto& binary_scalar : binary_variable) {
211 auto mask = static_cast<binary_scalar_type>(1);
212 for (index_type b = 0; b < num_bits_per_dimension; ++b) {
213 if (mutation_distribution_(random_number_generator_)) {
214 binary_scalar ^= mask;
215 }
216 mask <<= 1U;
217 }
218 }
219 }
220
221 // Evaluate function values.
222 std::transform(binary_population_.begin(), binary_population_.end(),
223 population_values_.begin(),
224 [this](const auto& binary_var) { return evaluate_on(binary_var); });
225
226 ++iterations_;
227 }
228
232 [[nodiscard]] auto is_stop_criteria_satisfied() const -> bool {
233 return evaluations_ >= max_evaluations_;
234 }
235
239 void configure_iteration_logger(logging::iterations::iteration_logger<
240 real_value_genetic_optimizer<ObjectiveFunction>>& iteration_logger)
241 const {
242 iteration_logger.template append<index_type>(
243 "Iter.", &this_type::iterations);
244 iteration_logger.template append<index_type>(
245 "Eval.", &this_type::evaluations);
246 iteration_logger.template append<value_type>(
247 "Value", &this_type::opt_value);
248 }
249
253 [[nodiscard]] auto opt_variable() const -> const variable_type& {
254 return opt_variable_;
255 }
256
260 [[nodiscard]] auto opt_value() const -> const value_type& {
261 return opt_value_;
262 }
263
267 [[nodiscard]] auto iterations() const noexcept -> index_type {
268 return iterations_;
269 }
270
274 [[nodiscard]] auto evaluations() const noexcept -> index_type {
275 return evaluations_;
276 }
277
284 auto max_evaluations(index_type value) -> this_type& {
285 NUM_COLLECT_PRECONDITION(value > 0, this->logger(),
286 "Maximum number of function evaluations must be a positive "
287 "integer.");
288 max_evaluations_ = value;
289 return *this;
290 }
291
298 auto seed(random_number_generator_type::result_type value) -> this_type& {
299 random_number_generator_.seed(value);
300 return *this;
301 }
302
309 auto population_size(index_type value) -> this_type& {
310 NUM_COLLECT_PRECONDITION(value > 0, this->logger(),
311 "Population size must be a positive integer.");
312 NUM_COLLECT_PRECONDITION(value % 2 == 0, this->logger(),
313 "Population size must be an even number.");
314 population_size_ = value;
315 return *this;
316 }
317
324 auto crossover_probability(variable_scalar_type value) -> this_type& {
326 static_cast<variable_scalar_type>(0) <= value &&
327 value <= static_cast<variable_scalar_type>(1),
328 this->logger(), "Probability of crossover must be in [0, 1].");
329 crossover_distribution_ = std::bernoulli_distribution(value);
330 return *this;
331 }
332
339 auto mutation_probability(variable_scalar_type value) -> this_type& {
341 static_cast<variable_scalar_type>(0) <= value &&
342 value <= static_cast<variable_scalar_type>(1),
343 this->logger(), "Probability of mutation must be in [0, 1].");
344 mutation_distribution_ = std::bernoulli_distribution(value);
345 return *this;
346 }
347
348private:
350 static constexpr index_type num_bits_per_dimension = BitsPerDimension;
351
353 using binary_scalar_type = std::uint32_t;
354
356 static constexpr index_type dim_at_compile_time =
357 variable_type::RowsAtCompileTime;
358
360 using binary_variable_type =
361 Eigen::Vector<binary_scalar_type, dim_at_compile_time>;
362
364 static constexpr binary_scalar_type binary_mask =
365 (static_cast<binary_scalar_type>(1)
366 << static_cast<binary_scalar_type>(num_bits_per_dimension)) -
367 static_cast<binary_scalar_type>(1);
368
375 [[nodiscard]] auto evaluate_on(const binary_variable_type& binary_variable)
376 -> value_type {
377 static constexpr variable_scalar_type binary_to_rate =
378 static_cast<variable_scalar_type>(1) /
379 static_cast<variable_scalar_type>(binary_mask);
380 for (index_type d = 0; d < dim_; ++d) {
381 const binary_scalar_type binary_scalar =
382 util::gray_code_to_binary(binary_variable(d));
383 buffer_variable_(d) =
384 static_cast<variable_scalar_type>(binary_scalar) *
385 binary_to_rate * width_(d) +
386 lower_(d);
387 }
388
389 obj_fun_.evaluate_on(buffer_variable_);
390 const value_type value = correct_value_if_needed(obj_fun_.value());
391 if (value < opt_value_) {
392 opt_variable_ = buffer_variable_;
393 opt_value_ = value;
394 }
395 ++evaluations_;
396 return value;
397 }
398
405 [[nodiscard]] static auto correct_value_if_needed(value_type value) noexcept
406 -> value_type {
407 constexpr auto safe_limit = std::numeric_limits<value_type>::max();
408 if (!isfinite(value)) {
409 return safe_limit;
410 }
411 return value;
412 }
413
415 objective_function_type obj_fun_;
416
418 variable_type lower_{};
419
421 variable_type upper_{};
422
424 variable_type width_{};
425
427 index_type dim_{0};
428
430 static constexpr index_type default_population_size = 200;
431
433 index_type population_size_{default_population_size};
434
436 util::vector<binary_variable_type> binary_population_;
437
439 util::vector<binary_variable_type> prev_binary_population_;
440
442 Eigen::VectorX<value_type> population_values_;
443
445 random_number_generator_type random_number_generator_{
446 std::random_device()()};
447
449 std::uniform_real_distribution<variable_scalar_type>
450 probability_distribution_{static_cast<variable_scalar_type>(0),
451 static_cast<variable_scalar_type>(1)};
452
454 static constexpr auto default_crossover_probability =
455 static_cast<variable_scalar_type>(0.6);
456
458 std::bernoulli_distribution crossover_distribution_{
459 default_crossover_probability};
460
462 static constexpr auto default_mutation_probability =
463 static_cast<variable_scalar_type>(0.1);
464
466 std::bernoulli_distribution mutation_distribution_{
467 default_mutation_probability};
468
470 variable_type buffer_variable_{};
471
473 Eigen::VectorX<variable_scalar_type> buffer_probabilities_{};
474
476 variable_type opt_variable_{};
477
479 value_type opt_value_{std::numeric_limits<value_type>::max()};
480
482 index_type iterations_{0};
483
485 index_type evaluations_{0};
486
488 static constexpr index_type default_max_evaluations = 10000;
489
491 index_type max_evaluations_{default_max_evaluations};
492};
493
494} // namespace num_collect::opt
Definition of assertion macros.
Class of tags of logs without memory management.
Base class of implementations of optimization algorithms.
Class to perform optimization for real-valued variables using genetic algorithm iba1994.
Concept of multi-variate objective functions in optimization.
Concept of objective functions in optimization.
Definition of functions for gray code.
Definition of index_type type.
Definition of isfinite function.
Definition of iteration_logger class.
Definition of log_tag_view class.
Definition of multi_variate_objective_function concept.
std::ptrdiff_t index_type
Type of indices in this library.
Definition index_type.h:33
auto isfinite(const T &val) -> bool
Check whether a number is finite.
Definition isfinite.h:39
Namespace of optimization algorithms.
constexpr auto real_value_genetic_optimizer_tag
Tag of real_value_genetic_optimizer.
constexpr auto gray_code_to_binary(std::uint32_t gray_code) -> std::uint32_t
Convert an integer from Gray code to binary warren2013.
Definition gray_code.h:54
Definition of objective_function concept.
Definition of optimizer_base class.
Definition of NUM_COLLECT_PRECONDITION macro.
#define NUM_COLLECT_PRECONDITION(CONDITION,...)
Check whether a precondition is satisfied and throw an exception if not.
Definition of vector class.