123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- /*
- * Copyright Nick Thompson, 2024
- * Use, modification and distribution are subject to the
- * Boost Software License, Version 1.0. (See accompanying file
- * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
- */
- #ifndef BOOST_MATH_OPTIMIZATION_RANDOM_SEARCH_HPP
- #define BOOST_MATH_OPTIMIZATION_RANDOM_SEARCH_HPP
- #include <atomic>
- #include <cmath>
- #include <limits>
- #include <mutex>
- #include <random>
- #include <sstream>
- #include <stdexcept>
- #include <thread>
- #include <utility>
- #include <vector>
- #include <boost/math/optimization/detail/common.hpp>
- namespace boost::math::optimization {
- template <typename ArgumentContainer> struct random_search_parameters {
- using Real = typename ArgumentContainer::value_type;
- ArgumentContainer lower_bounds;
- ArgumentContainer upper_bounds;
- size_t max_function_calls = 10000*std::thread::hardware_concurrency();
- ArgumentContainer const *initial_guess = nullptr;
- unsigned threads = std::thread::hardware_concurrency();
- };
- template <typename ArgumentContainer>
- void validate_random_search_parameters(random_search_parameters<ArgumentContainer> const ¶ms) {
- using std::isfinite;
- using std::isnan;
- std::ostringstream oss;
- detail::validate_bounds(params.lower_bounds, params.upper_bounds);
- if (params.initial_guess) {
- detail::validate_initial_guess(*params.initial_guess, params.lower_bounds, params.upper_bounds);
- }
- if (params.threads == 0) {
- oss << __FILE__ << ":" << __LINE__ << ":" << __func__;
- oss << ": There must be at least one thread.";
- throw std::invalid_argument(oss.str());
- }
- }
- template <typename ArgumentContainer, class Func, class URBG>
- ArgumentContainer random_search(
- const Func cost_function,
- random_search_parameters<ArgumentContainer> const ¶ms,
- URBG &gen,
- std::invoke_result_t<Func, ArgumentContainer> target_value = std::numeric_limits<std::invoke_result_t<Func, ArgumentContainer>>::quiet_NaN(),
- std::atomic<bool> *cancellation = nullptr,
- std::atomic<std::invoke_result_t<Func, ArgumentContainer>> *current_minimum_cost = nullptr,
- std::vector<std::pair<ArgumentContainer, std::invoke_result_t<Func, ArgumentContainer>>> *queries = nullptr)
- {
- using Real = typename ArgumentContainer::value_type;
- using DimensionlessReal = decltype(Real()/Real());
- using ResultType = std::invoke_result_t<Func, ArgumentContainer>;
- using std::isnan;
- using std::uniform_real_distribution;
- validate_random_search_parameters(params);
- const size_t dimension = params.lower_bounds.size();
- std::atomic<bool> target_attained = false;
- // Unfortunately, the "minimum_cost" variable can either be passed
- // (for observability) or not (if the user doesn't care).
- // That makes this a bit awkward . . .
- std::atomic<ResultType> lowest_cost = std::numeric_limits<ResultType>::infinity();
- ArgumentContainer best_vector;
- if constexpr (detail::has_resize_v<ArgumentContainer>) {
- best_vector.resize(dimension, std::numeric_limits<Real>::quiet_NaN());
- }
- if (params.initial_guess) {
- auto initial_cost = cost_function(*params.initial_guess);
- if (!isnan(initial_cost)) {
- lowest_cost = initial_cost;
- best_vector = *params.initial_guess;
- if (current_minimum_cost) {
- *current_minimum_cost = initial_cost;
- }
- }
- }
- std::mutex mt;
- std::vector<std::thread> thread_pool;
- std::atomic<size_t> function_calls = 0;
- for (unsigned j = 0; j < params.threads; ++j) {
- auto seed = gen();
- thread_pool.emplace_back([&, seed]() {
- URBG g(seed);
- ArgumentContainer trial_vector;
- // This vector is empty unless the user requests the queries be stored:
- std::vector<std::pair<ArgumentContainer, std::invoke_result_t<Func, ArgumentContainer>>> local_queries;
- if constexpr (detail::has_resize_v<ArgumentContainer>) {
- trial_vector.resize(dimension, std::numeric_limits<Real>::quiet_NaN());
- }
- while (function_calls < params.max_function_calls) {
- if (cancellation && *cancellation) {
- break;
- }
- if (target_attained) {
- break;
- }
- // Fill trial vector:
- uniform_real_distribution<DimensionlessReal> unif01(DimensionlessReal(0), DimensionlessReal(1));
- for (size_t i = 0; i < dimension; ++i) {
- trial_vector[i] = params.lower_bounds[i] + (params.upper_bounds[i] - params.lower_bounds[i])*unif01(g);
- }
- ResultType trial_cost = cost_function(trial_vector);
- ++function_calls;
- if (isnan(trial_cost)) {
- continue;
- }
- if (trial_cost < lowest_cost) {
- lowest_cost = trial_cost;
- if (current_minimum_cost) {
- *current_minimum_cost = trial_cost;
- }
- // We expect to need to acquire this lock with decreasing frequency
- // as the computation proceeds:
- std::scoped_lock lock(mt);
- best_vector = trial_vector;
- }
- if (queries) {
- local_queries.push_back(std::make_pair(trial_vector, trial_cost));
- }
- if (!isnan(target_value) && trial_cost <= target_value) {
- target_attained = true;
- }
- }
- if (queries) {
- std::scoped_lock lock(mt);
- queries->insert(queries->begin(), local_queries.begin(), local_queries.end());
- }
- });
- }
- for (auto &thread : thread_pool) {
- thread.join();
- }
- return best_vector;
- }
- } // namespace boost::math::optimization
- #endif
|