SIRIUS 7.5.0
Electronic structure library and applications
anderson_stable_mixer.hpp
Go to the documentation of this file.
1// Copyright (c) 2013-2019 Simon Frasch, Anton Kozhevnikov, Thomas Schulthess
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without modification, are permitted provided that
5// the following conditions are met:
6//
7// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
8// following disclaimer.
9// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
10// and the following disclaimer in the documentation and/or other materials provided with the distribution.
11//
12// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
13// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
14// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
15// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
16// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
17// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
18// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19
20/** \file anderson_stable_mixer.hpp
21 *
22 * \brief Contains definition and implementation sirius::Anderson_stable.
23 */
24
25#ifndef __ANDERSON_STABLE_MIXER_HPP__
26#define __ANDERSON_STABLE_MIXER_HPP__
27
28#include <tuple>
29#include <functional>
30#include <utility>
31#include <vector>
32#include <limits>
33#include <memory>
34#include <exception>
35#include <cmath>
36#include <numeric>
37
38#include "SDDK/memory.hpp"
39#include "mixer/mixer.hpp"
40#include "core/la/linalg.hpp"
41
42namespace sirius {
43namespace mixer {
44
45/// Anderson mixer.
46/**
47 * Quasi-Newton limited-memory method which updates \f$ x_{n+1} = x_n - G_nf_n \f$
48 * where \f$ G_n \f$ is an approximate inverse Jacobian. Anderson is derived
49 * by taking the low-rank update to the inverse Jacobian
50 *
51 * \f[
52 * G_{n+1} = (G_n + \Delta X_n - G_n \Delta F_n)(\Delta F_n^T \Delta F_n)^{-1}\Delta F_n^T
53 * \f]
54 *
55 * such that the secant equations \f$ G_{n+1} \Delta F_n = \Delta X_n \f$ are satisfied for previous
56 * iterations. Then \f$ G_n \f$ is taken \f$ -\beta I \f$. This implementation uses Gram-Schmidt
57 * to orthogonalize \f$ \Delta F_n \f$ to solve the least-squares problem. If stability is
58 * not an issue, use Anderson instead.
59 *
60 * Reference paper: Fang, Haw‐ren, and Yousef Saad. "Two classes of multisecant
61 * methods for nonlinear acceleration." Numerical Linear Algebra with Applications
62 * 16.3 (2009): 197-221.
63 */
64template <typename... FUNCS>
65class Anderson_stable : public Mixer<FUNCS...>
66{
67 private:
68 double beta_;
70 std::size_t history_size_;
71
72 public:
73 Anderson_stable(std::size_t max_history, double beta)
74 : Mixer<FUNCS...>(max_history)
75 , beta_(beta)
76 , R_(max_history - 1, max_history - 1),
77 history_size_(0)
78 {
79 this->R_.zero();
80 }
81
82 void mix_impl() override
83 {
84 const auto idx_step = this->idx_hist(this->step_);
85 const auto idx_next_step = this->idx_hist(this->step_ + 1);
86 const auto idx_step_prev = this->idx_hist(this->step_ - 1);
87
88 const bool normalize = false;
89
90 const auto history_size = static_cast<int>(this->history_size_);
91
92 // TODO: beta scaling?
93
94 // Set up the next x_{n+1} = x_n.
95 // Can't use this->output_history_[idx_step + 1] directly here,
96 // as it's still used when history is full.
97 this->copy(this->output_history_[idx_step], this->input_);
98
99 // + beta * f_n
100 this->axpy(this->beta_, this->residual_history_[idx_step], this->input_);
101
102 if (history_size > 0) {
103 // Compute the difference residual[step] - residual[step - 1]
104 // and store it in residual[step - 1], but don't destroy
105 // residual[step]
106 this->scale(-1.0, this->residual_history_[idx_step_prev]);
107 this->axpy(1.0, this->residual_history_[idx_step], this->residual_history_[idx_step_prev]);
108
109 // Do the same for difference x
110 this->scale(-1.0, this->output_history_[idx_step_prev]);
111 this->axpy(1.0, this->output_history_[idx_step], this->output_history_[idx_step_prev]);
112
113 // orthogonalize residual_history_[step-1] w.r.t. residual_history_[1:step-2] using modified Gram-Schmidt.
114 for (int i = 1; i <= history_size - 1; ++i) {
115 auto j = this->idx_hist(this->step_ - i - 1);
116 auto sz = this->template inner_product<normalize>(
117 this->residual_history_[j],
118 this->residual_history_[idx_step_prev]
119 );
120 this->R_(history_size - 1 - i, history_size - 1) = sz;
121 this->axpy(-sz, this->residual_history_[j], this->residual_history_[idx_step_prev]);
122 }
123
124 // repeat orthogonalization.. seems really necessary.
125 for (int i = 1; i <= history_size - 1; ++i) {
126 auto j = this->idx_hist(this->step_ - i - 1);
127 auto sz = this->template inner_product<normalize>(
128 this->residual_history_[j],
129 this->residual_history_[idx_step_prev]
130 );
131 this->R_(history_size - 1 - i, history_size - 1) += sz;
132 this->axpy(-sz, this->residual_history_[j], this->residual_history_[idx_step_prev]);
133 }
134
135 // normalize the new residual difference vec itself
136 auto nrm2 = this->template inner_product<normalize>(
137 this->residual_history_[idx_step_prev],
138 this->residual_history_[idx_step_prev]
139 );
140
141 if (nrm2 > 0) {
142 auto sz = std::sqrt(nrm2);
143 this->R_(history_size - 1, history_size - 1) = sz;
144 this->scale(1.0 / sz, this->residual_history_[idx_step_prev]);
145
146 // Now do the Anderson iteration bit
147
148 // Compute h = Q' * f_n
149 sddk::mdarray<double, 1> h(history_size);
150 for (int i = 1; i <= history_size; ++i) {
151 auto j = this->idx_hist(this->step_ - i);
152 h(history_size - i) = this->template inner_product<normalize>(this->residual_history_[j], this->residual_history_[idx_step]);
153 }
154
155 // next compute k = R⁻¹ * h... just do that by hand for now, can dispatch to blas later.
156 sddk::mdarray<double, 1> k(history_size);
157 for (int i = 0; i < history_size; ++i) {
158 k[i] = h[i];
159 }
160
161 for (int j = history_size - 1; j >= 0; --j) {
162 k(j) /= this->R_(j, j);
163 for (int i = j - 1; i >= 0; --i) {
164 k(i) -= this->R_(i, j) * k(j);
165 }
166 }
167
168 // - beta * Q * h
169 for (int i = 1; i <= history_size; ++i) {
170 auto j = this->idx_hist(this->step_ - i);
171 this->axpy(-this->beta_ * h(history_size - i), this->residual_history_[j], this->input_);
172 }
173
174 // - (delta X) k
175 for (int i = 1; i <= history_size; ++i) {
176 auto j = this->idx_hist(this->step_ - i);
177 this->axpy(-k(history_size - i), this->output_history_[j], this->input_);
178 }
179 } else {
180 // In the unlikely event of a breakdown when exactly
181 // converged or an inner product that is broken, simply
182 // reset the history size to 0 to restart the mixer.
183 this->history_size_ = 0;
184 }
185 }
186
187 // When the history is full, drop the first column.
188 // Basically we have delta F = [q1 Q2] * [r11 R12; O R22]
189 // and we apply a couple rotations to make [R12; R22] upper triangular again
190 // and simultaneously apply the adjoint of the rotations to [q1 Q2].
191 // afterwards we drop the first column and last row of the new R, the new
192 // Q currently ends up in the first so many columns of delta F, so we have
193 // to do some swapping to restore the circular buffer for residual_history_
194 if (this->history_size_ == this->max_history_ - 1) {
195 // Restore [R12; R22] to upper triangular
196 for (int row = 1; row <= history_size - 1; ++row) {
197 auto rotation = la::wrap(la::lib_t::lapack).lartg(this->R_(row - 1, row), this->R_(row, row));
198 auto c = std::get<0>(rotation);
199 auto s = std::get<1>(rotation);
200 auto nrm = std::get<2>(rotation);
201
202 // Apply the Given's rotation to the initial column
203 this->R_(row - 1, row) = nrm;
204 this->R_(row , row) = 0;
205
206 // Apply Given's rotation to R
207 for (int col = row + 1; col < history_size; ++col) {
208 auto r1 = this->R_(row - 1, col);
209 auto r2 = this->R_(row , col);
210
211 this->R_(row - 1, col) = c * r1 + s * r2;
212 this->R_(row , col) = -s * r1 + c * r2;
213 }
214
215 // Apply the Given's rotation to Q (i.e. orthonormal basis for ΔF)
216 int i1 = this->idx_hist(this->step_ - history_size + row - 1);
217 int i2 = this->idx_hist(this->step_ - history_size + row);
218 this->rotate(c, s, this->residual_history_[i1], this->residual_history_[i2]);
219 }
220
221 // Move the columns one place to the right
222 for (int i = 1; i <= history_size - 1; ++i) {
223 int i1 = this->idx_hist(this->step_ - i - 1);
224 int i2 = this->idx_hist(this->step_ - i);
225 std::swap(this->residual_history_[i2], this->residual_history_[i1]);
226 }
227
228 // Delete last row and first column of R.
229 for (int col = 0; col <= history_size - 2; ++col) {
230 for (int row = 0; row <= col; ++row) {
231 this->R_(row, col) = this->R_(row, col + 1);
232 }
233 }
234 }
235
236 this->copy(this->input_, this->output_history_[idx_next_step]);
237 this->history_size_ = std::min(this->history_size_ + 1, this->max_history_ - 1);
238 }
239};
240} // namespace mixer
241} // namespace sirius
242
243#endif // __ANDERSON_STABLE_MIXER_HPP__
Abstract mixer for variadic number of Function objects, which are described by FunctionProperties.
Definition: mixer.hpp:279
void zero(memory_t mem__, size_t idx0__, size_t n__)
Zero n elements starting from idx0.
Definition: memory.hpp:1316
Linear algebra interface.
Memory management functions and classes.
Contains definition and implementation of sirius::Mixer base class.
@ lapack
CPU LAPACK.
Namespace of the SIRIUS library.
Definition: sirius.f90:5