Loading...
Searching...
No Matches
anderson.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * Anderson (Pulay) density mixer in real space.
5 *
6 * Implements the residual-minimization variant of Pulay/Anderson mixing
7 * with configurable history depth. Falls back to linear mixing when the
8 * LAPACK least-squares solve fails or when only one history entry exists.
9 */
10
11#include <algorithm>
12#include <cmath>
13
14#include "io/logger.hpp"
16
18
19namespace q3 {
20using namespace q3::lapack;
21
22/**
23 * Anderson (Pulay) mixing with configurable history depth (real-space).
24 * Uses the residual minimization approach for faster SCF convergence.
25 */
27 public:
30
31 std::vector<Real> mix_one_component(const std::vector<Real>& input_density,
32 const std::vector<Real>& output_density) override {
33 int number_of_points = static_cast<int>(input_density.size());
34
35 // Compute residual
36 std::vector<Real> residual(number_of_points);
37 for (int point_index = 0; point_index < number_of_points; point_index++) {
38 residual[point_index] = output_density[point_index] - input_density[point_index];
39 }
40
41 if (preconditioner_) {
42 preconditioner_(residual);
43 }
44
45 // Store in history
46 density_history_.push_back(input_density);
47 residual_history_.push_back(residual);
48
49 // Trim to max history depth
50 while (static_cast<int>(density_history_.size()) > history_depth_) {
51 density_history_.erase(density_history_.begin());
52 residual_history_.erase(residual_history_.begin());
53 }
54
55 int number_of_history_entries = static_cast<int>(density_history_.size());
56
57 // If only one entry, fall back to linear mixing
58 if (number_of_history_entries <= 1) {
59 std::vector<Real> mixed_density(number_of_points);
60 for (int point_index = 0; point_index < number_of_points; point_index++) {
61 mixed_density[point_index] =
62 input_density[point_index] + mixing_parameter_ * residual[point_index];
63 }
64 return mixed_density;
65 }
66
67 /**
68 * Build overlap matrix A[i,j] = <R_i - R_last | R_j - R_last>
69 * and RHS b[i] = <R_i - R_last | R_last>
70 *
71 * All dot products are accumulated locally first, then reduced together
72 * in a single MPI collective call to avoid O(history^2) synchronizations.
73 */
74 int number_of_differences = number_of_history_entries - 1;
75 std::vector<Real> overlap_matrix(number_of_differences * number_of_differences,
76 0.0); // column-major
77 std::vector<Real> right_hand_side(number_of_differences, 0.0);
78
79 const auto& residual_last = residual_history_.back();
80
81 // Pre-compute delta residuals for all history entries.
82 std::vector<std::vector<Real>> delta_residuals(number_of_differences,
83 std::vector<Real>(number_of_points));
84 for (int difference_index = 0; difference_index < number_of_differences;
85 difference_index++) {
86 for (int point_index = 0; point_index < number_of_points; point_index++) {
87 delta_residuals[difference_index][point_index] =
88 residual_history_[difference_index][point_index] - residual_last[point_index];
89 }
90 }
91
92 // Accumulate all dot products locally (no MPI communication yet).
93 for (int difference_index = 0; difference_index < number_of_differences;
94 difference_index++) {
95 const auto& delta_residual_i = delta_residuals[difference_index];
96
97 Real rhs_dot = 0.0;
98 for (int point_index = 0; point_index < number_of_points; point_index++) {
99 rhs_dot -= delta_residual_i[point_index] * residual_last[point_index];
100 }
101 right_hand_side[difference_index] = rhs_dot;
102
103 for (int column_index = 0; column_index < number_of_differences; column_index++) {
104 Real dot = 0.0;
105 const auto& delta_residual_j = delta_residuals[column_index];
106 for (int point_index = 0; point_index < number_of_points; point_index++) {
107 dot += delta_residual_i[point_index] * delta_residual_j[point_index];
108 }
109 overlap_matrix[difference_index + column_index * number_of_differences] =
110 dot; // column-major
111 }
112 }
113
114 /**
115 * Single batched MPI reduction: pack rhs and upper triangle of overlap into one vector,
116 * reduce once, then unpack. This is exactly one MPI_Allreduce regardless of history depth.
117 */
118 if (global_reduce_) {
119 // Pack: [rhs[0..n-1], overlap[0,0], overlap[1,0], overlap[0,1], ...]
120 int overlap_size = number_of_differences * number_of_differences;
121 std::vector<Real> reduce_buffer(number_of_differences + overlap_size);
122 for (int i = 0; i < number_of_differences; i++) {
123 reduce_buffer[i] = right_hand_side[i];
124 }
125 for (int i = 0; i < overlap_size; i++) {
126 reduce_buffer[number_of_differences + i] = overlap_matrix[i];
127 }
128 global_reduce_(reduce_buffer);
129 // Unpack
130 for (int i = 0; i < number_of_differences; i++) {
131 right_hand_side[i] = reduce_buffer[i];
132 }
133 for (int i = 0; i < overlap_size; i++) {
134 overlap_matrix[i] = reduce_buffer[number_of_differences + i];
135 }
136 }
137
138 // Solve A * gamma = b via LAPACK least-squares
139 int number_of_right_hand_sides = 1;
140 int lapack_workspace_size = 4 * number_of_differences;
141 std::vector<Real> lapack_workspace(lapack_workspace_size);
142 int info;
143 dgels_("N",
144 &number_of_differences,
145 &number_of_differences,
146 &number_of_right_hand_sides,
147 overlap_matrix.data(),
148 &number_of_differences,
149 right_hand_side.data(),
150 &number_of_differences,
151 lapack_workspace.data(),
152 &lapack_workspace_size,
153 &info);
154
155 if (info != 0) {
156 log::warn("Anderson mixing: LAPACK dgels failed (info={}), falling back to linear",
157 info);
158 std::vector<Real> mixed_density(number_of_points);
159 for (int point_index = 0; point_index < number_of_points; point_index++) {
160 mixed_density[point_index] =
161 input_density[point_index] + mixing_parameter_ * residual[point_index];
162 }
163 return mixed_density;
164 }
165
166 // right_hand_side now contains the solution gamma
167
168 // Construct optimal input: rho_opt = rho_last + Σ gamma_i (rho_i - rho_last)
169
170 // Construct optimal residual: res_opt = res_last + Σ gamma_i (res_i - res_last)
171 std::vector<Real> density_optimal(number_of_points), residual_optimal(number_of_points);
172 const auto& density_last = density_history_.back();
173
174 for (int point_index = 0; point_index < number_of_points; point_index++) {
175 density_optimal[point_index] = density_last[point_index];
176 residual_optimal[point_index] = residual_last[point_index];
177 }
178
179 for (int difference_index = 0; difference_index < number_of_differences;
180 difference_index++) {
181 Real gamma_i = right_hand_side[difference_index];
182 for (int point_index = 0; point_index < number_of_points; point_index++) {
183 density_optimal[point_index] +=
184 gamma_i *
185 (density_history_[difference_index][point_index] - density_last[point_index]);
186 residual_optimal[point_index] +=
187 gamma_i *
188 (residual_history_[difference_index][point_index] - residual_last[point_index]);
189 }
190 }
191
192 // Final mixed density: rho_new = rho_opt + beta * res_opt
193 std::vector<Real> mixed_density(number_of_points);
194 for (int point_index = 0; point_index < number_of_points; point_index++) {
195 mixed_density[point_index] =
196 density_optimal[point_index] + mixing_parameter_ * residual_optimal[point_index];
197 }
198
199 return mixed_density;
200 }
201
202 void reset() override {
203 density_history_.clear();
204 residual_history_.clear();
205 }
206
207 [[nodiscard]] std::string name() const override {
208 return "anderson";
209 }
210
211 private:
212 // History buffers
213 std::vector<std::vector<Real>> density_history_;
214 std::vector<std::vector<Real>> residual_history_;
215};
216
217} // namespace q3
AndersonMixer(Real mixing_parameter=0.7, int history_depth=8)
Definition anderson.hpp:28
std::string name() const override
Definition anderson.hpp:207
void reset() override
Definition anderson.hpp:202
std::vector< Real > mix_one_component(const std::vector< Real > &input_density, const std::vector< Real > &output_density) override
Definition anderson.hpp:31
int history_depth_
Definition init.hpp:93
int history_depth() const
Definition init.hpp:76
Real mixing_parameter_
Definition init.hpp:92
Real mixing_parameter() const
Definition init.hpp:73
Mixer operating on real-space charge density vectors.
Definition init.hpp:18
std::function< void(std::vector< Real > &)> global_reduce_
Definition init.hpp:54
Preconditioner preconditioner_
Definition init.hpp:53
void dgels_(const char *trans, const int *m, const int *n, const int *nrhs, double *a, const int *lda, double *b, const int *ldb, double *work, const int *lwork, int *info)
void warn(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:187
double Real