Coverage Report

Created: 2025-01-24 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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