Loading...
Searching...
No Matches
init.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * ReciprocalSpaceMixer — base class for mixers operating in G-space.
5 *
6 * Transparently wraps the real-space mix() interface: densities are FFT'd
7 * to G-space, mixed via the virtual mix_in_reciprocal_space() hook, then
8 * transformed back. Concrete implementation: BroydenMixer.
9 */
10
11#include <memory>
12#include <utility>
13#include <vector>
14
16
17namespace q3 {
18
19/**
20 * Mixer operating on G-space charge density vectors with a precomputed metric.
21 *
22 * The unified real-space mix() transforms densities to reciprocal space, defers
23 * to the concrete mix_in_reciprocal_space() update, and transforms back, so the
24 * caller never has to know the mixer works in G-space.
25 */
27 public:
28 using Mixer::Mixer;
29
40
41 void mix(const std::vector<std::vector<Real>>& input_density,
42 std::vector<std::vector<Real>>& output_density,
43 int number_of_spin_components) override {
44 /**
45 * A density-dependent preconditioner needs the TOTAL real-space density,
46 * and it needs it here: mix() receives (ρ↑, ρ↓), and the rotation to
47 * (total, magnetization) happens further down in G-space, so the sum
48 * over spin channels has to be done explicitly before the transforms.
49 */
50 if (preconditioner_ != nullptr) {
51 preconditioner_->update(
52 sum_over_spin_channels(input_density, number_of_spin_components));
53 }
54
55 std::vector<std::vector<Complex>> input_density_g(number_of_spin_components);
56 std::vector<std::vector<Complex>> output_density_g(number_of_spin_components);
57 for (int spin_index = 0; spin_index < number_of_spin_components; ++spin_index) {
58 to_reciprocal_space_(input_density[spin_index], input_density_g[spin_index]);
59 to_reciprocal_space_(output_density[spin_index], output_density_g[spin_index]);
60 }
61
62 /**
63 * Spin-polarized densities are mixed in the (total, magnetization)
64 * representation, the one that diagonalizes the mixing metric: channel
65 * 0 = ρ↑+ρ↓ carries the whole Coulomb response, so it gets the 1/G²
66 * metric and Kerker preconditioning; channel 1 = ρ↑−ρ↓ has no Coulomb
67 * kernel, so it gets the flat metric and no preconditioning. The
68 * per-channel metric code in the concrete mixers assumes this layout.
69 */
70 if (number_of_spin_components == 2) {
73 }
74
75 mix_in_reciprocal_space(input_density_g, output_density_g, number_of_spin_components);
76
77 /**
78 * For dual-grid (ultrasoft/PAW) calculations, the hard G-vectors (above the smooth
79 * cutoff) carry the augmentation charges. They sit outside the Broyden subspace, so
80 * they need an update of their own; plain linear mixing at the configured
81 * mixing_beta is the cheapest stable one. Q3 was replacing them at 100% (beta=1.0),
82 * which caused augmentation oscillations and Broyden divergence.
83 * Fix: linear-mix hard G with the same mixing_parameter_ used for the smooth region.
84 */
86 for (int spin_index = 0; spin_index < number_of_spin_components; ++spin_index) {
87 int number_of_g_vectors = static_cast<int>(input_density_g[spin_index].size());
88 for (int g_index = number_of_smooth_g_vectors_; g_index < number_of_g_vectors;
89 ++g_index) {
90 input_density_g[spin_index][g_index] +=
91 mixing_parameter_ * (output_density_g[spin_index][g_index] -
92 input_density_g[spin_index][g_index]);
93 }
94 }
95 }
96
97 if (number_of_spin_components == 2) {
99 }
100
101 for (int spin_index = 0; spin_index < number_of_spin_components; ++spin_index) {
102 to_real_space_(input_density_g[spin_index], output_density[spin_index]);
103 }
104 }
105
106 /// Total real-space density ρ↑+ρ↓ (a copy of channel 0 when unpolarized).
107 static std::vector<Real>
108 sum_over_spin_channels(const std::vector<std::vector<Real>>& density_real,
109 int number_of_spin_components) {
110 std::vector<Real> total = density_real[0];
111 for (int spin_index = 1; spin_index < number_of_spin_components; ++spin_index) {
112 for (std::size_t index = 0; index < total.size(); ++index) {
113 total[index] += density_real[spin_index][index];
114 }
115 }
116 return total;
117 }
118
119 /// In-place (ρ↑, ρ↓) → (ρ↑+ρ↓, ρ↑−ρ↓).
120 static void
121 convert_up_down_to_total_magnetization(std::vector<std::vector<Complex>>& density_g) {
122 for (std::size_t g_index = 0; g_index < density_g[0].size(); ++g_index) {
123 Complex up = density_g[0][g_index];
124 Complex down = density_g[1][g_index];
125 density_g[0][g_index] = up + down;
126 density_g[1][g_index] = up - down;
127 }
128 }
129
130 /// In-place (ρ↑+ρ↓, ρ↑−ρ↓) → (ρ↑, ρ↓).
131 static void
132 convert_total_magnetization_to_up_down(std::vector<std::vector<Complex>>& density_g) {
133 for (std::size_t g_index = 0; g_index < density_g[0].size(); ++g_index) {
134 Complex total = density_g[0][g_index];
135 Complex magnetization = density_g[1][g_index];
136 density_g[0][g_index] = 0.5 * (total + magnetization);
137 density_g[1][g_index] = 0.5 * (total - magnetization);
138 }
139 }
140
141 /**
142 * Perform one mixing step in G-space.
143 * @param input_density_g input density in G-space (modified in-place with the mixed result).
144 * @param output_density_g output density in G-space.
145 * @param number_of_spin_components number of spin components.
146 */
147 virtual void mix_in_reciprocal_space(std::vector<std::vector<Complex>>& input_density_g,
148 const std::vector<std::vector<Complex>>& output_density_g,
149 int number_of_spin_components) = 0;
150
151 protected:
152 /**
153 * Weighted inner product ⟨ρ_a|ρ_b⟩ in the two-channel mixing metric -- the same
154 * metric the SCF reports its convergence estimate in, so that a conv_thr value
155 * carried in from a plane-wave input means the same thing here as there:
156 *
157 * channel 0 (total charge): Σ_{G≠0} w_charge(G) ρ_a*(G) ρ_b(G),
158 * w_charge = Hartree kernel ∝ 1/G²
159 * channel 1 (magnetization): Σ_{all G, incl. G=0} w_m ρ_a*(G) ρ_b(G),
160 * w_m = flat metric (no Coulomb kernel)
161 *
162 * On gamma-only half-sphere grids every G≠0 term is doubled (ρ(-G) = ρ*(G));
163 * the G=0 magnetization term is stored once and is not doubled.
164 *
165 * @param g_vector_count Limit the G range (e.g. smooth-only mixing);
166 * pass a negative value to use the full vectors.
167 */
168 Real compute_metric_dot_product(const std::vector<std::vector<Complex>>& rho_a,
169 const std::vector<std::vector<Complex>>& rho_b,
170 int number_of_spin_components,
171 int g_vector_count = -1) const {
172 int g_count = (g_vector_count >= 0) ? g_vector_count : static_cast<int>(rho_a[0].size());
173 Real gamma_factor = is_gamma_only_ ? 2.0 : 1.0;
174 Real result = 0.0;
175
176 /**
177 * Total charge channel: Hartree 1/G² metric, G=0 excluded (fixed by
178 * charge conservation, and the kernel diverges there).
179 */
180 Real charge_result = 0.0;
181 for (int g_index = first_nonzero_g_index_; g_index < g_count; g_index++) {
182 charge_result += charge_metric_weights_[g_index] *
183 std::real(std::conj(rho_a[0][g_index]) * rho_b[0][g_index]);
184 }
185 result += charge_result * gamma_factor;
186
187 /**
188 * Magnetization channels: flat metric including G=0 (the total moment
189 * is not conserved and must contribute to the metric).
190 */
191 for (int spin_index = 1; spin_index < number_of_spin_components; spin_index++) {
192 for (int g_index = 0; g_index < g_count; g_index++) {
193 Real g_factor = (g_index < first_nonzero_g_index_) ? 1.0 : gamma_factor;
194 result +=
196 std::real(std::conj(rho_a[spin_index][g_index]) * rho_b[spin_index][g_index]);
197 }
198 }
199
200 return result;
201 }
202
203 std::function<void(const std::vector<Real>&, std::vector<Complex>&)> to_reciprocal_space_;
204 std::function<void(const std::vector<Complex>&, std::vector<Real>&)> to_real_space_;
205 std::vector<Real> charge_metric_weights_;
206 std::shared_ptr<Preconditioner> preconditioner_;
209 bool is_gamma_only_{false};
211};
212
213} // namespace q3
Abstract base class for all charge density mixers.
Definition init.hpp:46
Mixer(Real mixing_parameter, int history_depth)
Definition init.hpp:48
Real mixing_parameter_
Definition init.hpp:92
std::vector< Real > charge_metric_weights_
Definition init.hpp:205
static void convert_up_down_to_total_magnetization(std::vector< std::vector< Complex > > &density_g)
In-place (ρ↑, ρ↓) → (ρ↑+ρ↓, ρ↑−ρ↓).
Definition init.hpp:121
void initialize(const MixerEnvironment &environment) override
Supply the representation context the mixer needs before the first mix().
Definition init.hpp:30
static std::vector< Real > sum_over_spin_channels(const std::vector< std::vector< Real > > &density_real, int number_of_spin_components)
Total real-space density ρ↑+ρ↓ (a copy of channel 0 when unpolarized).
Definition init.hpp:108
std::shared_ptr< Preconditioner > preconditioner_
Definition init.hpp:206
static void convert_total_magnetization_to_up_down(std::vector< std::vector< Complex > > &density_g)
In-place (ρ↑+ρ↓, ρ↑−ρ↓) → (ρ↑, ρ↓).
Definition init.hpp:132
Real magnetization_metric_weight_
Definition init.hpp:207
std::function< void(const std::vector< Real > &, std::vector< Complex > &)> to_reciprocal_space_
Definition init.hpp:203
virtual void mix_in_reciprocal_space(std::vector< std::vector< Complex > > &input_density_g, const std::vector< std::vector< Complex > > &output_density_g, int number_of_spin_components)=0
Real compute_metric_dot_product(const std::vector< std::vector< Complex > > &rho_a, const std::vector< std::vector< Complex > > &rho_b, int number_of_spin_components, int g_vector_count=-1) const
Definition init.hpp:168
void mix(const std::vector< std::vector< Real > > &input_density, std::vector< std::vector< Real > > &output_density, int number_of_spin_components) override
Definition init.hpp:41
std::function< void(const std::vector< Complex > &, std::vector< Real > &)> to_real_space_
Definition init.hpp:204
double Real
std::complex< double > Complex
int first_nonzero_g_index
Definition init.hpp:39
Real magnetization_metric_weight
Definition init.hpp:38
std::function< void(const std::vector< Complex > &, std::vector< Real > &)> to_real_space
Definition init.hpp:30
int number_of_smooth_g_vectors
Definition init.hpp:41
std::shared_ptr< Preconditioner > preconditioner
Definition init.hpp:37
std::function< void(const std::vector< Real > &, std::vector< Complex > &)> to_reciprocal_space
Definition init.hpp:29
std::vector< Real > charge_metric_weights
Definition init.hpp:31