Loading...
Searching...
No Matches
polak_ribiere_direction.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * PolakRibiereDirection — the conjugate search direction of a descent over
5 * one matrix per k-point: -G conjugated against the direction before it.
6 *
7 * Steepest descent on the spread functional creeps: a three-function chain
8 * on eight k-points takes five hundred iterations to do what conjugation
9 * does in a few dozen. The memory a conjugate direction needs -- the last
10 * gradient and the last direction -- is this object's state, so the
11 * minimiser asks for the next direction and carries nothing itself.
12 *
13 * `beta` is clamped at zero, which is the standard automatic restart: a
14 * negative value means the new gradient has turned far enough that the old
15 * direction is no longer useful, and following it anyway is how conjugate
16 * gradient goes unstable on a non-quadratic functional. A conjugated
17 * direction that points uphill is worse than no conjugation at all, so the
18 * steepest-descent direction is returned instead, rather than letting a line
19 * search spend its whole budget halving a step that was never going to help.
20 */
21
22#include <algorithm>
23#include <cstddef>
24#include <vector>
25
28
29namespace q3 {
30
32 public:
33 /**
34 * The direction to search along from a gauge whose gradient is
35 * `gradients`. `restart` forgets the previous direction and returns the
36 * steepest-descent one, which the caller asks for at intervals because
37 * the manifold is curved and a direction carried too far along it stops
38 * being conjugate to anything.
39 */
40 [[nodiscard]] const std::vector<DenseMatrix<Complex>>&
41 next(const std::vector<DenseMatrix<Complex>>& gradients, bool restart) {
42 direction_ = conjugated(gradients, restart);
43 previous_gradients_ = gradients;
44 return direction_;
45 }
46
47 private:
48 [[nodiscard]] std::vector<DenseMatrix<Complex>>
49 conjugated(const std::vector<DenseMatrix<Complex>>& gradients, bool restart) const {
50 const std::vector<DenseMatrix<Complex>> steepest_descent = negated(gradients);
51 if (restart || previous_gradients_.empty() || direction_.empty()) {
52 return steepest_descent;
53 }
54
55 Real numerator = 0.0;
56 Real denominator = 0.0;
57 for (std::size_t kpoint = 0; kpoint < gradients.size(); kpoint++) {
58 DenseMatrix<Complex> change = gradients[kpoint];
59 change.add_scaled(-1.0, previous_gradients_[kpoint]);
60 numerator += gradients[kpoint].real_inner_product(change);
61 denominator += previous_gradients_[kpoint].frobenius_norm_squared();
62 }
63 if (denominator <= 0.0) {
64 return steepest_descent;
65 }
66 const Real beta = std::max(0.0, numerator / denominator);
67 if (beta == 0.0) {
68 return steepest_descent;
69 }
70
71 std::vector<DenseMatrix<Complex>> direction = steepest_descent;
72 Real slope = 0.0;
73 for (std::size_t kpoint = 0; kpoint < gradients.size(); kpoint++) {
74 direction[kpoint].add_scaled(beta, direction_[kpoint]);
75 slope += gradients[kpoint].real_inner_product(direction[kpoint]);
76 }
77 return slope >= 0.0 ? steepest_descent : direction;
78 }
79
80 /// -G at every k-point: the steepest-descent direction.
81 [[nodiscard]] static std::vector<DenseMatrix<Complex>>
82 negated(const std::vector<DenseMatrix<Complex>>& gradients) {
83 std::vector<DenseMatrix<Complex>> result;
84 result.reserve(gradients.size());
85 for (const DenseMatrix<Complex>& gradient : gradients) {
86 result.push_back(gradient.scaled(-1.0));
87 }
88 return result;
89 }
90
91 std::vector<DenseMatrix<Complex>> previous_gradients_;
92 std::vector<DenseMatrix<Complex>> direction_;
93};
94
95} // namespace q3
const std::vector< DenseMatrix< Complex > > & next(const std::vector< DenseMatrix< Complex > > &gradients, bool restart)
double Real