SIRIUS 7.5.0
Electronic structure library and applications
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 mixer.hpp
21 *
22 * \brief Contains definition and implementation of sirius::Mixer base class.
23 */
24
25#ifndef __MIXER_HPP__
26#define __MIXER_HPP__
27
28#include <tuple>
29#include <functional>
30#include <utility>
31#include <vector>
32#include <limits>
33#include <memory>
34#include <stdexcept>
35#include <cmath>
36#include <numeric>
37
38namespace sirius {
39
40/// Mixer functions and objects.
41namespace mixer {
42
43/// Describes operations on a function type used for mixing.
44/** The properties contain functions, which determine the behaviour of a given type during mixing. The inner product
45 * function result is used for calculating mixing parameters. If a function should not contribute to generation of
46 * mixing parameters, the inner product function should always return 0.
47 */
48template <typename FUNC>
50{
51 using type = FUNC;
52
53 ///
54 /**
55 * \param [in] size_ Function, which returns a measure of size of the (global) function.
56 * \param [in] inner_ Function, which computes the (global) inner product. This determines the contribution to mixing parameters rmse.
57 * \param [in] scal_ Function, which scales the input (x = alpha * x).
58 * \param [in] copy_ Function, which copies from one object to the other (y = x).
59 * \param [in] axpy_ Function, which scales and adds one object to the other (y = alpha * x + y).
60 */
61 FunctionProperties(std::function<double(const FUNC&)> size_,
62 std::function<double(const FUNC&, const FUNC&)> inner_,
63 std::function<void(double, FUNC&)> scal_,
64 std::function<void(const FUNC&, FUNC&)> copy_,
65 std::function<void(double, const FUNC&, FUNC&)> axpy_,
66 std::function<void(double, double, FUNC&, FUNC&)> rotate_)
67 : size(size_)
68 , inner(inner_)
69 , scal(scal_)
70 , copy(copy_)
71 , axpy(axpy_)
72 , rotate(rotate_)
73 {
74 }
75
77 : size([](const FUNC&) -> double { return 0; })
78 , inner([](const FUNC&, const FUNC&) -> double { return 0.0; })
79 , scal([](double, FUNC&) -> void {})
80 , copy([](const FUNC&, FUNC&) -> void {})
81 , axpy([](double, const FUNC&, FUNC&) -> void {})
82 , rotate([](double, double, FUNC&, FUNC&) -> void {})
83 {
84 }
85
86 // Size proportional to the local contribution of the inner product.
87 std::function<double(const FUNC&)> size; // TODO: this sounds more like a normalization factor.
88
89 // Inner product function. Determines contribution to mixing.
90 std::function<double(const FUNC&, const FUNC&)> inner;
91
92 // scaling. x = alpha * x
93 std::function<void(double, FUNC&)> scal;
94
95 // copy function. y = x
96 std::function<void(const FUNC&, FUNC&)> copy;
97
98 // axpy function. y = alpha * x + y
99 std::function<void(double, const FUNC&, FUNC&)> axpy;
100
101 // rotate function [x y] * [c -s; s c]
102 std::function<void(double, double, FUNC&, FUNC&)> rotate;
103};
104
105// Implementation of templated recursive calls through tuples
106namespace mixer_impl {
107
108/// Compute inner product <x|y> between pairs of functions in tuples and accumulate in the result.
109/** This function is used in Broyden mixers to compute inner products of residuals. */
110template <std::size_t FUNC_REVERSE_INDEX, bool normalize, typename... FUNCS>
112{
113 static double apply(const std::tuple<FunctionProperties<FUNCS>...>& function_prop,
114 const std::tuple<std::unique_ptr<FUNCS>...>& x, const std::tuple<std::unique_ptr<FUNCS>...>& y)
115 {
116 double result = 0.0;
117 if (std::get<FUNC_REVERSE_INDEX>(x) && std::get<FUNC_REVERSE_INDEX>(y)) {
118 /* compute inner product */
119 auto v = std::get<FUNC_REVERSE_INDEX>(function_prop)
120 .inner(*std::get<FUNC_REVERSE_INDEX>(x), *std::get<FUNC_REVERSE_INDEX>(y));
121 /* normalize if necessary */
122 if (normalize) {
123 auto sx = std::get<FUNC_REVERSE_INDEX>(function_prop).size(*std::get<FUNC_REVERSE_INDEX>(x));
124 auto sy = std::get<FUNC_REVERSE_INDEX>(function_prop).size(*std::get<FUNC_REVERSE_INDEX>(y));
125 if (sx != sy) {
126 throw std::runtime_error("[sirius::mixer::InnerProduct] sizes of two functions don't match");
127 }
128 if (sx) {
129 v /= sx;
130 } else {
131 v = 0;
132 }
133 }
134
135 result += v;
136 }
137 return result + InnerProduct<FUNC_REVERSE_INDEX - 1, normalize, FUNCS...>::apply(function_prop, x, y);
138 }
139};
140
141template <bool normalize, typename... FUNCS>
142struct InnerProduct<0, normalize, FUNCS...>
143{
144 static double apply(const std::tuple<FunctionProperties<FUNCS>...>& function_prop,
145 const std::tuple<std::unique_ptr<FUNCS>...>& x, const std::tuple<std::unique_ptr<FUNCS>...>& y)
146 {
147 if (std::get<0>(x) && std::get<0>(y)) {
148 auto v = std::get<0>(function_prop).inner(*std::get<0>(x), *std::get<0>(y));
149 if (normalize) {
150 auto sx = std::get<0>(function_prop).size(*std::get<0>(x));
151 auto sy = std::get<0>(function_prop).size(*std::get<0>(y));
152 if (sx != sy) {
153 throw std::runtime_error("[sirius::mixer::InnerProduct] sizes of two functions don't match");
154 }
155 if (sx) {
156 v /= sx;
157 } else {
158 v = 0;
159 }
160 }
161 return v;
162 } else {
163 return 0;
164 }
165 }
166};
167
168template <std::size_t FUNC_REVERSE_INDEX, typename... FUNCS>
170{
171 static void apply(const std::tuple<FunctionProperties<FUNCS>...>& function_prop, double alpha,
172 std::tuple<std::unique_ptr<FUNCS>...>& x)
173 {
174 if (std::get<FUNC_REVERSE_INDEX>(x)) {
175 std::get<FUNC_REVERSE_INDEX>(function_prop).scal(alpha, *std::get<FUNC_REVERSE_INDEX>(x));
176 }
178 }
179};
180
181template <typename... FUNCS>
182struct Scaling<0, FUNCS...>
183{
184 static void apply(const std::tuple<FunctionProperties<FUNCS>...>& function_prop, double alpha,
185 std::tuple<std::unique_ptr<FUNCS>...>& x)
186 {
187 if (std::get<0>(x)) {
188 std::get<0>(function_prop).scal(alpha, *std::get<0>(x));
189 }
190 }
191};
192
193template <std::size_t FUNC_REVERSE_INDEX, typename... FUNCS>
194struct Copy
195{
196 static void apply(const std::tuple<FunctionProperties<FUNCS>...>& function_prop,
197 const std::tuple<std::unique_ptr<FUNCS>...>& x, std::tuple<std::unique_ptr<FUNCS>...>& y)
198 {
199 if (std::get<FUNC_REVERSE_INDEX>(x) && std::get<FUNC_REVERSE_INDEX>(y)) {
200 std::get<FUNC_REVERSE_INDEX>(function_prop)
201 .copy(*std::get<FUNC_REVERSE_INDEX>(x), *std::get<FUNC_REVERSE_INDEX>(y));
202 }
204 }
205};
206
207template <typename... FUNCS>
208struct Copy<0, FUNCS...>
209{
210 static void apply(const std::tuple<FunctionProperties<FUNCS>...>& function_prop,
211 const std::tuple<std::unique_ptr<FUNCS>...>& x, std::tuple<std::unique_ptr<FUNCS>...>& y)
212 {
213 if (std::get<0>(x) && std::get<0>(y)) {
214 std::get<0>(function_prop).copy(*std::get<0>(x), *std::get<0>(y));
215 }
216 }
217};
218
219template <std::size_t FUNC_REVERSE_INDEX, typename... FUNCS>
220struct Axpy
221{
222 static void apply(const std::tuple<FunctionProperties<FUNCS>...>& function_prop, double alpha,
223 const std::tuple<std::unique_ptr<FUNCS>...>& x, std::tuple<std::unique_ptr<FUNCS>...>& y)
224 {
225 if (std::get<FUNC_REVERSE_INDEX>(x) && std::get<FUNC_REVERSE_INDEX>(y)) {
226 std::get<FUNC_REVERSE_INDEX>(function_prop)
227 .axpy(alpha, *std::get<FUNC_REVERSE_INDEX>(x), *std::get<FUNC_REVERSE_INDEX>(y));
228 }
229 Axpy<FUNC_REVERSE_INDEX - 1, FUNCS...>::apply(function_prop, alpha, x, y);
230 }
231};
232
233template <typename... FUNCS>
234struct Axpy<0, FUNCS...>
235{
236 static void apply(const std::tuple<FunctionProperties<FUNCS>...>& function_prop, double alpha,
237 const std::tuple<std::unique_ptr<FUNCS>...>& x, std::tuple<std::unique_ptr<FUNCS>...>& y)
238 {
239 if (std::get<0>(x) && std::get<0>(y)) {
240 std::get<0>(function_prop).axpy(alpha, *std::get<0>(x), *std::get<0>(y));
241 }
242 }
243};
244
245template <std::size_t FUNC_REVERSE_INDEX, typename... FUNCS>
246struct Rotate
247{
248 static void apply(const std::tuple<FunctionProperties<FUNCS>...>& function_prop, double c, double s,
249 std::tuple<std::unique_ptr<FUNCS>...>& x, std::tuple<std::unique_ptr<FUNCS>...>& y)
250 {
251 if (std::get<FUNC_REVERSE_INDEX>(x) && std::get<FUNC_REVERSE_INDEX>(y)) {
252 std::get<FUNC_REVERSE_INDEX>(function_prop)
253 .rotate(c, s, *std::get<FUNC_REVERSE_INDEX>(x), *std::get<FUNC_REVERSE_INDEX>(y));
254 }
255 Rotate<FUNC_REVERSE_INDEX - 1, FUNCS...>::apply(function_prop, c, s, x, y);
256 }
257};
258
259template <typename... FUNCS>
260struct Rotate<0, FUNCS...>
261{
262 static void apply(const std::tuple<FunctionProperties<FUNCS>...>& function_prop, double c, double s,
263 std::tuple<std::unique_ptr<FUNCS>...>& x, std::tuple<std::unique_ptr<FUNCS>...>& y)
264 {
265 if (std::get<0>(x) && std::get<0>(y)) {
266 std::get<0>(function_prop).rotate(c, s, *std::get<0>(x), *std::get<0>(y));
267 }
268 }
269};
270
271} // namespace mixer_impl
272
273/// Abstract mixer for variadic number of Function objects, which are described by FunctionProperties.
274/** Can mix variadic number of functions objects, for which operations are defined in FunctionProperties. Only
275 * functions, which are explicitly initialized, are mixed.
276 */
277template <typename... FUNCS>
278class Mixer
279{
280 public:
281 static_assert(sizeof...(FUNCS) > 0, "At least one function type must be provided");
282
283 static constexpr std::size_t number_of_functions = sizeof...(FUNCS);
284
285 /// Construct a mixer. Functions have to initialized individually.
286 /** \param [in] max_history Maximum number of steps stored, which contribute to the mixing.
287 * \param [in] commm Communicator used for exchaning mixing contributions.
288 */
289 Mixer(std::size_t max_history)
290 : step_(0)
291 , max_history_(max_history)
292 , rmse_history_(max_history)
293 , output_history_(max_history)
294 , residual_history_(max_history)
295 {
296 }
297
298 virtual ~Mixer() = default;
299
300 /// Initialize function at given index with given value. A new function object is created with "args" passed to the
301 /// constructor. Only initialized functions are mixed.
302 /** \param [in] function_prop Function properties, which describe operations.
303 * \param [in] init_value Initial function value for input / output.
304 * \param [in] args Arguments, which are passed to the constructor of function placeholder objects.
305 */
306 template <std::size_t FUNC_INDEX, typename... ARGS>
308 const FunctionProperties<typename std::tuple_element<FUNC_INDEX, std::tuple<FUNCS...>>::type>& function_prop,
309 const typename std::tuple_element<FUNC_INDEX, std::tuple<FUNCS...>>::type& init_value, ARGS&&... args)
310 {
311 if (step_ > 0) {
312 throw std::runtime_error("Initializing function_prop after mixing not allowed!");
313 }
314
315 std::get<FUNC_INDEX>(functions_) = function_prop;
316
317 // NOTE: don't use std::forward for args, because we need them multiple times (don't forward
318 // r-value references)
319
320 // create function object placeholders with arguments provided
321 std::get<FUNC_INDEX>(input_).reset(
322 new typename std::tuple_element<FUNC_INDEX, std::tuple<FUNCS...>>::type(args...));
323
324 for (std::size_t i = 0; i < max_history_; ++i) {
325 std::get<FUNC_INDEX>(output_history_[i])
326 .reset(new typename std::tuple_element<FUNC_INDEX, std::tuple<FUNCS...>>::type(args...));
327 std::get<FUNC_INDEX>(residual_history_[i])
328 .reset(new typename std::tuple_element<FUNC_INDEX, std::tuple<FUNCS...>>::type(args...));
329 }
330
331 // initialize output and input with given initial value
332 std::get<FUNC_INDEX>(functions_).copy(init_value, *std::get<FUNC_INDEX>(output_history_[0]));
333 std::get<FUNC_INDEX>(functions_).copy(init_value, *std::get<FUNC_INDEX>(input_));
334 }
335
336 /// Set input for next mixing step
337 /** \param [in] input Input functions, for which a copy operation is invoked.
338 */
339 template <std::size_t FUNC_INDEX>
340 void set_input(const typename std::tuple_element<FUNC_INDEX, std::tuple<FUNCS...>>::type& input)
341 {
342 if (std::get<FUNC_INDEX>(input_)) {
343 std::get<FUNC_INDEX>(functions_).copy(input, *std::get<FUNC_INDEX>(input_));
344 } else {
345 throw std::runtime_error("Mixer function not initialized!");
346 }
347 }
348
349 /// Access last generated output. Mixing must have been performed at least once.
350 /** \param [out] output Output function, into which the mixer output is copied.
351 */
352 template <std::size_t FUNC_INDEX>
353 void get_output(typename std::tuple_element<FUNC_INDEX, std::tuple<FUNCS...>>::type& output)
354 {
355 const auto idx = idx_hist(step_);
356 if (!std::get<FUNC_INDEX>(output_history_[idx])) {
357 throw std::runtime_error("Mixer function not initialized!");
358 }
359 std::get<FUNC_INDEX>(functions_).copy(*std::get<FUNC_INDEX>(output_history_[idx]), output);
360 }
361
362 /// Mix input and stored history. Returns the root mean square error computed by inner products of residuals.
363 /** \param [in] rms_min Minimum root mean square error. Mixing is only performed, if current RMS is above this
364 * threshold.
365 */
366 double mix(double rms_min__)
367 {
368 this->update_residual();
369 this->update_rms();
370 double rmse = rmse_history_[idx_hist(step_)];
371 if (rmse < rms_min__) {
372 return rmse;
373 }
374
375 /* call mixing implementation */
376 this->mix_impl();
377
378 ++step_;
379 return rmse;
380 }
381
382 protected:
383 // Mixing implementation
384 virtual void mix_impl() = 0;
385
386 // update residual history for current step
387 void update_residual()
388 {
389 this->copy(input_, residual_history_[idx_hist(step_)]);
390 this->axpy(-1.0, output_history_[idx_hist(step_)], residual_history_[idx_hist(step_)]);
391 }
392
393 // update rmse histroy for current step. Residuals must have been updated before.
394 void update_rms()
395 {
396 const auto idx = idx_hist(step_);
397
398 /* compute sum of inner products; each inner product is normalized */
399 double rmse = inner_product<true>(residual_history_[idx], residual_history_[idx]);
400
401 rmse_history_[idx_hist(step_)] = std::sqrt(rmse);
402 }
403
404 // Storage index of given step
405 std::size_t idx_hist(std::size_t step) const
406 {
407 return step % max_history_;
408 }
409
410 template <bool normalize>
411 double inner_product(const std::tuple<std::unique_ptr<FUNCS>...>& x,
412 const std::tuple<std::unique_ptr<FUNCS>...>& y)
413 {
414 return mixer_impl::InnerProduct<sizeof...(FUNCS) - 1, normalize, FUNCS...>::apply(functions_, x, y);
415 }
416
417 void scale(double alpha, std::tuple<std::unique_ptr<FUNCS>...>& x)
418 {
419 mixer_impl::Scaling<sizeof...(FUNCS) - 1, FUNCS...>::apply(functions_, alpha, x);
420 }
421
422 void copy(const std::tuple<std::unique_ptr<FUNCS>...>& x, std::tuple<std::unique_ptr<FUNCS>...>& y)
423 {
424 mixer_impl::Copy<sizeof...(FUNCS) - 1, FUNCS...>::apply(functions_, x, y);
425 }
426
427 void axpy(double alpha, const std::tuple<std::unique_ptr<FUNCS>...>& x, std::tuple<std::unique_ptr<FUNCS>...>& y)
428 {
429 mixer_impl::Axpy<sizeof...(FUNCS) - 1, FUNCS...>::apply(functions_, alpha, x, y);
430 }
431
432 void rotate(double c, double s, std::tuple<std::unique_ptr<FUNCS>...>& x, std::tuple<std::unique_ptr<FUNCS>...>& y)
433 {
434 mixer_impl::Rotate<sizeof...(FUNCS) - 1, FUNCS...>::apply(functions_, c, s, x, y);
435 }
436
437 // Strictly increasing counter, indicating the number of mixing steps
438 std::size_t step_;
439
440 // The maximum history size kept for each function
441 std::size_t max_history_;
442
443 // Properties, describing the each function type
444 std::vector<double> rmse_history_;
445
446 // Properties, describing the each function type
447 std::tuple<FunctionProperties<FUNCS>...> functions_;
448
449 // Input storage for next mixing step
450 std::tuple<std::unique_ptr<FUNCS>...> input_;
451
452 // The history of generated mixer outputs. The last generated output is at step_.
453 std::vector<std::tuple<std::unique_ptr<FUNCS>...>> output_history_;
454
455 // The residual history between input and output
456 std::vector<std::tuple<std::unique_ptr<FUNCS>...>> residual_history_;
457};
458} // namespace mixer
459} // namespace sirius
460
461#endif // __MIXER_HPP__
Abstract mixer for variadic number of Function objects, which are described by FunctionProperties.
Definition: mixer.hpp:279
void initialize_function(const FunctionProperties< typename std::tuple_element< FUNC_INDEX, std::tuple< FUNCS... > >::type > &function_prop, const typename std::tuple_element< FUNC_INDEX, std::tuple< FUNCS... > >::type &init_value, ARGS &&... args)
Definition: mixer.hpp:307
Mixer(std::size_t max_history)
Construct a mixer. Functions have to initialized individually.
Definition: mixer.hpp:289
void set_input(const typename std::tuple_element< FUNC_INDEX, std::tuple< FUNCS... > >::type &input)
Set input for next mixing step.
Definition: mixer.hpp:340
double mix(double rms_min__)
Mix input and stored history. Returns the root mean square error computed by inner products of residu...
Definition: mixer.hpp:366
void get_output(typename std::tuple_element< FUNC_INDEX, std::tuple< FUNCS... > >::type &output)
Access last generated output. Mixing must have been performed at least once.
Definition: mixer.hpp:353
void copy(T *target__, T const *source__, size_t n__)
Copy memory inside a device.
Definition: acc.hpp:320
std::enable_if_t< std::is_same< T, real_type< F > >::value, void > inner(::spla::Context &spla_ctx__, sddk::memory_t mem__, spin_range spins__, W const &wf_i__, band_range br_i__, Wave_functions< T > const &wf_j__, band_range br_j__, la::dmatrix< F > &result__, int irow0__, int jcol0__)
Compute inner product between the two sets of wave-functions.
Namespace of the SIRIUS library.
Definition: sirius.f90:5
Describes operations on a function type used for mixing.
Definition: mixer.hpp:50
FunctionProperties(std::function< double(const FUNC &)> size_, std::function< double(const FUNC &, const FUNC &)> inner_, std::function< void(double, FUNC &)> scal_, std::function< void(const FUNC &, FUNC &)> copy_, std::function< void(double, const FUNC &, FUNC &)> axpy_, std::function< void(double, double, FUNC &, FUNC &)> rotate_)
Definition: mixer.hpp:61
Compute inner product <x|y> between pairs of functions in tuples and accumulate in the result.
Definition: mixer.hpp:112