Quantum 3xplorer (Q3)
An AI-native quantum simulation engine
Loading...
Searching...
No Matches
convergence_window.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * ConvergenceWindow — an iteration has converged when its objective has
5 * stopped moving, relatively, for a window of consecutive steps.
6 *
7 * One step within tolerance proves nothing: a damped iteration passes
8 * through near-stationary points on its way, and a line search that has
9 * shrunk its step produces a value that moves by nothing while the gradient
10 * is still large. Requiring the last `window` differences all to be inside
11 * the tolerance is the least that distinguishes settled from paused. The
12 * test is relative to the current value, so the same tolerance serves a
13 * spread of ten and a spread of ten thousand.
14 */
15
16#include <algorithm>
17#include <cmath>
18#include <cstddef>
19#include <stdexcept>
20#include <string>
21#include <vector>
22
25
26namespace q3 {
27
29 public:
30 ConvergenceWindow(Real relative_tolerance, int window)
31 : relative_tolerance_(relative_tolerance), window_(window) {
32 if (!(relative_tolerance > 0.0)) {
33 throw std::invalid_argument(
34 "ConvergenceWindow: the relative tolerance must be positive, got " +
35 std::to_string(relative_tolerance));
36 }
37 if (window < 1) {
38 throw std::invalid_argument("ConvergenceWindow: the window must be at least one "
39 "step, got " +
40 std::to_string(window));
41 }
42 }
43
44 /// The objective at the end of one more step.
45 void record(Real value) {
46 history_.push_back(value);
47 }
48
49 /**
50 * Whether the last `window` differences are all within the tolerance of
51 * the current value. Needs `window + 1` values, so the first steps never
52 * count as converged.
53 */
54 [[nodiscard]] bool has_converged() const {
55 if (static_cast<int>(history_.size()) <= window_) {
56 return false;
57 }
58 const Real current = history_.back();
59 const Real scale =
60 std::max(std::abs(current), math::tolerances::relative_change_scale_floor);
61 for (int step = 1; step <= window_; step++) {
62 const Real earlier = history_[history_.size() - 1 - static_cast<std::size_t>(step)];
63 if (std::abs(current - earlier) / scale >= relative_tolerance_) {
64 return false;
65 }
66 }
67 return true;
68 }
69
70 [[nodiscard]] const std::vector<Real>& history() const {
71 return history_;
72 }
73
74 [[nodiscard]] int number_of_steps() const {
75 return static_cast<int>(history_.size());
76 }
77
78 /// The last `count` values, oldest first.
79 [[nodiscard]] std::vector<Real> recent_history(int count = 5) const {
80 const std::size_t start = history_.size() > static_cast<std::size_t>(count)
81 ? history_.size() - static_cast<std::size_t>(count)
82 : 0;
83 return std::vector<Real>(history_.begin() + static_cast<std::ptrdiff_t>(start),
84 history_.end());
85 }
86
87 /// The last `count` values, oldest first, as "a, b, c" with six decimals each.
88 [[nodiscard]] std::string recent_history_text(int count = 5) const {
89 return history_text(recent_history(count));
90 }
91
92 /**
93 * `values` as "a, b, c" with six decimals each: the one form in which an
94 * error names the spreads it reached, in its prose and on its own line.
95 */
96 [[nodiscard]] static std::string history_text(const std::vector<Real>& values) {
97 std::string text;
98 for (std::size_t index = 0; index < values.size(); index++) {
99 text += (index == 0 ? "" : ", ") + std::to_string(values[index]);
100 }
101 return text;
102 }
103
104 private:
105 Real relative_tolerance_;
106 int window_;
107 std::vector<Real> history_;
108};
109
110} // namespace q3
static std::string history_text(const std::vector< Real > &values)
std::string recent_history_text(int count=5) const
The last count values, oldest first, as "a, b, c" with six decimals each.
ConvergenceWindow(Real relative_tolerance, int window)
void record(Real value)
The objective at the end of one more step.
std::vector< Real > recent_history(int count=5) const
The last count values, oldest first.
const std::vector< Real > & history() const
constexpr double relative_change_scale_floor
double Real