numerical-collection-cpp 0.10.0
A collection of algorithms in numerical analysis implemented in C++
Loading...
Searching...
No Matches
dividing_rectangles.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 <iterator>
26#include <limits>
27#include <memory>
28#include <queue>
29#include <tuple>
30#include <utility>
31#include <vector>
32
45#include "num_collect/util/is_eigen_vector.h" // IWYU pragma: keep
47
48namespace num_collect::opt {
49
52 logging::log_tag_view("num_collect::opt::dividing_rectangles");
53
60template <concepts::objective_function ObjectiveFunction>
62 : public optimizer_base<dividing_rectangles<ObjectiveFunction>> {
63public:
66
68 using objective_function_type = ObjectiveFunction;
69
71 using variable_type = typename objective_function_type::variable_type;
72
74 using value_type = typename objective_function_type::value_type;
75
86
93 obj_fun_ = obj_fun;
94 }
95
102 void init(const variable_type& lower, const variable_type& upper) {
103 if constexpr (is_eigen_vector_v<variable_type>) {
104 NUM_COLLECT_PRECONDITION(lower.size() == upper.size(),
105 this->logger(),
106 "Lower and upper limits must have the same size.");
107 }
108 lower_ = lower;
109 upper_ = upper;
110 width_ = upper_ - lower_;
112
113 const auto half = static_cast<value_type>(0.5);
114 const variable_type first_var = half * width_ + lower_;
115 obj_fun_.evaluate_on(first_var);
116 opt_variable_ = first_var;
118
119 iterations_ = 0;
120 evaluations_ = 1;
121
122 rects_.clear();
123 rects_.emplace_back();
124 if constexpr (is_eigen_vector_v<variable_type>) {
125 rects_[0].push(
126 std::make_shared<rectangle>(variable_type::Zero(dim_),
127 variable_type::Ones(dim_), opt_value_));
128 } else {
129 rects_[0].push(
130 std::make_shared<rectangle>(static_cast<variable_type>(0),
131 static_cast<variable_type>(1), opt_value_));
132 }
133 }
134
138 void iterate() {
139 const auto divided_rects = determine_rects();
140 for (auto iter = std::rbegin(divided_rects);
141 iter != std::rend(divided_rects); ++iter) {
142 divide_rect(iter->first);
143 }
144 ++iterations_;
145 }
146
150 [[nodiscard]] auto is_stop_criteria_satisfied() const -> bool {
151 return evaluations() >= max_evaluations_;
152 }
153
159 const {
160 iteration_logger.template append<index_type>(
161 "Iter.", &this_type::iterations);
162 iteration_logger.template append<index_type>(
163 "Eval.", &this_type::evaluations);
164 iteration_logger.template append<value_type>(
165 "Value", &this_type::opt_value);
166 }
167
171 [[nodiscard]] auto opt_variable() const -> const variable_type& {
172 return opt_variable_;
173 }
174
178 [[nodiscard]] auto opt_value() const -> const value_type& {
179 return opt_value_;
180 }
181
185 [[nodiscard]] auto iterations() const noexcept -> index_type {
186 return iterations_;
187 }
188
192 [[nodiscard]] auto evaluations() const noexcept -> index_type {
193 return evaluations_;
194 }
195
203 NUM_COLLECT_PRECONDITION(value > 0, this->logger(),
204 "Maximum number of function evaluations must be a positive "
205 "integer.");
206 max_evaluations_ = value;
207 return *this;
208 }
209
218 NUM_COLLECT_PRECONDITION(value > static_cast<value_type>(0),
219 this->logger(),
220 "Minimum rate of improvement in the function value required "
221 "for potentially optimal rectangles must be a positive value.");
222 min_rate_imp_ = value;
223 return *this;
224 }
225
226private:
230 class rectangle {
231 public:
240 const value_type& value)
241 : lower_(lower),
242 upper_(upper),
243 is_divided_(util::safe_cast<std::size_t>(get_size(lower)), false),
244 dist_(norm(lower - upper) * half),
245 value_(value) {}
246
252 [[nodiscard]] auto lower() const -> const variable_type& {
253 return lower_;
254 }
255
261 [[nodiscard]] auto upper() const -> const variable_type& {
262 return upper_;
263 }
264
270 [[nodiscard]] auto is_divided() const -> const std::vector<bool>& {
271 return is_divided_;
272 }
273
279 [[nodiscard]] auto dist() const -> const value_type& { return dist_; }
280
286 [[nodiscard]] auto value() const -> const value_type& { return value_; }
287
300 !is_divided_[static_cast<std::size_t>(dim)]);
301 if constexpr (is_eigen_vector_v<variable_type>) {
302 lower_(dim) = lower;
303 upper_(dim) = upper;
304 } else {
305 lower_ = lower;
306 upper_ = upper;
307 }
308 dist_ = norm(lower_ - upper_) * half;
309 value_ = value;
310 is_divided_[static_cast<std::size_t>(dim)] = true;
311 if (std::all_of(std::begin(is_divided_), std::end(is_divided_),
312 [](bool elem) { return elem; })) {
313 std::fill(
314 std::begin(is_divided_), std::end(is_divided_), false);
315 }
316 }
317
327 [[nodiscard]] auto divide(index_type dim, value_type lower,
329 -> std::shared_ptr<rectangle> {
330 auto ptr = std::make_shared<rectangle>(*this);
331 ptr->divide_in_place(dim, lower, upper, value);
332 return ptr;
333 }
334
335 private:
337 static inline const auto half = static_cast<value_type>(0.5);
338
341
344
350 std::vector<bool> is_divided_;
351
354
357 };
358
370 [[nodiscard]] auto operator()(const std::shared_ptr<rectangle>& left,
371 const std::shared_ptr<rectangle>& right) const -> bool {
372 return left->value() > right->value();
373 }
374 };
375
382 [[nodiscard]] auto evaluate_on(const variable_type& variable)
383 -> value_type {
384 variable_type actual_var;
385 if constexpr (is_eigen_vector_v<variable_type>) {
386 actual_var = variable.cwiseProduct(width_) + lower_;
387 } else {
388 actual_var = variable * width_ + lower_;
389 }
390 obj_fun_.evaluate_on(actual_var);
391 const value_type value = correct_value_if_needed(obj_fun_.value());
392 if (value < opt_value_) {
393 opt_variable_ = actual_var;
394 opt_value_ = value;
395 }
396 ++evaluations_;
397 return value;
398 }
399
405 [[nodiscard]] auto determine_rects() const
406 -> std::vector<std::pair<std::size_t, value_type>> {
407 std::vector<std::pair<std::size_t, value_type>> search_rects;
408 std::size_t ind = 0;
409 // find the largest rectangle
410 for (; ind < rects_.size(); ++ind) {
411 if (!rects_[ind].empty()) {
412 break;
413 }
414 }
415 search_rects.emplace_back(ind, std::numeric_limits<value_type>::max());
416 ++ind;
417 // convex full scan
418 for (; ind < rects_.size(); ++ind) {
419 if (rects_[ind].empty()) {
420 continue;
421 }
422 while (true) {
423 const auto& [last_ind, last_slope] = search_rects.back();
424 const auto slope = calculate_slope(
425 *(rects_[last_ind].top()), *(rects_[ind].top()));
426 if (slope <= last_slope) {
427 search_rects.emplace_back(ind, slope);
428 break;
429 }
430 search_rects.pop_back();
431 }
432 }
433 // remove rectangles which won't update optimal value
434 using std::abs;
435 const auto value_bound = opt_value_ - min_rate_imp_ * abs(opt_value_);
436 for (auto iter = search_rects.begin(); iter != search_rects.end();) {
437 const auto& [ind, slope] = *iter;
438 if (rects_[ind].top()->value() -
439 slope * rects_[ind].top()->dist() <=
440 value_bound) {
441 ++iter;
442 } else {
443 iter = search_rects.erase(iter);
444 }
445 }
446 return search_rects;
447 }
448
456 [[nodiscard]] static auto calculate_slope(const rectangle& larger_rect,
457 const rectangle& smaller_rect) -> value_type {
458 return (larger_rect.value() - smaller_rect.value()) /
459 (larger_rect.dist() - smaller_rect.dist());
460 }
461
467 void divide_rect(std::size_t index) {
468 if (index + 1 == rects_.size()) {
469 rects_.emplace_back();
470 }
471 const auto origin = rects_[index].top();
472 rects_[index].pop();
473
474 const auto [divided_dim, lower_value, upper_value] =
476 value_type divided_lowest;
477 value_type divided_highest;
478 if constexpr (is_eigen_vector_v<variable_type>) {
479 divided_lowest = origin->lower()(divided_dim);
480 divided_highest = origin->upper()(divided_dim);
481 } else {
482 divided_lowest = origin->lower();
483 divided_highest = origin->upper();
484 }
485 const auto [divided_lower, divided_upper] =
486 separate_section(divided_lowest, divided_highest);
487
488 rects_[index + 1].push(origin->divide(
489 divided_dim, divided_lowest, divided_lower, lower_value));
490 rects_[index + 1].push(origin->divide(
491 divided_dim, divided_upper, divided_highest, upper_value));
492 origin->divide_in_place(
493 divided_dim, divided_lower, divided_upper, origin->value());
494 rects_[index + 1].push(origin);
495 }
496
504 [[nodiscard]] auto determine_divided_dimension(const rectangle& rect)
505 -> std::tuple<index_type, value_type, value_type> {
506 value_type min_value = std::numeric_limits<value_type>::max();
507 index_type dim = 0;
508 value_type dim_lower_value{};
509 value_type dim_upper_value{};
510 for (index_type i = 0; i < dim_; ++i) {
511 if (rect.is_divided()[util::safe_cast<std::size_t>(i)]) {
512 continue;
513 }
514
515 value_type width;
516 if constexpr (is_eigen_vector_v<variable_type>) {
517 width = rect.upper()[i] - rect.lower()[i];
518 } else {
519 width = rect.upper() - rect.lower();
520 }
521 static const auto one_third = static_cast<value_type>(1.0 / 3.0);
522 const value_type diff = width * one_third;
523
524 static const auto half = static_cast<value_type>(0.5);
525 variable_type center = (rect.lower() + rect.upper()) * half;
526 value_type origin_center;
527 if constexpr (is_eigen_vector_v<variable_type>) {
528 origin_center = center[i];
529 } else {
530 origin_center = center;
531 }
532
533 value_type lower_value;
534 value_type upper_value;
535 if constexpr (is_eigen_vector_v<variable_type>) {
536 center[i] = origin_center - diff;
537 lower_value = evaluate_on(center);
538 center[i] = origin_center + diff;
539 upper_value = evaluate_on(center);
540 } else {
541 lower_value = evaluate_on(origin_center - diff);
542 upper_value = evaluate_on(origin_center + diff);
543 }
544
545 if (lower_value < min_value) {
546 min_value = lower_value;
547 dim = i;
548 dim_lower_value = lower_value;
549 dim_upper_value = upper_value;
550 }
551 if (upper_value < min_value) {
552 min_value = upper_value;
553 dim = i;
554 dim_lower_value = lower_value;
555 dim_upper_value = upper_value;
556 }
557 }
558
559 return {dim, dim_lower_value, dim_upper_value};
560 }
561
569 [[nodiscard]] static auto separate_section(value_type lowest,
570 value_type highest) -> std::pair<value_type, value_type> {
571 const auto width = highest - lowest;
572 static const auto one_third = static_cast<value_type>(1.0 / 3.0);
573 static const auto two_thirds = static_cast<value_type>(2.0 / 3.0);
574 return {lowest + one_third * width, lowest + two_thirds * width};
575 }
576
583 [[nodiscard]] static auto correct_value_if_needed(value_type value) noexcept
584 -> value_type {
585 constexpr auto safe_limit =
586 std::numeric_limits<value_type>::max() * 1e-2;
587 if (!isfinite(value) || value > safe_limit) {
588 return safe_limit;
589 }
590 return value;
591 }
592
595
601 std::vector<std::priority_queue<std::shared_ptr<rectangle>,
602 std::vector<std::shared_ptr<rectangle>>, greater_rectangle>>
604
607
610
613
616
619
622
625
628
630 static constexpr index_type default_max_evaluations = 10000;
631
634
639 static inline const auto default_min_rate_imp =
640 static_cast<value_type>(1e-4);
641
647};
648
649} // namespace num_collect::opt
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 tags of logs without memory management.
auto logger() const noexcept -> const num_collect::logging::logger &
Access to the logger.
Class of hyper rectangle in DIRECT method.
std::vector< bool > is_divided_
Whether each dimension is divided.
rectangle(const variable_type &lower, const variable_type &upper, const value_type &value)
Constructor.
variable_type upper_
Element-wise upper limit.
auto divide(index_type dim, value_type lower, value_type upper, value_type value) const -> std::shared_ptr< rectangle >
Divide this object and return the divided rectangle.
value_type dist_
Distance between center point and end point.
void divide_in_place(index_type dim, value_type lower, value_type upper, value_type value)
Divide this object in place.
auto value() const -> const value_type &
Get function value at the center.
value_type value_
Function value at the center.
auto dist() const -> const value_type &
Get distance between center point and end point.
variable_type lower_
Element-wise lower limit.
auto upper() const -> const variable_type &
Get element-wise upper limit.
auto lower() const -> const variable_type &
Get element-wise lower limit.
auto is_divided() const -> const std::vector< bool > &
Get whether each dimension is divided.
Class of dividing rectangles (DIRECT) method jones1993 for optimization.
typename objective_function_type::variable_type variable_type
Type of variables.
value_type opt_value_
Current optimal value.
static auto separate_section(value_type lowest, value_type highest) -> std::pair< value_type, value_type >
Separate a section.
variable_type lower_
Element-wise lower limit.
variable_type width_
Element-wise width.
static auto calculate_slope(const rectangle &larger_rect, const rectangle &smaller_rect) -> value_type
Calculate slope.
auto evaluate_on(const variable_type &variable) -> value_type
Evaluate function value.
void configure_iteration_logger(logging::iterations::iteration_logger< this_type > &iteration_logger) const
Configure an iteration logger.
static auto correct_value_if_needed(value_type value) noexcept -> value_type
Correct function values if needed.
auto determine_rects() const -> std::vector< std::pair< std::size_t, value_type > >
Search rectangles to divide.
auto iterations() const noexcept -> index_type
Get the number of iterations.
void change_objective_function(const objective_function_type &obj_fun)
Change the objective function.
std::vector< std::priority_queue< std::shared_ptr< rectangle >, std::vector< std::shared_ptr< rectangle > >, greater_rectangle > > rects_
Rectangles.
objective_function_type obj_fun_
Objective function.
auto determine_divided_dimension(const rectangle &rect) -> std::tuple< index_type, value_type, value_type >
Determine the divided dimension of a rectangle.
variable_type upper_
Element-wise upper limit.
dividing_rectangles(const objective_function_type &obj_fun=objective_function_type())
Constructor.
void iterate()
Iterate the algorithm once.
value_type min_rate_imp_
Minimum rate of improvement in the function value required for potentially optimal rectangles.
index_type iterations_
Number of iterations.
ObjectiveFunction objective_function_type
Type of the objective function.
static const auto default_min_rate_imp
Default minimum rate of improvement in the function value required for potentially optimal rectangles...
void init(const variable_type &lower, const variable_type &upper)
Initialize the algorithm.
auto max_evaluations(index_type value) -> dividing_rectangles &
Set the maximum number of function evaluations.
void divide_rect(std::size_t index)
Divide a rectangle.
typename objective_function_type::value_type value_type
Type of function values.
variable_type opt_variable_
Current optimal variable.
index_type max_evaluations_
Maximum number of function evaluations.
auto is_stop_criteria_satisfied() const -> bool
Determine if stopping criteria of the algorithm are satisfied.
static constexpr index_type default_max_evaluations
Default maximum number of function evaluations.
index_type evaluations_
Number of function evaluations.
index_type dim_
Number of dimension.
auto opt_variable() const -> const variable_type &
Get current optimal variable.
auto evaluations() const noexcept -> index_type
Get the number of function evaluations.
auto min_rate_imp(value_type value) -> dividing_rectangles &
Set the minimum rate of improvement in the function value required for potentially optimal rectangles...
auto opt_value() const -> const value_type &
Get current optimal value.
Base class of implementations of optimization algorithms.
Definition of exceptions.
Definition of get_size class.
Definition of index_type type.
Definition of is_eigen_vector class.
Definition of isfinite function.
Definition of iteration_logger class.
Definition of log_tag_view class.
Definition of macros for logging.
std::ptrdiff_t index_type
Type of indices in this library.
Definition index_type.h:33
auto norm(const Matrix &matrix)
Calculate norm of a matrix.
Definition norm.h:39
auto get_size(const Matrix &matrix) -> index_type
Get the size.
Definition get_size.h:39
auto isfinite(const T &val) -> bool
Check whether a number is finite.
Definition isfinite.h:39
Namespace of optimization algorithms.
constexpr auto dividing_rectangles_tag
Tag of dividing_rectangles.
auto safe_cast(const From &value) -> To
Cast safely.
Definition safe_cast.h:54
constexpr bool is_eigen_vector_v
Get whether a type is a Eigen's vector.
STL namespace.
Definition of norm class.
Definition of objective_function concept.
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.
auto operator()(const std::shared_ptr< rectangle > &left, const std::shared_ptr< rectangle > &right) const -> bool
Compare rectangles.