Loading...
Searching...
No Matches
init.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * PlanewaveEffectivePotentialCalculator — Hartree + exchange-correlation + ionic local
5 * potential assembly for a plane-wave Kohn-Sham representation.
6 *
7 * Given the spin-resolved charge density and the ionic local potential, this
8 * calculator produces the real-space effective potential per spin and its
9 * reciprocal-space projections, together with the Hartree and exchange-correlation
10 * energy terms. It holds no mutable state; the SCF iterator owns the result and
11 * copies it into its own members/energies.
12 *
13 * The exchange-correlation density is constructed by the virtual method
14 * build_exchange_correlation_density(), which subclasses override to include
15 * additional contributions (e.g., NLCC core charge from pseudopotentials).
16 */
17
18#include <cmath>
19#include <complex>
20#include <memory>
21#include <vector>
22
32
33namespace q3 {
34
36 public:
38 const PlanewaveBasis& planewave_basis,
39 const Crystal& crystal_structure,
40 const XCFunctional& exchange_correlation,
41 const execution::PlanewaveExecutionPolicy& execution_policy)
42 : planewave_basis_(planewave_basis)
43 , crystal_structure_(crystal_structure)
44 , exchange_correlation_(exchange_correlation)
45 , execution_policy_(execution_policy)
48 planewave_basis, crystal_structure, exchange_correlation)) {}
49
51
52 EffectivePotentialResult compute(const std::vector<std::vector<Real>>& charge_density_real,
53 int number_of_spin_components,
54 const std::vector<Complex>& local_ionic_potential_g) const {
56 int total_grid_points = planewave_basis_.fft_rho().total_grid_points();
57 int local_grid_points = planewave_basis_.fft_rho().local_grid_points();
58
59 std::vector<Real> total_charge_density_real(local_grid_points, 0.0);
60 for (int spin_index = 0; spin_index < number_of_spin_components; ++spin_index) {
61 for (int grid_index = 0; grid_index < local_grid_points; ++grid_index) {
62 total_charge_density_real[grid_index] +=
63 charge_density_real[spin_index][grid_index];
64 }
65 }
66 planewave_basis_.rho_r_to_g(total_charge_density_real, output.charge_density_g);
67
68 std::vector<Real> hartree_potential_real = compute_hartree_potential(output);
69
70 std::vector<std::vector<Real>> exchange_correlation_density;
71 std::vector<std::vector<Complex>> exchange_correlation_density_g;
72 build_exchange_correlation_density(charge_density_real,
73 number_of_spin_components,
74 exchange_correlation_density,
75 exchange_correlation_density_g);
76
77 std::vector<std::vector<Real>> exchange_correlation_potential_by_spin(
78 number_of_spin_components, std::vector<Real>(local_grid_points));
79 if (number_of_spin_components == 1) {
80 compute_xc_potential_unpolarized(exchange_correlation_density,
81 exchange_correlation_density_g,
82 charge_density_real,
83 exchange_correlation_potential_by_spin[0],
84 output);
85 } else {
86 compute_xc_potential_spin_polarized(exchange_correlation_density,
87 exchange_correlation_density_g,
88 charge_density_real,
89 exchange_correlation_potential_by_spin[0],
90 exchange_correlation_potential_by_spin[1],
91 output);
92 }
93
94 /**
95 * Reduce XC energy and XC potential energy within this pool.
96 * Both are real-space slab integrals (over local_grid_points) and each rank
97 * in the band communicator holds only its slab of the grid. With 1-rank-per-pool
98 * this is a no-op; with multiple ranks per pool the partial sums must be combined.
99 */
102
103 assemble_effective_potential(hartree_potential_real,
104 exchange_correlation_potential_by_spin,
105 number_of_spin_components,
106 local_ionic_potential_g,
107 output);
108 return output;
109 }
110
111 protected:
116 /// V_xc evaluation strategy (local-density vs gradient-corrected), fixed at construction.
117 std::unique_ptr<ExchangeCorrelationPotentialCalculator>
119 mutable std::vector<Real> cached_local_ionic_potential_real_;
120
121 /**
122 * Build the density seen by the XC functional.
123 * Base implementation copies the valence charge density directly.
124 * Subclasses override to include additional contributions (e.g., NLCC core charge).
125 */
127 const std::vector<std::vector<Real>>& charge_density_real,
128 int number_of_spin_components,
129 std::vector<std::vector<Real>>& exchange_correlation_density,
130 std::vector<std::vector<Complex>>& exchange_correlation_density_g) const {
131 int number_of_g_vectors = planewave_basis_.number_of_g_vectors();
132 int local_grid_points = planewave_basis_.fft_rho().local_grid_points();
133
134 exchange_correlation_density.assign(number_of_spin_components,
135 std::vector<Real>(local_grid_points));
136 exchange_correlation_density_g.assign(number_of_spin_components,
137 std::vector<Complex>(number_of_g_vectors));
138
139 for (int spin_index = 0; spin_index < number_of_spin_components; ++spin_index) {
140 for (int grid_index = 0; grid_index < local_grid_points; ++grid_index) {
141 exchange_correlation_density[spin_index][grid_index] =
142 charge_density_real[spin_index][grid_index];
143 }
144
145 planewave_basis_.rho_r_to_g(charge_density_real[spin_index],
146 exchange_correlation_density_g[spin_index]);
147 }
148 }
149
150 private:
151 std::vector<Real> compute_hartree_potential(EffectivePotentialResult& output) const {
152 int number_of_g_vectors = planewave_basis_.number_of_g_vectors();
153 int first_nonzero_g_index = planewave_basis_.first_nonzero_g_index();
154 Real cell_volume = crystal_structure_.cell_volume();
155 Real two_pi_over_lattice_parameter_squared =
157
158 std::vector<Complex> hartree_potential_g(number_of_g_vectors, Complex(0.0, 0.0));
159 Real hartree_energy_local = 0.0;
160 bool is_gamma_only = planewave_basis_.is_gamma_only();
161 for (int g_vector_index = first_nonzero_g_index; g_vector_index < number_of_g_vectors;
162 ++g_vector_index) {
163 Real g_squared = planewave_basis_.g_vectors()[g_vector_index].magnitude_squared *
164 two_pi_over_lattice_parameter_squared;
165 Real prefactor =
167 hartree_potential_g[g_vector_index] =
168 output.charge_density_g[g_vector_index] * prefactor;
169 /**
170 * For gamma-only half-sphere: double the energy contribution since
171 * each G≠0 represents both G and -G, and |ρ(-G)|² = |ρ(G)|².
172 */
173 Real gamma_factor = is_gamma_only ? 2.0 : 1.0;
174 hartree_energy_local +=
175 std::norm(output.charge_density_g[g_vector_index]) * prefactor * gamma_factor;
176 }
177 output.hartree_energy = hartree_energy_local * 0.5 * cell_volume;
178
179 std::vector<Real> hartree_potential_real;
180 planewave_basis_.rho_g_to_r(hartree_potential_g, hartree_potential_real);
181 return hartree_potential_real;
182 }
183
184 void compute_xc_potential_unpolarized(
185 const std::vector<std::vector<Real>>& exchange_correlation_density,
186 const std::vector<std::vector<Complex>>& exchange_correlation_density_g,
187 const std::vector<std::vector<Real>>& charge_density_real,
188 std::vector<Real>& exchange_correlation_potential,
189 EffectivePotentialResult& output) const {
190 int total_grid_points = planewave_basis_.fft_rho().total_grid_points();
191 int local_grid_points = planewave_basis_.fft_rho().local_grid_points();
192 Real cell_volume = crystal_structure_.cell_volume();
193
194 exchange_correlation_potential.assign(local_grid_points, 0.0);
195 std::vector<Real> exchange_correlation_energy_density(local_grid_points);
196
197 /**
198 * Whether this evaluation needs density gradients was decided once, at
199 * construction, by create_exchange_correlation_potential_calculator().
200 */
201 exchange_correlation_potential_calculator_->evaluate_unpolarized(
202 exchange_correlation_density[0],
203 exchange_correlation_density_g[0],
204 exchange_correlation_potential,
205 exchange_correlation_energy_density);
206
207 output.exchange_correlation_energy = 0.0;
208 output.exchange_correlation_potential_energy = 0.0;
209 for (int grid_index = 0; grid_index < local_grid_points; ++grid_index) {
210 output.exchange_correlation_energy += exchange_correlation_energy_density[grid_index] *
211 exchange_correlation_density[0][grid_index];
212 output.exchange_correlation_potential_energy +=
213 exchange_correlation_potential[grid_index] * charge_density_real[0][grid_index];
214 }
215 output.exchange_correlation_energy *= cell_volume / total_grid_points;
216 output.exchange_correlation_potential_energy *= cell_volume / total_grid_points;
217 }
218
219 void compute_xc_potential_spin_polarized(
220 const std::vector<std::vector<Real>>& exchange_correlation_density,
221 const std::vector<std::vector<Complex>>& exchange_correlation_density_g,
222 const std::vector<std::vector<Real>>& charge_density_real,
223 std::vector<Real>& exchange_correlation_potential_up,
224 std::vector<Real>& exchange_correlation_potential_down,
225 EffectivePotentialResult& output) const {
226 int total_grid_points = planewave_basis_.fft_rho().total_grid_points();
227 int local_grid_points = planewave_basis_.fft_rho().local_grid_points();
228 Real cell_volume = crystal_structure_.cell_volume();
229
230 exchange_correlation_potential_up.assign(local_grid_points, 0.0);
231 exchange_correlation_potential_down.assign(local_grid_points, 0.0);
232 std::vector<Real> exchange_correlation_energy_density(local_grid_points);
233
234 /**
235 * Whether this evaluation needs density gradients was decided once, at
236 * construction, by create_exchange_correlation_potential_calculator().
237 */
238 exchange_correlation_potential_calculator_->evaluate_spin_polarized(
239 exchange_correlation_density[0],
240 exchange_correlation_density[1],
241 exchange_correlation_density_g[0],
242 exchange_correlation_density_g[1],
243 exchange_correlation_potential_up,
244 exchange_correlation_potential_down,
245 exchange_correlation_energy_density);
246
247 output.exchange_correlation_energy = 0.0;
248 output.exchange_correlation_potential_energy = 0.0;
249 for (int grid_index = 0; grid_index < local_grid_points; ++grid_index) {
250 Real total_density = exchange_correlation_density[0][grid_index] +
251 exchange_correlation_density[1][grid_index];
252 output.exchange_correlation_energy +=
253 exchange_correlation_energy_density[grid_index] * total_density;
254 output.exchange_correlation_potential_energy +=
255 exchange_correlation_potential_up[grid_index] * charge_density_real[0][grid_index] +
256 exchange_correlation_potential_down[grid_index] *
257 charge_density_real[1][grid_index];
258 }
259 output.exchange_correlation_energy *= cell_volume / total_grid_points;
260 output.exchange_correlation_potential_energy *= cell_volume / total_grid_points;
261 }
262
263 /**
264 * Combine Hartree, exchange-correlation and ionic local potentials into the
265 * real-space effective potential per spin and its reciprocal-space projections.
266 */
267 void assemble_effective_potential(
268 const std::vector<Real>& hartree_potential_real,
269 const std::vector<std::vector<Real>>& exchange_correlation_potential_by_spin,
270 int number_of_spin_components,
271 const std::vector<Complex>& local_ionic_potential_g,
272 EffectivePotentialResult& output) const {
273 int grid_points = planewave_basis_.fft_rho().total_grid_points();
274
275 // Cache the local ionic potential FFT (constant during SCF)
277 planewave_basis_.rho_g_to_r(local_ionic_potential_g,
279 }
280 int local_grid_points = planewave_basis_.fft_rho().local_grid_points();
281 const auto& local_potential_real_space = cached_local_ionic_potential_real_;
282
283 output.real_space_potential.resize(number_of_spin_components);
284 for (int spin_index = 0; spin_index < number_of_spin_components; ++spin_index) {
285 output.real_space_potential[spin_index].resize(local_grid_points);
286 for (int grid_index = 0; grid_index < local_grid_points; ++grid_index) {
287 output.real_space_potential[spin_index][grid_index] =
288 hartree_potential_real[grid_index] +
289 exchange_correlation_potential_by_spin[spin_index][grid_index] +
290 local_potential_real_space[grid_index];
291 }
292 }
293
294 std::vector<Real> spin_averaged_potential(local_grid_points, 0.0);
295 for (int spin_index = 0; spin_index < number_of_spin_components; ++spin_index) {
296 for (int grid_index = 0; grid_index < local_grid_points; ++grid_index) {
297 spin_averaged_potential[grid_index] +=
298 output.real_space_potential[spin_index][grid_index] / number_of_spin_components;
299 }
300 }
301 planewave_basis_.rho_r_to_g(spin_averaged_potential, output.total_potential_g);
302
303 /**
304 * Each channel's own potential, transformed separately. Kept alongside
305 * the average rather than replacing it: the average is still what the
306 * norm-conserving path and the checkpoint format expect, and it is the
307 * correct thing for an unpolarized cell.
308 */
309 output.total_potential_g_by_spin.resize(number_of_spin_components);
310 for (int spin_index = 0; spin_index < number_of_spin_components; ++spin_index) {
311 planewave_basis_.rho_r_to_g(output.real_space_potential[spin_index],
312 output.total_potential_g_by_spin[spin_index]);
313 }
314
315 std::vector<Real> exchange_correlation_potential_average(local_grid_points, 0.0);
316 for (int grid_index = 0; grid_index < local_grid_points; ++grid_index) {
317 exchange_correlation_potential_average[grid_index] =
318 spin_averaged_potential[grid_index] - hartree_potential_real[grid_index] -
319 local_potential_real_space[grid_index];
320 }
321 planewave_basis_.rho_r_to_g(exchange_correlation_potential_average,
322 output.exchange_correlation_potential_g);
323 }
324};
325
326} // namespace q3
Real two_pi_over_lattice_parameter_squared() const
Definition crystal.hpp:199
Real cell_volume() const
Definition crystal.hpp:193
virtual int total_grid_points() const =0
virtual int local_grid_points() const =0
int number_of_g_vectors() const
Definition init.hpp:145
void rho_g_to_r(const std::vector< Complex > &charge_density_g_space, std::vector< Real > &charge_density_real_space) const
Definition init.hpp:328
const std::vector< GVector > & g_vectors() const
Definition init.hpp:148
void rho_r_to_g(const std::vector< Real > &charge_density_real_space, std::vector< Complex > &charge_density_g_space) const
Definition init.hpp:334
int first_nonzero_g_index() const
Definition init.hpp:151
FFTGrid & fft_rho()
Definition init.hpp:286
bool is_gamma_only() const
Definition init.hpp:157
const XCFunctional & exchange_correlation_
Definition init.hpp:114
const execution::PlanewaveExecutionPolicy & execution_policy_
Definition init.hpp:115
PlanewaveEffectivePotentialCalculator(const PlanewaveBasis &planewave_basis, const Crystal &crystal_structure, const XCFunctional &exchange_correlation, const execution::PlanewaveExecutionPolicy &execution_policy)
Definition init.hpp:37
const PlanewaveBasis & planewave_basis_
Definition init.hpp:112
virtual void build_exchange_correlation_density(const std::vector< std::vector< Real > > &charge_density_real, int number_of_spin_components, std::vector< std::vector< Real > > &exchange_correlation_density, std::vector< std::vector< Complex > > &exchange_correlation_density_g) const
Definition init.hpp:126
EffectivePotentialResult compute(const std::vector< std::vector< Real > > &charge_density_real, int number_of_spin_components, const std::vector< Complex > &local_ionic_potential_g) const
Definition init.hpp:52
std::unique_ptr< ExchangeCorrelationPotentialCalculator > exchange_correlation_potential_calculator_
V_xc evaluation strategy (local-density vs gradient-corrected), fixed at construction.
Definition init.hpp:118
virtual ~PlanewaveEffectivePotentialCalculator()=default
std::vector< Real > cached_local_ionic_potential_real_
Definition init.hpp:119
Abstract base class for XC functionals.
Definition init.hpp:23
virtual void reduce_within_pool_scalar(Real &value) const =0
constexpr Real electron_charge_squared_rydberg
Definition constants.hpp:32
constexpr Real four_pi
Definition constants.hpp:26
std::unique_ptr< ExchangeCorrelationPotentialCalculator > create_exchange_correlation_potential_calculator(const PlanewaveBasis &planewave_basis, const Crystal &crystal_structure, const XCFunctional &exchange_correlation)
Definition factory.hpp:26
double Real
std::complex< double > Complex
std::vector< Complex > charge_density_g
Definition result.hpp:17