File indexing completed on 2025-04-26 08:38:01
0001
0002
0003
0004
0005
0006
0007 #ifndef BOOST_MATH_OPTIMIZATION_DIFFERENTIAL_EVOLUTION_HPP
0008 #define BOOST_MATH_OPTIMIZATION_DIFFERENTIAL_EVOLUTION_HPP
0009 #include <atomic>
0010 #include <boost/math/optimization/detail/common.hpp>
0011 #include <cmath>
0012 #include <limits>
0013 #include <mutex>
0014 #include <random>
0015 #include <sstream>
0016 #include <stdexcept>
0017 #include <thread>
0018 #include <utility>
0019 #include <vector>
0020
0021 namespace boost::math::optimization {
0022
0023
0024
0025
0026
0027
0028
0029
0030 template <typename ArgumentContainer> struct differential_evolution_parameters {
0031 using Real = typename ArgumentContainer::value_type;
0032 using DimensionlessReal = decltype(Real()/Real());
0033 ArgumentContainer lower_bounds;
0034 ArgumentContainer upper_bounds;
0035
0036 DimensionlessReal mutation_factor = static_cast<DimensionlessReal>(0.65);
0037 DimensionlessReal crossover_probability = static_cast<DimensionlessReal>(0.5);
0038
0039 size_t NP = 500;
0040 size_t max_generations = 1000;
0041 ArgumentContainer const *initial_guess = nullptr;
0042 unsigned threads = std::thread::hardware_concurrency();
0043 };
0044
0045 template <typename ArgumentContainer>
0046 void validate_differential_evolution_parameters(differential_evolution_parameters<ArgumentContainer> const &de_params) {
0047 using std::isfinite;
0048 using std::isnan;
0049 std::ostringstream oss;
0050 detail::validate_bounds(de_params.lower_bounds, de_params.upper_bounds);
0051 if (de_params.NP < 4) {
0052 oss << __FILE__ << ":" << __LINE__ << ":" << __func__;
0053 oss << ": The population size must be at least 4, but requested population size of " << de_params.NP << ".";
0054 throw std::invalid_argument(oss.str());
0055 }
0056
0057
0058
0059
0060
0061
0062 auto F = de_params.mutation_factor;
0063 if (isnan(F) || F >= 1 || F <= 0) {
0064 oss << __FILE__ << ":" << __LINE__ << ":" << __func__;
0065 oss << ": F in (0, 1) is required, but got F=" << F << ".";
0066 throw std::domain_error(oss.str());
0067 }
0068 if (de_params.max_generations < 1) {
0069 oss << __FILE__ << ":" << __LINE__ << ":" << __func__;
0070 oss << ": There must be at least one generation.";
0071 throw std::invalid_argument(oss.str());
0072 }
0073 if (de_params.initial_guess) {
0074 detail::validate_initial_guess(*de_params.initial_guess, de_params.lower_bounds, de_params.upper_bounds);
0075 }
0076 if (de_params.threads == 0) {
0077 oss << __FILE__ << ":" << __LINE__ << ":" << __func__;
0078 oss << ": There must be at least one thread.";
0079 throw std::invalid_argument(oss.str());
0080 }
0081 }
0082
0083 template <typename ArgumentContainer, class Func, class URBG>
0084 ArgumentContainer differential_evolution(
0085 const Func cost_function, differential_evolution_parameters<ArgumentContainer> const &de_params, URBG &gen,
0086 std::invoke_result_t<Func, ArgumentContainer> target_value =
0087 std::numeric_limits<std::invoke_result_t<Func, ArgumentContainer>>::quiet_NaN(),
0088 std::atomic<bool> *cancellation = nullptr,
0089 std::vector<std::pair<ArgumentContainer, std::invoke_result_t<Func, ArgumentContainer>>> *queries = nullptr,
0090 std::atomic<std::invoke_result_t<Func, ArgumentContainer>> *current_minimum_cost = nullptr) {
0091 using Real = typename ArgumentContainer::value_type;
0092 using DimensionlessReal = decltype(Real()/Real());
0093 using ResultType = std::invoke_result_t<Func, ArgumentContainer>;
0094 using std::clamp;
0095 using std::isnan;
0096 using std::round;
0097 using std::uniform_real_distribution;
0098 validate_differential_evolution_parameters(de_params);
0099 const size_t dimension = de_params.lower_bounds.size();
0100 auto NP = de_params.NP;
0101 auto population = detail::random_initial_population(de_params.lower_bounds, de_params.upper_bounds, NP, gen);
0102 if (de_params.initial_guess) {
0103 population[0] = *de_params.initial_guess;
0104 }
0105 std::vector<ResultType> cost(NP, std::numeric_limits<ResultType>::quiet_NaN());
0106 std::atomic<bool> target_attained = false;
0107
0108 std::mutex mt;
0109
0110 std::vector<std::thread> thread_pool;
0111 auto const threads = de_params.threads;
0112 for (size_t j = 0; j < threads; ++j) {
0113
0114
0115
0116
0117
0118 thread_pool.emplace_back([&, j]() {
0119 for (size_t i = j; i < cost.size(); i += threads) {
0120 cost[i] = cost_function(population[i]);
0121 if (current_minimum_cost && cost[i] < *current_minimum_cost) {
0122 *current_minimum_cost = cost[i];
0123 }
0124 if (queries) {
0125 std::scoped_lock lock(mt);
0126 queries->push_back(std::make_pair(population[i], cost[i]));
0127 }
0128 if (!isnan(target_value) && cost[i] <= target_value) {
0129 target_attained = true;
0130 }
0131 }
0132 });
0133 }
0134 for (auto &thread : thread_pool) {
0135 thread.join();
0136 }
0137
0138 std::vector<ArgumentContainer> trial_vectors(NP);
0139 for (size_t i = 0; i < NP; ++i) {
0140 if constexpr (detail::has_resize_v<ArgumentContainer>) {
0141 trial_vectors[i].resize(dimension);
0142 }
0143 }
0144 std::vector<URBG> thread_generators(threads);
0145 for (size_t j = 0; j < threads; ++j) {
0146 thread_generators[j].seed(gen());
0147 }
0148
0149 std::vector<int> updated_indices(NP, 0);
0150
0151 for (size_t generation = 0; generation < de_params.max_generations; ++generation) {
0152 if (cancellation && *cancellation) {
0153 break;
0154 }
0155 if (target_attained) {
0156 break;
0157 }
0158 thread_pool.resize(0);
0159 for (size_t j = 0; j < threads; ++j) {
0160 thread_pool.emplace_back([&, j]() {
0161 auto& tlg = thread_generators[j];
0162 uniform_real_distribution<DimensionlessReal> unif01(DimensionlessReal(0), DimensionlessReal(1));
0163 for (size_t i = j; i < cost.size(); i += threads) {
0164 if (target_attained) {
0165 return;
0166 }
0167 if (cancellation && *cancellation) {
0168 return;
0169 }
0170 size_t r1, r2, r3;
0171 do {
0172 r1 = tlg() % NP;
0173 } while (r1 == i);
0174 do {
0175 r2 = tlg() % NP;
0176 } while (r2 == i || r2 == r1);
0177 do {
0178 r3 = tlg() % NP;
0179 } while (r3 == i || r3 == r2 || r3 == r1);
0180
0181 for (size_t k = 0; k < dimension; ++k) {
0182
0183 auto guaranteed_changed_idx = tlg() % dimension;
0184 if (unif01(tlg) < de_params.crossover_probability || k == guaranteed_changed_idx) {
0185 auto tmp = population[r1][k] + de_params.mutation_factor * (population[r2][k] - population[r3][k]);
0186 auto const &lb = de_params.lower_bounds[k];
0187 auto const &ub = de_params.upper_bounds[k];
0188
0189
0190 trial_vectors[i][k] = clamp(tmp, lb, ub);
0191 } else {
0192 trial_vectors[i][k] = population[i][k];
0193 }
0194 }
0195
0196 auto const trial_cost = cost_function(trial_vectors[i]);
0197 if (isnan(trial_cost)) {
0198 continue;
0199 }
0200 if (queries) {
0201 std::scoped_lock lock(mt);
0202 queries->push_back(std::make_pair(trial_vectors[i], trial_cost));
0203 }
0204 if (trial_cost < cost[i] || isnan(cost[i])) {
0205 cost[i] = trial_cost;
0206 if (!isnan(target_value) && cost[i] <= target_value) {
0207 target_attained = true;
0208 }
0209 if (current_minimum_cost && cost[i] < *current_minimum_cost) {
0210 *current_minimum_cost = cost[i];
0211 }
0212
0213
0214
0215 updated_indices[i] = 1;
0216 }
0217 }
0218 });
0219 }
0220 for (auto &thread : thread_pool) {
0221 thread.join();
0222 }
0223 for (size_t i = 0; i < NP; ++i) {
0224 if (updated_indices[i]) {
0225 population[i] = trial_vectors[i];
0226 updated_indices[i] = 0;
0227 }
0228 }
0229 }
0230
0231 auto it = std::min_element(cost.begin(), cost.end());
0232 return population[std::distance(cost.begin(), it)];
0233 }
0234
0235 }
0236 #endif