Coverage Report

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