numerical-collection-cpp 0.10.0
A collection of algorithms in numerical analysis implemented in C++
Loading...
Searching...
No Matches
adaptive_diagonal_curves.h
Go to the documentation of this file.
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 */
20#pragma once
21
22#include <algorithm>
23#include <cmath>
24#include <cstddef>
25#include <cstdint>
26#include <iterator>
27#include <limits>
28#include <string_view>
29#include <utility>
30#include <vector>
31
44
46
49 logging::log_tag_view("num_collect::opt::adaptive_diagonal_curves");
50
59template <concepts::multi_variate_objective_function ObjectiveFunction,
60 index_type MaxDigits = 8> // NOLINT(*-magic-numbers)
62 : public optimizer_base<
63 adaptive_diagonal_curves<ObjectiveFunction, MaxDigits>> {
64public:
67
69 using objective_function_type = ObjectiveFunction;
70
72 using variable_type = typename objective_function_type::variable_type;
73
75 using value_type = typename objective_function_type::value_type;
76
88
95 [[nodiscard]] static auto state_name(state_type state) -> std::string_view {
96 switch (state) {
98 return "none";
100 return "local";
102 return "local (last)";
104 return "global";
106 return "global (last)";
108 return "non dividable";
109 default:
110 return "invalid process";
111 }
112 }
113
123
130 value_dict_.change_objective_function(obj_fun);
131 }
132
139 void init(const variable_type& lower, const variable_type& upper) {
140 value_dict_.init(lower, upper);
141 groups_.clear();
142 iterations_ = 0;
144
145 // Initialize groups_, optimal_value_, optimal_group_index_.
147
148 // prec_optimal_value_, prec_optimal_group_index, and
149 // iterations_in_current_phase_ are initialized in switch_state().
150
152 }
153
157 void iterate() {
158 switch_state();
159
160 switch (state_) {
163 break;
166 break;
169 break;
172 break;
174 // Nothing can be done.
175 break;
176 default:
178 "invalid state (bug in adaptive_diagonal_curve class)");
179 }
180
181 ++iterations_;
182 }
183
187 [[nodiscard]] auto is_stop_criteria_satisfied() const -> bool {
188 return evaluations() >= max_evaluations_ ||
190 }
191
197 const {
198 iteration_logger.template append<index_type>(
199 "Iter.", &this_type::iterations);
200 iteration_logger.template append<index_type>(
201 "Eval.", &this_type::evaluations);
202 iteration_logger.template append<value_type>(
203 "Value", &this_type::opt_value);
204 constexpr index_type state_width = 15;
205 iteration_logger
206 .template append<std::string_view>(
208 ->width(state_width);
209 }
210
214 [[nodiscard]] auto opt_variable() const -> const variable_type& {
215 return value_dict_.opt_variable();
216 }
217
221 [[nodiscard]] auto opt_value() const -> const value_type& {
222 return value_dict_.opt_value();
223 }
224
228 [[nodiscard]] auto iterations() const noexcept -> index_type {
229 return iterations_;
230 }
231
235 [[nodiscard]] auto evaluations() const noexcept -> index_type {
236 return value_dict_.evaluations();
237 }
238
244 [[nodiscard]] auto last_state() const noexcept -> state_type {
245 return state_;
246 }
247
253 [[nodiscard]] auto last_state_name() const noexcept -> std::string_view {
254 return state_name(last_state());
255 }
256
264 NUM_COLLECT_PRECONDITION(value > 0, this->logger(),
265 "Maximum number of function evaluations must be a positive "
266 "integer.");
267
268 max_evaluations_ = value;
269 return *this;
270 }
271
280 NUM_COLLECT_PRECONDITION(value > static_cast<value_type>(0),
281 this->logger(),
282 "Minimum rate of improvement in the function value required for "
283 "potentially optimal rectangles must be a positive value.");
284 min_rate_imp_ = value;
285 return *this;
286 }
287
296 NUM_COLLECT_PRECONDITION(value > static_cast<value_type>(0),
297 this->logger(),
298 "Rate of function value used to check whether the function value "
299 "decreased in the current phase must be a positive value.");
300 decrease_rate_bound_ = value;
301 return *this;
302 }
303
304private:
307
309 using ternary_vector_type = typename dict_type::ternary_vector_type;
310
313
316
321 const index_type dim = value_dict_.dim();
322 auto point = ternary_vector_type{dim};
323 for (index_type i = 0; i < dim; ++i) {
324 point.push_back(0);
325 }
326
327 const auto [lower_vertex, upper_vertex] =
328 rectangle_type::determine_sample_points(point);
329 const auto lower_vertex_value = value_dict_(lower_vertex);
330 const auto upper_vertex_value = value_dict_(upper_vertex);
331 const auto ave_value = half * (lower_vertex_value + upper_vertex_value);
332 const auto rect = rectangle_type(point, ave_value);
333
334 groups_.emplace_back(rect.dist());
335 groups_.front().push(rect);
336 using std::min;
337 optimal_value_ = min(lower_vertex_value, upper_vertex_value);
339 }
340
347 using std::abs;
348 switch (state_) {
353 }
354 return;
357 return;
365 return;
366 }
369 util::safe_cast<index_type>((static_cast<std::size_t>(2)
372 return;
373 }
374 return;
382 return;
383 }
387 return;
389 // Nothing can be done.
390 return;
391 default:
396 }
397 }
398
403 using std::abs;
410 return;
411 }
412
413 const bool is_optimal_smallest =
414 (optimal_group_index_ == groups_.size() - 1);
415 const bool is_all_smallest =
416 std::all_of(std::begin(groups_), std::end(groups_) - 1,
417 [](const group_type& group) { return group.empty(); });
418 if ((!is_optimal_smallest) || is_all_smallest) {
422 return;
423 }
424
429 }
430
435 const std::size_t max_group_index = std::max<std::size_t>(
436 std::max<std::size_t>(prec_optimal_group_index_, 1) - 1,
439 min_nonempty_group_index(), max_group_index);
440 }
441
446 const std::size_t max_group_index = std::max<std::size_t>(
449 min_nonempty_group_index(), max_group_index);
450 }
451
456 const std::size_t max_group_index =
457 (std::max<std::size_t>(
460 2;
462 min_nonempty_group_index(), max_group_index);
463 }
464
469
475 [[nodiscard]] auto min_nonempty_group_index() const -> std::size_t {
476 for (std::size_t i = 0; i < groups_.size(); ++i) {
477 if (!groups_[i].empty()) {
478 return i;
479 }
480 }
482 "adaptive_diagonal_curves::init is not called.");
483 }
484
492 std::size_t min_group, std::size_t max_group) {
493 const auto search_rect =
494 determine_nondominated_rectangles(min_group, max_group);
495 bool divided_rectangle = false;
496 for (auto iter = std::rbegin(search_rect);
497 iter != std::rend(search_rect); ++iter) {
498 if (divide_rectangle(iter->first)) {
499 divided_rectangle = true;
500 }
501 }
502 if (!divided_rectangle) {
504 this->logger().warning()(
505 "No rectangle can be divided. "
506 "Stop the iteration or tune the parameter MaxDigits.");
507 }
508 }
509
518 std::size_t min_group, std::size_t max_group) const
519 -> std::vector<std::pair<std::size_t, value_type>> {
520 NUM_COLLECT_DEBUG_ASSERT(min_group <= max_group);
521 NUM_COLLECT_DEBUG_ASSERT(max_group < groups_.size());
522 NUM_COLLECT_DEBUG_ASSERT(!groups_[min_group].empty());
523
524 std::vector<std::pair<std::size_t, value_type>> search_rects;
525 search_rects.emplace_back(
526 min_group, std::numeric_limits<value_type>::max());
527
528 // convex full scan
529 for (std::size_t i = min_group + 1; i <= max_group; ++i) {
530 if (groups_[i].empty()) {
531 continue;
532 }
533 while (true) {
534 const auto& [last_i, last_slope] = search_rects.back();
535 const auto slope = calculate_slope(last_i, i);
536 if (slope <= last_slope) {
537 search_rects.emplace_back(i, slope);
538 break;
539 }
540 search_rects.pop_back();
541 }
542 }
543
544 // remove rectangles which won't update optimal value
545 using std::abs;
546 const auto value_bound =
548 for (auto iter = search_rects.begin(); iter != search_rects.end();) {
549 const auto& [ind, slope] = *iter;
550 if (groups_[ind].min_rect().ave_value() -
551 slope * groups_[ind].dist() <=
552 value_bound) {
553 ++iter;
554 } else {
555 iter = search_rects.erase(iter);
556 }
557 }
558
559 return search_rects;
560 }
561
569 [[nodiscard]] auto calculate_slope(
570 std::size_t group_ind1, std::size_t group_ind2) const -> value_type {
571 return (groups_[group_ind1].min_rect().ave_value() -
572 groups_[group_ind2].min_rect().ave_value()) /
573 (groups_[group_ind1].dist() - groups_[group_ind2].dist());
574 }
575
583 [[nodiscard]] auto divide_rectangle(std::size_t group_ind) -> bool {
584 if (!groups_[group_ind].is_dividable()) {
585 return false;
586 }
587
589 std::move(groups_[group_ind].pop().vertex());
590
591 const auto [dim, digit] = vertex.push_back(0);
592 const auto rect0 = create_rect(vertex, group_ind + 1);
593 vertex(dim, digit) = 1;
594 const auto rect1 = create_rect(vertex, group_ind + 1);
595 vertex(dim, digit) = 2;
596 const auto rect2 = create_rect(vertex, group_ind + 1);
597
598 if (groups_.size() == group_ind + 1) {
599 groups_.emplace_back(rect0.dist());
600 }
601 groups_[group_ind + 1].push(rect0);
602 groups_[group_ind + 1].push(rect1);
603 groups_[group_ind + 1].push(rect2);
604
605 return true;
606 }
607
615 [[nodiscard]] auto create_rect(const ternary_vector_type& vertex,
616 std::size_t group_ind) -> rectangle_type {
617 const auto [vertex1, vertex2] =
618 rectangle_type::determine_sample_points(vertex);
619 const auto value1 = value_dict_(vertex1);
620 const auto value2 = value_dict_(vertex2);
621 const auto ave_value = half * (value1 + value2);
622 if (value1 < optimal_value_) {
623 optimal_value_ = value1;
624 optimal_group_index_ = group_ind;
625 } else if (value1 <= optimal_value_ &&
626 group_ind > optimal_group_index_) {
627 optimal_group_index_ = group_ind;
628 }
629 if (value2 < optimal_value_) {
630 optimal_value_ = value2;
631 optimal_group_index_ = group_ind;
632 } else if (value2 <= optimal_value_ &&
633 group_ind > optimal_group_index_) {
634 optimal_group_index_ = group_ind;
635 }
636 return rectangle_type(vertex, ave_value);
637 }
638
640 static inline const auto half = static_cast<value_type>(0.5);
641
644
646 std::vector<group_type> groups_{};
647
650
653
660
662 std::size_t optimal_group_index_{0};
663
666
672
679
681 static constexpr index_type default_max_evaluations = 10000;
682
685
690 static inline const auto default_min_rate_imp =
691 static_cast<value_type>(1e-4);
692
698
703 static inline const auto default_decrease_rate_bound =
704 static_cast<value_type>(0.01);
705
711};
712
713} // namespace num_collect::opt
Definition of adc_group class.
Definition of adc_sample_dict class.
Definition of assertion macros.
#define NUM_COLLECT_DEBUG_ASSERT(CONDITION)
Macro to check whether a condition is satisfied in debug build only.
Definition assert.h:75
Class of exception on failure in algorithm.
Definition exception.h:93
Class of exception on not satisfying a precondition.
Definition exception.h:77
Class of tags of logs without memory management.
auto logger() const noexcept -> const num_collect::logging::logger &
Access to the logger.
void configure_iteration_logger(logging::iterations::iteration_logger< this_type > &iteration_logger) const
Configure an iteration logger.
void init(const variable_type &lower, const variable_type &upper)
Initialize the algorithm.
void divide_nondominated_rectangles(std::size_t min_group, std::size_t max_group)
Divide nondominated hyper-rectangles.
void change_objective_function(const objective_function_type &obj_fun)
Change the objective function.
auto max_evaluations(index_type value) -> adaptive_diagonal_curves &
Set the maximum number of function evaluations.
auto calculate_slope(std::size_t group_ind1, std::size_t group_ind2) const -> value_type
Calculate slope.
void iterate_globally()
Iterate once in the global phase (not last iteration).
adaptive_diagonal_curves(const objective_function_type &obj_fun=objective_function_type())
Constructor.
auto opt_variable() const -> const variable_type &
Get current optimal variable.
auto min_nonempty_group_index() const -> std::size_t
Get the minimum index of non-empty groups.
void iterate_locally_last()
Iterate once at the last of the local phase.
auto last_state() const noexcept -> state_type
Get the last state.
auto create_rect(const ternary_vector_type &vertex, std::size_t group_ind) -> rectangle_type
Create a hyper-rectangle.
void iterate_globally_last()
Iterate once at teh last of the global phase.
adaptive_diagonal_curves< ObjectiveFunction, MaxDigits > this_type
This class.
static auto state_name(state_type state) -> std::string_view
Convert a state to string.
void iterate_locally()
Iterate once in the local phase (not last iteration).
auto evaluations() const noexcept -> index_type
Get the number of function evaluations.
auto min_rate_imp(value_type value) -> adaptive_diagonal_curves &
Set the minimum rate of improvement in the function value required for potentially optimal rectangles...
void switch_state_on_local_last()
Switch to the next state if necessary in local_last state.
impl::adc_sample_dict< objective_function_type, MaxDigits > dict_type
auto is_stop_criteria_satisfied() const -> bool
Determine if stopping criteria of the algorithm are satisfied.
auto decrease_rate_bound(value_type value) -> adaptive_diagonal_curves &
Set the rate of function value used to check whether the function value decreased in the current phas...
void switch_state()
Switch to the next state if necessary.
void create_first_rectangle()
Create the first hyper-rectangle.
state_type
Enumeration of states in ADC method.
auto determine_nondominated_rectangles(std::size_t min_group, std::size_t max_group) const -> std::vector< std::pair< std::size_t, value_type > >
Determine nondominated hyper-rectangles.
auto divide_rectangle(std::size_t group_ind) -> bool
Divide a hyper-rectangle.
Class of groups in sergeyev2006 for num_collect::opt::adaptive_diagonal_curves.
Definition adc_group.h:40
adc_rectangle< value_type, ternary_vector_type > rectangle_type
Definition adc_group.h:46
auto empty() const -> bool
Check whether this group is empty.
Definition adc_group.h:78
Class of dictionaries of sampling points in num_collect::opt::adaptive_diagonal_curves.
Class of vectors of ternary floating-point numbers in num_collect::opt::adaptive_diagonal_curves.
auto push_back(digit_type digit) -> std::pair< index_type, index_type >
Add a digit to a dimension specified by next_divided_dimension_index().
Concept of multi-variate objective functions in optimization.
Definition of exceptions.
Definition of index_type type.
Definition of iteration_logger class.
Definition of log_tag_view class.
Definition of macros for logging.
#define NUM_COLLECT_LOG_AND_THROW(EXCEPTION_TYPE,...)
Write an error log and throw an exception for an error.
Definition of multi_variate_objective_function concept.
std::ptrdiff_t index_type
Type of indices in this library.
Definition index_type.h:33
Namespace of optimization algorithms.
constexpr auto adaptive_diagonal_curves_tag
Tag of adaptive_diagonal_curves.
auto safe_cast(const From &value) -> To
Cast safely.
Definition safe_cast.h:54
STL namespace.
Definition of optimizer_base class.
Definition of NUM_COLLECT_PRECONDITION macro.
#define NUM_COLLECT_PRECONDITION(CONDITION,...)
Check whether a precondition is satisfied and throw an exception if not.
Definition of safe_cast function.