common.hpp 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. /*
  2. * Copyright Nick Thompson, 2024
  3. * Use, modification and distribution are subject to the
  4. * Boost Software License, Version 1.0. (See accompanying file
  5. * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  6. */
  7. #ifndef BOOST_MATH_OPTIMIZATION_DETAIL_COMMON_HPP
  8. #define BOOST_MATH_OPTIMIZATION_DETAIL_COMMON_HPP
  9. #include <algorithm> // for std::sort
  10. #include <cmath>
  11. #include <limits>
  12. #include <sstream>
  13. #include <stdexcept>
  14. #include <random>
  15. #include <type_traits> // for std::false_type
  16. namespace boost::math::optimization::detail {
  17. template <typename T, typename = void> struct has_resize : std::false_type {};
  18. template <typename T>
  19. struct has_resize<T, std::void_t<decltype(std::declval<T>().resize(size_t{}))>> : std::true_type {};
  20. template <typename T> constexpr bool has_resize_v = has_resize<T>::value;
  21. template <typename ArgumentContainer>
  22. void validate_bounds(ArgumentContainer const &lower_bounds, ArgumentContainer const &upper_bounds) {
  23. using std::isfinite;
  24. std::ostringstream oss;
  25. if (lower_bounds.size() == 0) {
  26. oss << __FILE__ << ":" << __LINE__ << ":" << __func__;
  27. oss << ": The dimension of the problem cannot be zero.";
  28. throw std::domain_error(oss.str());
  29. }
  30. if (upper_bounds.size() != lower_bounds.size()) {
  31. oss << __FILE__ << ":" << __LINE__ << ":" << __func__;
  32. oss << ": There must be the same number of lower bounds as upper bounds, but given ";
  33. oss << upper_bounds.size() << " upper bounds, and " << lower_bounds.size() << " lower bounds.";
  34. throw std::domain_error(oss.str());
  35. }
  36. for (size_t i = 0; i < lower_bounds.size(); ++i) {
  37. auto lb = lower_bounds[i];
  38. auto ub = upper_bounds[i];
  39. if (lb > ub) {
  40. oss << __FILE__ << ":" << __LINE__ << ":" << __func__;
  41. oss << ": The upper bound must be greater than or equal to the lower bound, but the upper bound is " << ub
  42. << " and the lower is " << lb << ".";
  43. throw std::domain_error(oss.str());
  44. }
  45. if (!isfinite(lb)) {
  46. oss << __FILE__ << ":" << __LINE__ << ":" << __func__;
  47. oss << ": The lower bound must be finite, but got " << lb << ".";
  48. oss << " For infinite bounds, emulate with std::numeric_limits<Real>::lower() or use a standard infinite->finite "
  49. "transform.";
  50. throw std::domain_error(oss.str());
  51. }
  52. if (!isfinite(ub)) {
  53. oss << __FILE__ << ":" << __LINE__ << ":" << __func__;
  54. oss << ": The upper bound must be finite, but got " << ub << ".";
  55. oss << " For infinite bounds, emulate with std::numeric_limits<Real>::max() or use a standard infinite->finite "
  56. "transform.";
  57. throw std::domain_error(oss.str());
  58. }
  59. }
  60. }
  61. template <typename ArgumentContainer, class URBG>
  62. std::vector<ArgumentContainer> random_initial_population(ArgumentContainer const &lower_bounds,
  63. ArgumentContainer const &upper_bounds,
  64. size_t initial_population_size, URBG &&gen) {
  65. using Real = typename ArgumentContainer::value_type;
  66. using DimensionlessReal = decltype(Real()/Real());
  67. constexpr bool has_resize = detail::has_resize_v<ArgumentContainer>;
  68. std::vector<ArgumentContainer> population(initial_population_size);
  69. auto const dimension = lower_bounds.size();
  70. for (size_t i = 0; i < population.size(); ++i) {
  71. if constexpr (has_resize) {
  72. population[i].resize(dimension);
  73. } else {
  74. // Argument type must be known at compile-time; like std::array:
  75. if (population[i].size() != dimension) {
  76. std::ostringstream oss;
  77. oss << __FILE__ << ":" << __LINE__ << ":" << __func__;
  78. oss << ": For containers which do not have resize, the default size must be the same as the dimension, ";
  79. oss << "but the default container size is " << population[i].size() << " and the dimension of the problem is "
  80. << dimension << ".";
  81. oss << " The function argument container type is " << typeid(ArgumentContainer).name() << ".\n";
  82. throw std::runtime_error(oss.str());
  83. }
  84. }
  85. }
  86. // Why don't we provide an option to initialize with (say) a Gaussian distribution?
  87. // > If the optimum's location is fairly well known,
  88. // > a Gaussian distribution may prove somewhat faster, although it
  89. // > may also increase the probability that the population will converge prematurely.
  90. // > In general, uniform distributions are preferred, since they best reflect
  91. // > the lack of knowledge about the optimum's location.
  92. // - Differential Evolution: A Practical Approach to Global Optimization
  93. // That said, scipy uses Latin Hypercube sampling and says self-avoiding sequences are preferable.
  94. // So this is something that could be investigated and potentially improved.
  95. using std::uniform_real_distribution;
  96. uniform_real_distribution<DimensionlessReal> dis(DimensionlessReal(0), DimensionlessReal(1));
  97. for (size_t i = 0; i < population.size(); ++i) {
  98. for (size_t j = 0; j < dimension; ++j) {
  99. auto const &lb = lower_bounds[j];
  100. auto const &ub = upper_bounds[j];
  101. population[i][j] = lb + dis(gen) * (ub - lb);
  102. }
  103. }
  104. return population;
  105. }
  106. template <typename ArgumentContainer>
  107. void validate_initial_guess(ArgumentContainer const &initial_guess, ArgumentContainer const &lower_bounds,
  108. ArgumentContainer const &upper_bounds) {
  109. using std::isfinite;
  110. std::ostringstream oss;
  111. auto const dimension = lower_bounds.size();
  112. if (initial_guess.size() != dimension) {
  113. oss << __FILE__ << ":" << __LINE__ << ":" << __func__;
  114. oss << ": The initial guess must have the same dimensions as the problem,";
  115. oss << ", but the problem size is " << dimension << " and the initial guess has " << initial_guess.size()
  116. << " elements.";
  117. throw std::domain_error(oss.str());
  118. }
  119. for (size_t i = 0; i < dimension; ++i) {
  120. auto lb = lower_bounds[i];
  121. auto ub = upper_bounds[i];
  122. if (!isfinite(initial_guess[i])) {
  123. oss << __FILE__ << ":" << __LINE__ << ":" << __func__;
  124. oss << ": At index " << i << ", the initial guess is " << initial_guess[i]
  125. << ", make sure all elements of the initial guess are finite.";
  126. throw std::domain_error(oss.str());
  127. }
  128. if (initial_guess[i] < lb || initial_guess[i] > ub) {
  129. oss << __FILE__ << ":" << __LINE__ << ":" << __func__;
  130. oss << ": At index " << i << " the initial guess " << initial_guess[i] << " is not in the bounds [" << lb << ", "
  131. << ub << "].";
  132. throw std::domain_error(oss.str());
  133. }
  134. }
  135. }
  136. // Return indices corresponding to the minimum function values.
  137. template <typename Real> std::vector<size_t> best_indices(std::vector<Real> const &function_values) {
  138. using std::isnan;
  139. const size_t n = function_values.size();
  140. std::vector<size_t> indices(n);
  141. for (size_t i = 0; i < n; ++i) {
  142. indices[i] = i;
  143. }
  144. std::sort(indices.begin(), indices.end(), [&](size_t a, size_t b) {
  145. if (isnan(function_values[a])) {
  146. return false;
  147. }
  148. if (isnan(function_values[b])) {
  149. return true;
  150. }
  151. return function_values[a] < function_values[b];
  152. });
  153. return indices;
  154. }
  155. template<typename RandomAccessContainer>
  156. auto weighted_lehmer_mean(RandomAccessContainer const & values, RandomAccessContainer const & weights) {
  157. using std::isfinite;
  158. if (values.size() != weights.size()) {
  159. std::ostringstream oss;
  160. oss << __FILE__ << ":" << __LINE__ << ":" << __func__;
  161. oss << ": There must be the same number of weights as values, but got " << values.size() << " values and " << weights.size() << " weights.";
  162. throw std::logic_error(oss.str());
  163. }
  164. if (values.size() == 0) {
  165. std::ostringstream oss;
  166. oss << __FILE__ << ":" << __LINE__ << ":" << __func__;
  167. oss << ": There must at least one value provided.";
  168. throw std::logic_error(oss.str());
  169. }
  170. using Real = typename RandomAccessContainer::value_type;
  171. Real numerator = 0;
  172. Real denominator = 0;
  173. for (size_t i = 0; i < values.size(); ++i) {
  174. if (weights[i] < 0 || !isfinite(weights[i])) {
  175. std::ostringstream oss;
  176. oss << __FILE__ << ":" << __LINE__ << ":" << __func__;
  177. oss << ": All weights must be positive and finite, but got received weight " << weights[i] << " at index " << i << " of " << weights.size() << ".";
  178. throw std::domain_error(oss.str());
  179. }
  180. Real tmp = weights[i]*values[i];
  181. numerator += tmp*values[i];
  182. denominator += tmp;
  183. }
  184. return numerator/denominator;
  185. }
  186. } // namespace boost::math::optimization::detail
  187. #endif