/builds/MusicScience37Projects/numerical-analysis/numerical-collection-cpp/include/num_collect/opt/adaptive_diagonal_curves.h
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2021 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 | | */ |
16 | | /*! |
17 | | * \file |
18 | | * \brief Definition of dividing_rectangles class. |
19 | | */ |
20 | | #pragma once |
21 | | |
22 | | #include <algorithm> |
23 | | #include <cmath> |
24 | | #include <cstddef> |
25 | | #include <cstdint> |
26 | | #include <iterator> |
27 | | #include <limits> |
28 | | #include <memory> |
29 | | #include <queue> |
30 | | #include <string_view> |
31 | | #include <utility> |
32 | | #include <vector> |
33 | | |
34 | | #include <hash_tables/maps/multi_open_address_map_st.h> |
35 | | |
36 | | #include "num_collect/base/concepts/real_scalar.h" |
37 | | #include "num_collect/base/exception.h" |
38 | | #include "num_collect/base/index_type.h" |
39 | | #include "num_collect/logging/iterations/iteration_logger.h" |
40 | | #include "num_collect/logging/log_tag_view.h" |
41 | | #include "num_collect/logging/logging_macros.h" |
42 | | #include "num_collect/opt/concepts/multi_variate_objective_function.h" |
43 | | #include "num_collect/opt/concepts/objective_function.h" |
44 | | #include "num_collect/opt/impl/ternary_vector.h" |
45 | | #include "num_collect/opt/optimizer_base.h" |
46 | | #include "num_collect/util/assert.h" |
47 | | #include "num_collect/util/safe_cast.h" |
48 | | |
49 | | namespace num_collect::opt { |
50 | | |
51 | | //! Tag of adaptive_diagonal_curves. |
52 | | constexpr auto adaptive_diagonal_curves_tag = |
53 | | logging::log_tag_view("num_collect::opt::adaptive_diagonal_curves"); |
54 | | |
55 | | namespace impl { |
56 | | |
57 | | /*! |
58 | | * \brief Class of dictionaries of sampling points in \ref |
59 | | * num_collect::opt::adaptive_diagonal_curves. |
60 | | * |
61 | | * \tparam ObjectiveFunction Type of objective function. |
62 | | */ |
63 | | template <concepts::objective_function ObjectiveFunction> |
64 | | class adc_sample_dict; |
65 | | |
66 | | /*! |
67 | | * \brief Class of dictionaries of sampling points in \ref |
68 | | * num_collect::opt::adaptive_diagonal_curves. |
69 | | * |
70 | | * \tparam ObjectiveFunction Type of objective function. |
71 | | */ |
72 | | template <concepts::multi_variate_objective_function ObjectiveFunction> |
73 | | class adc_sample_dict<ObjectiveFunction> { |
74 | | public: |
75 | | //! Type of the objective function. |
76 | | using objective_function_type = ObjectiveFunction; |
77 | | |
78 | | //! Type of variables. |
79 | | using variable_type = typename objective_function_type::variable_type; |
80 | | |
81 | | //! Type of function values. |
82 | | using value_type = typename objective_function_type::value_type; |
83 | | |
84 | | /*! |
85 | | * \brief Constructor. |
86 | | * |
87 | | * \param[in] obj_fun Objective function. |
88 | | */ |
89 | | explicit adc_sample_dict( |
90 | | const objective_function_type& obj_fun = objective_function_type()) |
91 | 8 | : obj_fun_(obj_fun) { |
92 | 8 | constexpr std::size_t initial_space = 10000; |
93 | 8 | value_dict_.reserve_approx(initial_space); |
94 | 8 | } |
95 | | |
96 | | /*! |
97 | | * \brief Change the objective function. |
98 | | * |
99 | | * \param[in] obj_fun Objective function. |
100 | | */ |
101 | | void change_objective_function(const objective_function_type& obj_fun) { |
102 | | obj_fun_ = obj_fun; |
103 | | } |
104 | | |
105 | | /*! |
106 | | * \brief Initialize this object. |
107 | | * |
108 | | * \param[in] lower Element-wise lower limit. |
109 | | * \param[in] upper Element-wise upper limit. |
110 | | */ |
111 | 8 | void init(const variable_type& lower, const variable_type& upper) { |
112 | 8 | if (lower.size() != upper.size()) { |
113 | 0 | NUM_COLLECT_LOG_AND_THROW(invalid_argument, |
114 | 0 | "Element-wise limits must have the same size."); |
115 | 0 | } |
116 | 8 | if (!(lower.array() < upper.array()).all()) { |
117 | 0 | NUM_COLLECT_LOG_AND_THROW(invalid_argument, |
118 | 0 | "Element-wise limits must satisfy lower < upper for each " |
119 | 0 | "element."); |
120 | 0 | } |
121 | 8 | lower_ = lower; |
122 | 8 | width_ = upper - lower; |
123 | 8 | dim_ = lower.size(); |
124 | 8 | value_dict_.clear(); |
125 | 8 | } |
126 | | |
127 | | /*! |
128 | | * \brief Evaluate or get function value. |
129 | | * |
130 | | * \warning This function assumes that init() has already been called. |
131 | | * |
132 | | * \param[in] point Point in the unit hyper-cube. |
133 | | * \return Function value. |
134 | | */ |
135 | 15.5k | [[nodiscard]] auto operator()(const ternary_vector& point) -> value_type { |
136 | 15.5k | return value_dict_.get_or_create_with_factory( |
137 | 15.5k | point, [this, &point] { return evaluate_on(point); }); |
138 | 15.5k | } |
139 | | |
140 | | /*! |
141 | | * \brief Get the number of dimension. |
142 | | * |
143 | | * \return Number of dimension. |
144 | | */ |
145 | 402 | [[nodiscard]] auto dim() const -> index_type { return dim_; } |
146 | | |
147 | | /*! |
148 | | * \copydoc num_collect::opt::optimizer_base::opt_variable |
149 | | */ |
150 | 8 | [[nodiscard]] auto opt_variable() const -> const variable_type& { |
151 | 8 | return opt_variable_; |
152 | 8 | } |
153 | | |
154 | | /*! |
155 | | * \brief Get the point in the unit hyper-cube for the current optimal |
156 | | * variable. |
157 | | * |
158 | | * \return Point in the unit hyper-cube for the current optimal variable. |
159 | | */ |
160 | 1 | [[nodiscard]] auto opt_point() const -> const ternary_vector& { |
161 | 1 | return opt_point_; |
162 | 1 | } |
163 | | |
164 | | /*! |
165 | | * \copydoc num_collect::opt::optimizer_base::opt_value |
166 | | */ |
167 | 57 | [[nodiscard]] auto opt_value() const -> const value_type& { |
168 | 57 | return opt_value_; |
169 | 57 | } |
170 | | |
171 | | /*! |
172 | | * \copydoc num_collect::opt::optimizer_base::evaluations |
173 | | */ |
174 | 508 | [[nodiscard]] auto evaluations() const noexcept -> index_type { |
175 | 508 | return static_cast<index_type>(value_dict_.size()); |
176 | 508 | } |
177 | | |
178 | | private: |
179 | | /*! |
180 | | * \brief Evaluate function value. |
181 | | * |
182 | | * \warning This function assumes that init() has already been called. |
183 | | * |
184 | | * \param[in] point Point in the unit hyper-cube. |
185 | | * \return Function value. |
186 | | */ |
187 | 2.01k | [[nodiscard]] auto evaluate_on(const ternary_vector& point) -> value_type { |
188 | 2.01k | NUM_COLLECT_DEBUG_ASSERT(point.dim() == dim_); |
189 | 2.01k | auto var = variable_type(dim_); |
190 | 8.05k | for (index_type i = 0; i < dim_; ++i) { |
191 | 6.04k | var(i) = lower_(i) + |
192 | 6.04k | width_(i) * point.elem_as<typename variable_type::Scalar>(i); |
193 | 6.04k | } |
194 | 2.01k | obj_fun_.evaluate_on(var); |
195 | | |
196 | 2.01k | if (value_dict_.empty() || obj_fun_.value() < opt_value_) { |
197 | 85 | opt_point_ = point; |
198 | 85 | opt_variable_ = var; |
199 | 85 | opt_value_ = obj_fun_.value(); |
200 | 85 | } |
201 | | |
202 | 2.01k | return obj_fun_.value(); |
203 | 2.01k | } |
204 | | |
205 | | //! Objective function. |
206 | | objective_function_type obj_fun_; |
207 | | |
208 | | //! Element-wise lower limit. |
209 | | variable_type lower_{}; |
210 | | |
211 | | //! Element-wise width. |
212 | | variable_type width_{}; |
213 | | |
214 | | //! Number of dimension. |
215 | | index_type dim_{0}; |
216 | | |
217 | | //! Dictionary of sampled points. |
218 | | hash_tables::maps::multi_open_address_map_st<impl::ternary_vector, |
219 | | value_type> |
220 | | value_dict_{}; |
221 | | |
222 | | //! Point in the unit hyper-cube for the current optimal variable. |
223 | | ternary_vector opt_point_{}; |
224 | | |
225 | | //! Current optimal variable. |
226 | | variable_type opt_variable_{}; |
227 | | |
228 | | //! Current optimal value. |
229 | | value_type opt_value_{}; |
230 | | }; |
231 | | |
232 | | /*! |
233 | | * \brief Class of rectangles as proposed in \cite Sergeyev2000 for \ref |
234 | | * num_collect::opt::adaptive_diagonal_curves. |
235 | | * |
236 | | * \tparam Value Type of function values. |
237 | | */ |
238 | | template <base::concepts::real_scalar Value> |
239 | | class adc_rectangle { |
240 | | public: |
241 | | //! Type of function values. |
242 | | using value_type = Value; |
243 | | |
244 | | /*! |
245 | | * \brief Constructor. |
246 | | * |
247 | | * \param[in] vertex A vertex with lower components. |
248 | | * \param[in] ave_value Average function value. |
249 | | */ |
250 | | adc_rectangle( |
251 | | const impl::ternary_vector& vertex, const value_type& ave_value) |
252 | 7.77k | : vertex_(vertex), ave_value_(ave_value) {} |
253 | | |
254 | | /*! |
255 | | * \brief Get the vertex with lower first component. |
256 | | * |
257 | | * \return A vertex with lower first component. |
258 | | */ |
259 | 2.59k | [[nodiscard]] auto vertex() const -> const impl::ternary_vector& { |
260 | 2.59k | return vertex_; |
261 | 2.59k | } |
262 | | |
263 | | /*! |
264 | | * \brief Get the average function value. |
265 | | * |
266 | | * \return Average function value. |
267 | | */ |
268 | 60.2k | [[nodiscard]] auto ave_value() const -> const value_type& { |
269 | 60.2k | return ave_value_; |
270 | 60.2k | } |
271 | | |
272 | | /*! |
273 | | * \brief Determine sampling points. |
274 | | * |
275 | | * \return Sampling points. |
276 | | */ |
277 | | [[nodiscard]] auto sample_points() const |
278 | 1 | -> std::pair<ternary_vector, ternary_vector> { |
279 | 1 | return determine_sample_points(vertex_); |
280 | 1 | } |
281 | | |
282 | | /*! |
283 | | * \brief Get the distance between center point and vertex. |
284 | | * |
285 | | * \return Distance between center point and vertex. |
286 | | */ |
287 | 58 | [[nodiscard]] auto dist() const -> value_type { |
288 | 58 | auto squared_sum = static_cast<value_type>(0); |
289 | 231 | for (index_type i = 0; i < vertex_.dim(); ++i) { |
290 | 173 | using std::pow; |
291 | 173 | squared_sum += |
292 | 173 | pow(static_cast<value_type>(3), -2 * (vertex_.digits(i) - 1)); |
293 | 173 | } |
294 | 58 | using std::sqrt; |
295 | 58 | const auto half = static_cast<value_type>(0.5); |
296 | 58 | return half * sqrt(squared_sum); |
297 | 58 | } |
298 | | |
299 | | /*! |
300 | | * \brief Determine sampling points. |
301 | | * |
302 | | * \param[in] lowest_vertex A vertex with lower first component. |
303 | | * \return Sampling points. |
304 | | */ |
305 | | [[nodiscard]] static auto determine_sample_points( |
306 | | const ternary_vector& lowest_vertex) |
307 | 7.76k | -> std::pair<ternary_vector, ternary_vector> { |
308 | 7.76k | auto res = std::make_pair(lowest_vertex, lowest_vertex); |
309 | 7.76k | const auto dim = lowest_vertex.dim(); |
310 | 31.0k | for (index_type i = 0; i < dim; ++i) { |
311 | 23.2k | const auto digits = lowest_vertex.digits(i); |
312 | 23.2k | NUM_COLLECT_DEBUG_ASSERT(digits > 0); |
313 | 23.2k | std::uint_fast32_t one_count = 0; |
314 | 130k | for (index_type j = 0; j < digits; ++j) { |
315 | 107k | if (lowest_vertex(i, j) == ternary_vector::digit_type{1}) { |
316 | 58.0k | ++one_count; |
317 | 58.0k | } |
318 | 107k | } |
319 | | |
320 | 23.2k | auto last_digit = // NOLINTNEXTLINE: false positive |
321 | 23.2k | static_cast<std::int_fast32_t>(lowest_vertex(i, digits - 1)); |
322 | 23.2k | ++last_digit; |
323 | 23.2k | constexpr std::uint_fast32_t odd_mask = 1; |
324 | 23.2k | if ((one_count & odd_mask) == odd_mask) { |
325 | 11.3k | res.first(i, digits - 1) = |
326 | 11.3k | static_cast<ternary_vector::digit_type>(last_digit); |
327 | 11.9k | } else { |
328 | 11.9k | res.second(i, digits - 1) = |
329 | 11.9k | static_cast<ternary_vector::digit_type>(last_digit); |
330 | 11.9k | } |
331 | 23.2k | } |
332 | 7.76k | normalize_point(res.first); |
333 | 7.76k | normalize_point(res.second); |
334 | 7.76k | return res; |
335 | 7.76k | } |
336 | | |
337 | | private: |
338 | | /*! |
339 | | * \brief Normalize point. |
340 | | * |
341 | | * \param[in,out] point Point. |
342 | | */ |
343 | 15.5k | static void normalize_point(ternary_vector& point) { |
344 | 62.1k | for (index_type i = 0; i < point.dim(); ++i) { |
345 | 214k | for (index_type j = point.digits(i) - 1; j > 0; --j) { |
346 | 167k | if (point(i, j) == ternary_vector::digit_type{3}) { |
347 | 8.90k | point(i, j) = 0; |
348 | 8.90k | std::int_fast32_t temp = |
349 | 8.90k | point(i, j - 1); // NOLINT: false positive |
350 | 8.90k | ++temp; |
351 | 8.90k | point(i, j - 1) = |
352 | 8.90k | static_cast<ternary_vector::digit_type>(temp); |
353 | 8.90k | } |
354 | 167k | } |
355 | 46.5k | } |
356 | 15.5k | } |
357 | | |
358 | | //! A vertex with lower first component. |
359 | | impl::ternary_vector vertex_; |
360 | | |
361 | | //! Average function value. |
362 | | value_type ave_value_; |
363 | | }; |
364 | | |
365 | | /*! |
366 | | * \brief Class of groups in \cite Sergeyev2006 for \ref |
367 | | * num_collect::opt::adaptive_diagonal_curves. |
368 | | * |
369 | | * \tparam Value Type of function values. |
370 | | */ |
371 | | template <base::concepts::real_scalar Value> |
372 | | class adc_group { |
373 | | public: |
374 | | //! Type of function values. |
375 | | using value_type = Value; |
376 | | |
377 | | //! Type of hyper-rectangles. |
378 | | using rectangle_type = adc_rectangle<value_type>; |
379 | | |
380 | | //! Type of pointers of hyper-rectangles. |
381 | | using rectangle_pointer_type = std::shared_ptr<rectangle_type>; |
382 | | |
383 | | /*! |
384 | | * \brief Constructor. |
385 | | * |
386 | | * \param[in] dist Distance between center point and vertex. |
387 | | */ |
388 | 61 | explicit adc_group(value_type dist) : dist_(dist) {} |
389 | | |
390 | | /*! |
391 | | * \brief Add a hyper-rectangle to this group. |
392 | | * |
393 | | * \param[in] rect Rectangle. |
394 | | */ |
395 | 7.77k | void push(rectangle_pointer_type rect) { |
396 | 7.77k | NUM_COLLECT_DEBUG_ASSERT(rect); |
397 | 7.77k | rects_.push(std::move(rect)); |
398 | 7.77k | } |
399 | | |
400 | | /*! |
401 | | * \brief Access the hyper-rectangle with the smallest average of |
402 | | * function values at diagonal vertices. |
403 | | * |
404 | | * \return Reference of pointer to the rectangle. |
405 | | */ |
406 | 8.93k | [[nodiscard]] auto min_rect() const -> const rectangle_pointer_type& { |
407 | 8.93k | NUM_COLLECT_DEBUG_ASSERT(!rects_.empty()); |
408 | 8.93k | return rects_.top(); |
409 | 8.93k | } |
410 | | |
411 | | /*! |
412 | | * \brief Check whether this group is empty. |
413 | | * |
414 | | * \return Whether this group is empty. |
415 | | */ |
416 | 9.69k | [[nodiscard]] auto empty() const -> bool { return rects_.empty(); } |
417 | | |
418 | | /*! |
419 | | * \brief Pick out the hyper-rectangle with the smallest average of function |
420 | | * values at diagonal vertices. |
421 | | * |
422 | | * \return Rectangle. |
423 | | */ |
424 | 2.58k | [[nodiscard]] auto pop() -> rectangle_pointer_type { |
425 | 2.58k | NUM_COLLECT_DEBUG_ASSERT(!rects_.empty()); |
426 | 2.58k | auto rect = rects_.top(); |
427 | 2.58k | rects_.pop(); |
428 | 2.58k | return rect; |
429 | 2.58k | } |
430 | | |
431 | | /*! |
432 | | * \brief Get the distance between center point and vertex. |
433 | | * |
434 | | * \return Distance between center point and vertex. |
435 | | */ |
436 | 8.93k | [[nodiscard]] auto dist() const -> const value_type& { return dist_; } |
437 | | |
438 | | private: |
439 | | /*! |
440 | | * \brief Class to compare rectangles. |
441 | | */ |
442 | | struct greater { |
443 | | /*! |
444 | | * \brief Compare rectangles. |
445 | | * |
446 | | * \param[in] left Left-hand-side rectangle. |
447 | | * \param[in] right Right-hand-side rectangle. |
448 | | * \return Result of left > right. |
449 | | */ |
450 | | [[nodiscard]] auto operator()(const rectangle_pointer_type& left, |
451 | 25.6k | const rectangle_pointer_type& right) const -> bool { |
452 | 25.6k | return left->ave_value() > right->ave_value(); |
453 | 25.6k | } |
454 | | }; |
455 | | |
456 | | //! Type of queues of rectangles. |
457 | | using queue_type = std::priority_queue<rectangle_pointer_type, |
458 | | std::vector<rectangle_pointer_type>, greater>; |
459 | | |
460 | | //! Rectangles. |
461 | | queue_type rects_{}; |
462 | | |
463 | | //! Distance between center point and vertex. |
464 | | value_type dist_; |
465 | | }; |
466 | | |
467 | | } // namespace impl |
468 | | |
469 | | /*! |
470 | | * \brief Class of adaptive diagonal curves (ADC) method \cite Sergeyev2006 for |
471 | | * optimization. |
472 | | * |
473 | | * \tparam ObjectiveFunction Type of the objective function. |
474 | | */ |
475 | | template <concepts::objective_function ObjectiveFunction> |
476 | | class adaptive_diagonal_curves |
477 | | : public optimizer_base<adaptive_diagonal_curves<ObjectiveFunction>> { |
478 | | public: |
479 | | //! This class. |
480 | | using this_type = adaptive_diagonal_curves<ObjectiveFunction>; |
481 | | |
482 | | //! Type of the objective function. |
483 | | using objective_function_type = ObjectiveFunction; |
484 | | |
485 | | //! Type of variables. |
486 | | using variable_type = typename objective_function_type::variable_type; |
487 | | |
488 | | //! Type of function values. |
489 | | using value_type = typename objective_function_type::value_type; |
490 | | |
491 | | /*! |
492 | | * \brief Enumeration of states in ADC method. |
493 | | */ |
494 | | enum class state_type : std::uint8_t { |
495 | | none, //!< No operation. |
496 | | local, //!< Local phase (not last iteration). |
497 | | local_last, //!< Last iteration in local phase. |
498 | | global, //!< Global phase (not last iteration). |
499 | | global_last, //!< Last iteration in global phase. |
500 | | }; |
501 | | |
502 | | /*! |
503 | | * \brief Convert a state to string. |
504 | | * |
505 | | * \param[in] state State. |
506 | | * \return Name of state. |
507 | | */ |
508 | 48 | [[nodiscard]] static auto state_name(state_type state) -> std::string_view { |
509 | 48 | switch (state) { |
510 | 2 | case state_type::none: |
511 | 2 | return "none"; |
512 | 7 | case state_type::local: |
513 | 7 | return "local"; |
514 | 6 | case state_type::local_last: |
515 | 6 | return "local (last)"; |
516 | 31 | case state_type::global: |
517 | 31 | return "global"; |
518 | 2 | case state_type::global_last: |
519 | 2 | return "global (last)"; |
520 | 0 | default: |
521 | 0 | return "invalid process"; |
522 | 48 | } |
523 | 48 | } |
524 | | |
525 | | /*! |
526 | | * \brief Constructor. |
527 | | * |
528 | | * \param[in] obj_fun Objective function. |
529 | | */ |
530 | | explicit adaptive_diagonal_curves( |
531 | | const objective_function_type& obj_fun = objective_function_type()) |
532 | 4 | : optimizer_base<adaptive_diagonal_curves<ObjectiveFunction>>( |
533 | 4 | adaptive_diagonal_curves_tag), |
534 | 4 | value_dict_(obj_fun) {} |
535 | | |
536 | | /*! |
537 | | * \brief Change the objective function. |
538 | | * |
539 | | * \param[in] obj_fun Objective function. |
540 | | */ |
541 | | void change_objective_function(const objective_function_type& obj_fun) { |
542 | | value_dict_.change_objective_function(obj_fun); |
543 | | } |
544 | | |
545 | | /*! |
546 | | * \brief Initialize the algorithm. |
547 | | * |
548 | | * \param[in] lower Lower limit. |
549 | | * \param[in] upper Upper limit. |
550 | | */ |
551 | 4 | void init(const variable_type& lower, const variable_type& upper) { |
552 | 4 | value_dict_.init(lower, upper); |
553 | 4 | groups_.clear(); |
554 | 4 | iterations_ = 0; |
555 | 4 | state_ = state_type::none; |
556 | | |
557 | | // Initialize groups_, optimal_value_, optimal_group_index_. |
558 | 4 | create_first_rectangle(); |
559 | | |
560 | | // prec_optimal_value_, prec_optimal_group_index, and |
561 | | // iterations_in_current_phase_ are initialized in switch_state(). |
562 | 4 | } |
563 | | |
564 | | /*! |
565 | | * \copydoc num_collect::opt::optimizer_base::iterate |
566 | | */ |
567 | 450 | void iterate() { |
568 | 450 | switch_state(); |
569 | | |
570 | 450 | switch (state_) { |
571 | 99 | case state_type::local: |
572 | 99 | iterate_locally(); |
573 | 99 | break; |
574 | 32 | case state_type::local_last: |
575 | 32 | iterate_locally_last(); |
576 | 32 | break; |
577 | 301 | case state_type::global: |
578 | 301 | iterate_globally(); |
579 | 301 | break; |
580 | 18 | case state_type::global_last: |
581 | 18 | iterate_globally_last(); |
582 | 18 | break; |
583 | 0 | default: |
584 | 0 | NUM_COLLECT_LOG_AND_THROW(algorithm_failure, |
585 | 450 | "invalid state (bug in adaptive_diagonal_curve class)"); |
586 | 450 | } |
587 | | |
588 | 450 | ++iterations_; |
589 | 450 | } |
590 | | |
591 | | /*! |
592 | | * \copydoc num_collect::base::iterative_solver_base::is_stop_criteria_satisfied |
593 | | */ |
594 | 451 | [[nodiscard]] auto is_stop_criteria_satisfied() const -> bool { |
595 | 451 | return evaluations() >= max_evaluations_; |
596 | 451 | } |
597 | | |
598 | | /*! |
599 | | * \copydoc num_collect::base::iterative_solver_base::configure_iteration_logger |
600 | | */ |
601 | | void configure_iteration_logger( |
602 | | logging::iterations::iteration_logger<this_type>& iteration_logger) |
603 | 2 | const { |
604 | 2 | iteration_logger.template append<index_type>( |
605 | 2 | "Iter.", &this_type::iterations); |
606 | 2 | iteration_logger.template append<index_type>( |
607 | 2 | "Eval.", &this_type::evaluations); |
608 | 2 | iteration_logger.template append<value_type>( |
609 | 2 | "Value", &this_type::opt_value); |
610 | 2 | constexpr index_type state_width = 15; |
611 | 2 | iteration_logger |
612 | 2 | .template append<std::string_view>( |
613 | 2 | "State", &this_type::last_state_name) |
614 | 2 | ->width(state_width); |
615 | 2 | } |
616 | | |
617 | | /*! |
618 | | * \copydoc num_collect::opt::optimizer_base::opt_variable |
619 | | */ |
620 | 3 | [[nodiscard]] auto opt_variable() const -> const variable_type& { |
621 | 3 | return value_dict_.opt_variable(); |
622 | 3 | } |
623 | | |
624 | | /*! |
625 | | * \copydoc num_collect::opt::optimizer_base::opt_value |
626 | | */ |
627 | 52 | [[nodiscard]] auto opt_value() const -> const value_type& { |
628 | 52 | return value_dict_.opt_value(); |
629 | 52 | } |
630 | | |
631 | | /*! |
632 | | * \copydoc num_collect::opt::optimizer_base::iterations |
633 | | */ |
634 | 50 | [[nodiscard]] auto iterations() const noexcept -> index_type { |
635 | 50 | return iterations_; |
636 | 50 | } |
637 | | |
638 | | /*! |
639 | | * \copydoc num_collect::opt::optimizer_base::evaluations |
640 | | */ |
641 | 501 | [[nodiscard]] auto evaluations() const noexcept -> index_type { |
642 | 501 | return value_dict_.evaluations(); |
643 | 501 | } |
644 | | |
645 | | /*! |
646 | | * \brief Get the last state. |
647 | | * |
648 | | * \return Last state. |
649 | | */ |
650 | 48 | [[nodiscard]] auto last_state() const noexcept -> state_type { |
651 | 48 | return state_; |
652 | 48 | } |
653 | | |
654 | | /*! |
655 | | * \brief Get the name of the last state. |
656 | | * |
657 | | * \return Last state. |
658 | | */ |
659 | 48 | [[nodiscard]] auto last_state_name() const noexcept -> std::string_view { |
660 | 48 | return state_name(last_state()); |
661 | 48 | } |
662 | | |
663 | | /*! |
664 | | * \brief Set the maximum number of function evaluations. |
665 | | * |
666 | | * \param[in] value Value. |
667 | | * \return This object. |
668 | | */ |
669 | 2 | auto max_evaluations(index_type value) -> adaptive_diagonal_curves& { |
670 | 2 | if (value <= 0) { |
671 | 0 | NUM_COLLECT_LOG_AND_THROW(invalid_argument, |
672 | 0 | "Maximum number of function evaluations must be a positive " |
673 | 0 | "integer."); |
674 | 0 | } |
675 | 2 | max_evaluations_ = value; |
676 | 2 | return *this; |
677 | 2 | } |
678 | | |
679 | | /*! |
680 | | * \brief Set the minimum rate of improvement in the function value required |
681 | | * for potentially optimal rectangles. |
682 | | * |
683 | | * \param[in] value Value. |
684 | | * \return This object. |
685 | | */ |
686 | | auto min_rate_imp(value_type value) -> adaptive_diagonal_curves& { |
687 | | if (value <= static_cast<value_type>(0)) { |
688 | | NUM_COLLECT_LOG_AND_THROW(invalid_argument, |
689 | | "Minimum rate of improvement in the function value required " |
690 | | "for potentially optimal rectangles must be a positive value."); |
691 | | } |
692 | | min_rate_imp_ = value; |
693 | | return *this; |
694 | | } |
695 | | |
696 | | /*! |
697 | | * \brief Set the rate of function value used to check whether the function |
698 | | * value decreased in the current phase. |
699 | | * |
700 | | * \param[in] value Value. |
701 | | * \return This object. |
702 | | */ |
703 | 1 | auto decrease_rate_bound(value_type value) -> adaptive_diagonal_curves& { |
704 | 1 | if (value <= static_cast<value_type>(0)) { |
705 | 0 | NUM_COLLECT_LOG_AND_THROW(invalid_argument, |
706 | 0 | "Rate of function value used to check whether the function " |
707 | 0 | "value decreased in the current phase must be a positive " |
708 | 0 | "value."); |
709 | 0 | } |
710 | 1 | decrease_rate_bound_ = value; |
711 | 1 | return *this; |
712 | 1 | } |
713 | | |
714 | | private: |
715 | | //! Type of dictionaries of sample points. |
716 | | using dict_type = impl::adc_sample_dict<objective_function_type>; |
717 | | |
718 | | //! Type of groups of hyper-rectangles. |
719 | | using group_type = impl::adc_group<value_type>; |
720 | | |
721 | | //! Type of hyper-rectangles. |
722 | | using rectangle_type = typename group_type::rectangle_type; |
723 | | |
724 | | /*! |
725 | | * \brief Create the first hyper-rectangle. |
726 | | */ |
727 | 4 | void create_first_rectangle() { |
728 | 4 | const index_type dim = value_dict_.dim(); |
729 | 4 | auto point = impl::ternary_vector{dim}; |
730 | 16 | for (index_type i = 0; i < dim; ++i) { |
731 | 12 | point.push_back(i, 0); |
732 | 12 | } |
733 | | |
734 | 4 | const auto [lower_vertex, upper_vertex] = |
735 | 4 | rectangle_type::determine_sample_points(point); |
736 | 4 | const auto lower_vertex_value = value_dict_(lower_vertex); |
737 | 4 | const auto upper_vertex_value = value_dict_(upper_vertex); |
738 | 4 | const auto ave_value = half * (lower_vertex_value + upper_vertex_value); |
739 | 4 | const auto rect = std::make_shared<rectangle_type>(point, ave_value); |
740 | | |
741 | 4 | groups_.emplace_back(rect->dist()); |
742 | 4 | groups_.front().push(rect); |
743 | 4 | using std::min; |
744 | 4 | optimal_value_ = min(lower_vertex_value, upper_vertex_value); |
745 | 4 | optimal_group_index_ = 0; |
746 | 4 | } |
747 | | |
748 | | /*! |
749 | | * \brief Switch to the next state if necessary. |
750 | | * |
751 | | * Step 2.1, 2.4, 3, 4.4, 4.5, 4.7 in \cite Sergeyev2006. |
752 | | */ |
753 | 450 | void switch_state() { |
754 | 450 | using std::abs; |
755 | 450 | switch (state_) { |
756 | 97 | case state_type::local: |
757 | 97 | ++iterations_in_current_phase_; |
758 | 97 | if (iterations_in_current_phase_ > value_dict_.dim()) { |
759 | 32 | state_ = state_type::local_last; |
760 | 32 | } |
761 | 97 | return; |
762 | 32 | case state_type::local_last: |
763 | 32 | switch_state_on_local_last(); |
764 | 32 | return; |
765 | 300 | case state_type::global: |
766 | 300 | if (optimal_value_ <= prec_optimal_value_ - |
767 | 300 | decrease_rate_bound_ * abs(prec_optimal_value_)) { |
768 | 0 | state_ = state_type::local; |
769 | 0 | prec_optimal_value_ = optimal_value_; |
770 | 0 | iterations_in_current_phase_ = 1; |
771 | 0 | prec_optimal_group_index_ = optimal_group_index_; |
772 | 0 | return; |
773 | 0 | } |
774 | 300 | ++iterations_in_current_phase_; |
775 | 300 | if (iterations_in_current_phase_ > |
776 | 300 | util::safe_cast<index_type>((static_cast<std::size_t>(2) |
777 | 300 | << util::safe_cast<std::size_t>(value_dict_.dim())))) { |
778 | 18 | state_ = state_type::global_last; |
779 | 18 | return; |
780 | 18 | } |
781 | 282 | return; |
782 | 282 | case state_type::global_last: |
783 | 18 | if (optimal_value_ <= prec_optimal_value_ - |
784 | 18 | decrease_rate_bound_ * abs(prec_optimal_value_)) { |
785 | 0 | state_ = state_type::local; |
786 | 0 | prec_optimal_value_ = optimal_value_; |
787 | 0 | iterations_in_current_phase_ = 1; |
788 | 0 | prec_optimal_group_index_ = optimal_group_index_; |
789 | 0 | return; |
790 | 0 | } |
791 | 18 | state_ = state_type::global; |
792 | 18 | iterations_in_current_phase_ = 1; |
793 | 18 | prec_optimal_group_index_ = optimal_group_index_; |
794 | 18 | return; |
795 | 3 | default: |
796 | 3 | state_ = state_type::local; |
797 | 3 | prec_optimal_value_ = optimal_value_; |
798 | 3 | iterations_in_current_phase_ = 1; |
799 | 3 | prec_optimal_group_index_ = optimal_group_index_; |
800 | 450 | } |
801 | 450 | } |
802 | | |
803 | | /*! |
804 | | * \brief Switch to the next state if necessary in local_last state. |
805 | | */ |
806 | 32 | void switch_state_on_local_last() { |
807 | 32 | using std::abs; |
808 | 32 | if (optimal_value_ <= prec_optimal_value_ - |
809 | 32 | decrease_rate_bound_ * abs(prec_optimal_value_)) { |
810 | 30 | state_ = state_type::local; |
811 | 30 | prec_optimal_value_ = optimal_value_; |
812 | 30 | iterations_in_current_phase_ = 1; |
813 | 30 | prec_optimal_group_index_ = optimal_group_index_; |
814 | 30 | return; |
815 | 30 | } |
816 | | |
817 | 2 | const bool is_optimal_smallest = |
818 | 2 | (optimal_group_index_ == groups_.size() - 1); |
819 | 2 | const bool is_all_smallest = |
820 | 2 | std::all_of(std::begin(groups_), std::end(groups_) - 1, |
821 | 5 | [](const group_type& group) { return group.empty(); }); |
822 | 2 | if ((!is_optimal_smallest) || is_all_smallest) { |
823 | 1 | state_ = state_type::local; |
824 | 1 | iterations_in_current_phase_ = 1; |
825 | 1 | prec_optimal_group_index_ = optimal_group_index_; |
826 | 1 | return; |
827 | 1 | } |
828 | | |
829 | 1 | state_ = state_type::global; |
830 | 1 | prec_optimal_value_ = optimal_value_; |
831 | 1 | iterations_in_current_phase_ = 1; |
832 | 1 | prec_optimal_group_index_ = optimal_group_index_; |
833 | 1 | } |
834 | | |
835 | | /*! |
836 | | * \brief Iterate once in the local phase (not last iteration). |
837 | | */ |
838 | 99 | void iterate_locally() { |
839 | 99 | const std::size_t max_group_index = std::max<std::size_t>( |
840 | 99 | std::max<std::size_t>(prec_optimal_group_index_, 1) - 1, |
841 | 99 | min_nonempty_group_index()); |
842 | 99 | divide_nondominated_rectangles( |
843 | 99 | min_nonempty_group_index(), max_group_index); |
844 | 99 | } |
845 | | |
846 | | /*! |
847 | | * \brief Iterate once at the last of the local phase. |
848 | | */ |
849 | 50 | void iterate_locally_last() { |
850 | 50 | const std::size_t max_group_index = std::max<std::size_t>( |
851 | 50 | prec_optimal_group_index_, min_nonempty_group_index()); |
852 | 50 | divide_nondominated_rectangles( |
853 | 50 | min_nonempty_group_index(), max_group_index); |
854 | 50 | } |
855 | | |
856 | | /*! |
857 | | * \brief Iterate once in the global phase (not last iteration). |
858 | | */ |
859 | 301 | void iterate_globally() { |
860 | 301 | const std::size_t max_group_index = |
861 | 301 | (std::max<std::size_t>( |
862 | 301 | prec_optimal_group_index_, min_nonempty_group_index()) + |
863 | 301 | min_nonempty_group_index() + 1) / |
864 | 301 | 2; |
865 | 301 | divide_nondominated_rectangles( |
866 | 301 | min_nonempty_group_index(), max_group_index); |
867 | 301 | } |
868 | | |
869 | | /*! |
870 | | * \brief Iterate once at teh last of the global phase. |
871 | | */ |
872 | 18 | void iterate_globally_last() { iterate_locally_last(); } |
873 | | |
874 | | /*! |
875 | | * \brief Get the minimum index of non-empty groups. |
876 | | * |
877 | | * \return Minimum index of groups. |
878 | | */ |
879 | 1.20k | [[nodiscard]] auto min_nonempty_group_index() const -> std::size_t { |
880 | 6.57k | for (std::size_t i = 0; i < groups_.size(); ++i) { |
881 | 6.57k | if (!groups_[i].empty()) { |
882 | 1.20k | return i; |
883 | 1.20k | } |
884 | 6.57k | } |
885 | 0 | NUM_COLLECT_LOG_AND_THROW(precondition_not_satisfied, |
886 | 0 | "adaptive_diagonal_curves::init is not called."); |
887 | 0 | } |
888 | | |
889 | | /*! |
890 | | * \brief Divide nondominated hyper-rectangles. |
891 | | * |
892 | | * \param[in] min_group Minimum index of groups to search in. |
893 | | * \param[in] max_group Maximum index of groups to search in. |
894 | | */ |
895 | | void divide_nondominated_rectangles( |
896 | 450 | std::size_t min_group, std::size_t max_group) { |
897 | 450 | const auto search_rect = |
898 | 450 | determine_nondominated_rectangles(min_group, max_group); |
899 | 450 | for (auto iter = std::rbegin(search_rect); |
900 | 3.03k | iter != std::rend(search_rect); ++iter) { |
901 | 2.58k | divide_rectangle(iter->first); |
902 | 2.58k | } |
903 | 450 | } |
904 | | |
905 | | /*! |
906 | | * \brief Determine nondominated hyper-rectangles. |
907 | | * |
908 | | * \param[in] min_group Minimum index of groups to search in. |
909 | | * \param[in] max_group Maximum index of groups to search in. |
910 | | * \return List of (index of group, slope). |
911 | | */ |
912 | | [[nodiscard]] auto determine_nondominated_rectangles( |
913 | | std::size_t min_group, std::size_t max_group) const |
914 | 450 | -> std::vector<std::pair<std::size_t, value_type>> { |
915 | 450 | NUM_COLLECT_DEBUG_ASSERT(min_group <= max_group); |
916 | 450 | NUM_COLLECT_DEBUG_ASSERT(max_group < groups_.size()); |
917 | 450 | NUM_COLLECT_DEBUG_ASSERT(!groups_[min_group].empty()); |
918 | | |
919 | 450 | std::vector<std::pair<std::size_t, value_type>> search_rects; |
920 | 450 | search_rects.emplace_back( |
921 | 450 | min_group, std::numeric_limits<value_type>::max()); |
922 | | |
923 | | // convex full scan |
924 | 3.10k | for (std::size_t i = min_group + 1; i <= max_group; ++i) { |
925 | 2.65k | if (groups_[i].empty()) { |
926 | 3 | continue; |
927 | 3 | } |
928 | 3.17k | while (true) { |
929 | 3.17k | const auto& [last_i, last_slope] = search_rects.back(); |
930 | 3.17k | const auto slope = calculate_slope(last_i, i); |
931 | 3.17k | if (slope <= last_slope) { |
932 | 2.65k | search_rects.emplace_back(i, slope); |
933 | 2.65k | break; |
934 | 2.65k | } |
935 | 517 | search_rects.pop_back(); |
936 | 517 | } |
937 | 2.65k | } |
938 | | |
939 | | // remove rectangles which won't update optimal value |
940 | 450 | using std::abs; |
941 | 450 | const auto value_bound = |
942 | 450 | optimal_value_ - min_rate_imp_ * abs(optimal_value_); |
943 | 3.03k | for (auto iter = search_rects.begin(); iter != search_rects.end();) { |
944 | 2.58k | const auto& [ind, slope] = *iter; |
945 | 2.58k | if (groups_[ind].min_rect()->ave_value() - |
946 | 2.58k | slope * groups_[ind].dist() <= |
947 | 2.58k | value_bound) { |
948 | 2.58k | ++iter; |
949 | 2.58k | } else { |
950 | 0 | iter = search_rects.erase(iter); |
951 | 0 | } |
952 | 2.58k | } |
953 | | |
954 | 450 | return search_rects; |
955 | 450 | } |
956 | | |
957 | | /*! |
958 | | * \brief Calculate slope. |
959 | | * |
960 | | * \param[in] group_ind1 Index of group. |
961 | | * \param[in] group_ind2 Index of group. |
962 | | * \return Slope. |
963 | | */ |
964 | | [[nodiscard]] auto calculate_slope( |
965 | 3.17k | std::size_t group_ind1, std::size_t group_ind2) const -> value_type { |
966 | 3.17k | return (groups_[group_ind1].min_rect()->ave_value() - |
967 | 3.17k | groups_[group_ind2].min_rect()->ave_value()) / |
968 | 3.17k | (groups_[group_ind1].dist() - groups_[group_ind2].dist()); |
969 | 3.17k | } |
970 | | |
971 | | /*! |
972 | | * \brief Divide a hyper-rectangle. |
973 | | * |
974 | | * \param[in] group_ind Index of group. |
975 | | */ |
976 | 2.58k | void divide_rectangle(std::size_t group_ind) { |
977 | 2.58k | impl::ternary_vector vertex = groups_[group_ind].pop()->vertex(); |
978 | 2.58k | index_type divided_dim = 1; |
979 | 4.81k | for (; divided_dim < vertex.dim(); ++divided_dim) { |
980 | 4.24k | if (vertex.digits(divided_dim) < vertex.digits(0)) { |
981 | 2.01k | break; |
982 | 2.01k | } |
983 | 4.24k | } |
984 | 2.58k | if (divided_dim == vertex.dim()) { |
985 | 573 | divided_dim = 0; |
986 | 573 | } |
987 | | |
988 | 2.58k | vertex.push_back(divided_dim, 0); |
989 | 2.58k | const auto rect0 = create_rect(vertex, group_ind + 1); |
990 | 2.58k | vertex(divided_dim, vertex.digits(divided_dim) - 1) = 1; |
991 | 2.58k | const auto rect1 = create_rect(vertex, group_ind + 1); |
992 | 2.58k | vertex(divided_dim, vertex.digits(divided_dim) - 1) = 2; |
993 | 2.58k | const auto rect2 = create_rect(vertex, group_ind + 1); |
994 | | |
995 | 2.58k | if (groups_.size() == group_ind + 1) { |
996 | 53 | groups_.emplace_back(rect0->dist()); |
997 | 53 | } |
998 | 2.58k | groups_[group_ind + 1].push(rect0); |
999 | 2.58k | groups_[group_ind + 1].push(rect1); |
1000 | 2.58k | groups_[group_ind + 1].push(rect2); |
1001 | 2.58k | } |
1002 | | |
1003 | | /*! |
1004 | | * \brief Create a hyper-rectangle. |
1005 | | * |
1006 | | * \param[in] vertex Vertex with lower first component. |
1007 | | * \param[in] group_ind Group index. |
1008 | | * \return Hyper-rectangle. |
1009 | | */ |
1010 | | [[nodiscard]] auto create_rect(const impl::ternary_vector& vertex, |
1011 | 7.76k | std::size_t group_ind) -> std::shared_ptr<rectangle_type> { |
1012 | 7.76k | const auto [vertex1, vertex2] = |
1013 | 7.76k | rectangle_type::determine_sample_points(vertex); |
1014 | 7.76k | const auto value1 = value_dict_(vertex1); |
1015 | 7.76k | const auto value2 = value_dict_(vertex2); |
1016 | 7.76k | const auto ave_value = half * (value1 + value2); |
1017 | 7.76k | if (value1 < optimal_value_) { |
1018 | 45 | optimal_value_ = value1; |
1019 | 45 | optimal_group_index_ = group_ind; |
1020 | 7.71k | } else if (value1 <= optimal_value_ && |
1021 | 7.71k | group_ind > optimal_group_index_) { |
1022 | 22 | optimal_group_index_ = group_ind; |
1023 | 22 | } |
1024 | 7.76k | if (value2 < optimal_value_) { |
1025 | 32 | optimal_value_ = value2; |
1026 | 32 | optimal_group_index_ = group_ind; |
1027 | 7.72k | } else if (value2 <= optimal_value_ && |
1028 | 7.72k | group_ind > optimal_group_index_) { |
1029 | 0 | optimal_group_index_ = group_ind; |
1030 | 0 | } |
1031 | 7.76k | return std::make_shared<rectangle_type>(vertex, ave_value); |
1032 | 7.76k | } |
1033 | | |
1034 | | //! Half. |
1035 | | static inline const auto half = static_cast<value_type>(0.5); |
1036 | | |
1037 | | //! Dictionary of sampled points. |
1038 | | dict_type value_dict_; |
1039 | | |
1040 | | //! Groups of hyper-rectangles. |
1041 | | std::vector<group_type> groups_{}; |
1042 | | |
1043 | | //! Number of iterations. |
1044 | | index_type iterations_{0}; |
1045 | | |
1046 | | //! State. |
1047 | | state_type state_{state_type::none}; |
1048 | | |
1049 | | /*! |
1050 | | * \brief Current optimal value. |
1051 | | * |
1052 | | * This value is used for updating \ref optimal_group_index_. |
1053 | | */ |
1054 | | value_type optimal_value_{}; |
1055 | | |
1056 | | //! Index of the group in which the optimal solution exists. |
1057 | | std::size_t optimal_group_index_{0}; |
1058 | | |
1059 | | //! Old optimal value at the start of the current phase. |
1060 | | value_type prec_optimal_value_{}; |
1061 | | |
1062 | | /*! |
1063 | | * \brief Index of the group in which the old optimal solution at the start |
1064 | | * of the current phase exists. |
1065 | | */ |
1066 | | std::size_t prec_optimal_group_index_{0}; |
1067 | | |
1068 | | /*! |
1069 | | * \brief Number of iterations in the current phase. |
1070 | | * |
1071 | | * This is initialized at the start of the local or global phases. |
1072 | | */ |
1073 | | index_type iterations_in_current_phase_{0}; |
1074 | | |
1075 | | //! Default maximum number of function evaluations. |
1076 | | static constexpr index_type default_max_evaluations = 10000; |
1077 | | |
1078 | | //! Maximum number of function evaluations. |
1079 | | index_type max_evaluations_{default_max_evaluations}; |
1080 | | |
1081 | | /*! |
1082 | | * \brief Default minimum rate of improvement in the function value required |
1083 | | * for potentially optimal rectangles. |
1084 | | */ |
1085 | | static inline const auto default_min_rate_imp = |
1086 | | static_cast<value_type>(1e-4); |
1087 | | |
1088 | | /*! |
1089 | | * \brief Minimum rate of improvement in the function value required for |
1090 | | * potentially optimal rectangles. |
1091 | | */ |
1092 | | value_type min_rate_imp_{default_min_rate_imp}; |
1093 | | |
1094 | | /*! |
1095 | | * \brief Default rate of function value used to check whether the function |
1096 | | * value decreased in the current phase. |
1097 | | */ |
1098 | | static inline const auto default_decrease_rate_bound = |
1099 | | static_cast<value_type>(0.01); |
1100 | | |
1101 | | /*! |
1102 | | * \brief Rate of function value used to check whether the function value |
1103 | | * decreased in the current phase. |
1104 | | */ |
1105 | | value_type decrease_rate_bound_{default_decrease_rate_bound}; |
1106 | | }; |
1107 | | |
1108 | | } // namespace num_collect::opt |