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