Loading...
Searching...
No Matches
init.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4
5#include <cmath>
6
7/**
8 * PlanewaveBasis — facade over domain-specific plane-wave basis components.
9 *
10 * Sub-domains:
11 * fft_pair_ — charge-density and wavefunction FFT grids
12 * g_vectors_ — charge-density G-vector sphere
13 * plane_wave_table_ — per-k-point active plane waves (initialized separately)
14 * transformers — stateless G ↔ R conversion (WavefunctionTransformer,
15 * ChargeDensityTransformer)
16 */
17
18#include <vector>
19
30
31namespace q3 {
32
33/**
34 * Energy cutoffs that determine the plane-wave basis size.
35 */
37 /// Wavefunction energy cutoff in Rydberg.
39 /// Charge density energy cutoff in Rydberg (typically 4× wavefunction cutoff for NC PP).
41};
42
44 public:
45 PlanewaveBasis() = default;
46
48 : energy_cutoff_wavefunction_(config.energy_cutoff_wavefunction)
49 , energy_cutoff_charge_density_(config.energy_cutoff_charge_density) {
50 if (energy_cutoff_charge_density_ <= 0.0) {
51 energy_cutoff_charge_density_ = 4.0 * energy_cutoff_wavefunction_;
52 }
53 }
54
55 /**
56 * Generate G-vectors and FFT grids for given cutoffs.
57 */
58 void generate(const FFTGridFactory& fft_grid_factory,
59 const Crystal& crystal_structure,
62 bool is_gamma_only = false,
63 bool use_measured_fft_planning = true,
64 bool use_exact_wavefunction_grid = false) {
65 is_gamma_only_ = is_gamma_only;
66 energy_cutoff_wavefunction_ = wavefunction_energy_cutoff;
67 energy_cutoff_charge_density_ = charge_density_energy_cutoff;
68 if (energy_cutoff_charge_density_ <= 0.0) {
69 energy_cutoff_charge_density_ = 4.0 * energy_cutoff_wavefunction_;
70 }
71 fft_pair_.initialize(fft_grid_factory,
72 crystal_structure,
73 energy_cutoff_charge_density_,
74 energy_cutoff_wavefunction_,
75 use_measured_fft_planning,
76 use_exact_wavefunction_grid);
77 /**
78 * Charge density G-vectors must remain replicated on all band ranks.
79 * Only wavefunction coefficients (per k-point) are distributed via FFT
80 * slab decomposition — see PlaneWaveSelector::select().
81 */
82 g_vectors_.generate(crystal_structure,
83 energy_cutoff_charge_density_,
84 fft_pair_.fft_rho().grid_dimension_1(),
85 fft_pair_.fft_rho().grid_dimension_2(),
86 fft_pair_.fft_rho().grid_dimension_3(),
87 is_gamma_only_,
88 0,
89 1,
90 -1,
91 -1);
92 maximum_plane_waves_per_kpoint_ = g_vectors_.estimate_maximum_plane_waves_per_kpoint(
93 energy_cutoff_wavefunction_, crystal_structure.two_pi_over_lattice_parameter_squared());
94
95 /**
96 * Tell the density grid which sticks carry G-vectors, so a stick
97 * layout can balance them and skip the empty ones. A grid that does
98 * not distribute by stick ignores this.
99 */
100 fft_pair_.fft_rho().assign_sticks(
101 stick_weights_for_grid(fft_pair_.fft_rho(), [](const GVector&) { return true; }));
102 }
103
104 /**
105 * Count the G-vectors on each stick of a grid, by wrapped first and second
106 * Miller index, for those the predicate accepts and that fit in the grid.
107 */
108 template <typename Predicate>
109 [[nodiscard]] std::vector<int> stick_weights_for_grid(const FFTGrid& grid,
110 Predicate accept) const {
111 int dimension_1 = grid.grid_dimension_1();
112 int dimension_2 = grid.grid_dimension_2();
113 int dimension_3 = grid.grid_dimension_3();
114 std::vector<int> weights(static_cast<std::size_t>(dimension_1) * dimension_2, 0);
115 for (const auto& g_vector : g_vectors_.g_vectors()) {
116 int miller_1 = static_cast<int>(std::lround(g_vector.miller.x));
117 int miller_2 = static_cast<int>(std::lround(g_vector.miller.y));
118 int miller_3 = static_cast<int>(std::lround(g_vector.miller.z));
119 if (std::abs(miller_1) > dimension_1 / 2 || std::abs(miller_2) > dimension_2 / 2 ||
120 std::abs(miller_3) > dimension_3 / 2 || !accept(g_vector)) {
121 continue;
122 }
123 int wrapped_1 = miller_1 >= 0 ? miller_1 : miller_1 + dimension_1;
124 int wrapped_2 = miller_2 >= 0 ? miller_2 : miller_2 + dimension_2;
125 weights[static_cast<std::size_t>(wrapped_1) * dimension_2 + wrapped_2] += 1;
126 }
127 /**
128 * Every stick is owned, balanced by how much of the sphere it carries.
129 * A stick outside the sphere could be dropped, but two consumers here
130 * still read the whole grid box -- the
131 * dense-to-smooth potential copy and the smooth-to-dense density
132 * transfer -- and truncating would change their answer against a
133 * serial run. Transporting the empty sticks costs the all-to-all the
134 * same volume the slab transpose moved; skipping them is a later
135 * optimisation that wants those two loops made sphere-only first.
136 */
137 for (auto& weight : weights) {
138 weight = std::max(weight, 1);
139 }
140 return weights;
141 }
142
143 // ─── Charge-density G-vectors ──────────────────────────────────
144
145 [[nodiscard]] int number_of_g_vectors() const {
146 return g_vectors_.number_of_g_vectors();
147 }
148 [[nodiscard]] const std::vector<GVector>& g_vectors() const {
149 return g_vectors_.g_vectors();
150 }
151 [[nodiscard]] int first_nonzero_g_index() const {
152 return g_vectors_.first_nonzero_g_index();
153 }
154 [[nodiscard]] const std::vector<Real>& g_vector_magnitudes() const {
155 return g_vectors_.g_vector_magnitudes();
156 }
157 [[nodiscard]] bool is_gamma_only() const {
158 return is_gamma_only_;
159 }
160
161 /**
162 * Whether this rank's plane-wave list begins with G = 0.
163 *
164 * The gamma trick sums 2 Re over the stored half sphere and removes the
165 * origin, which the half sphere holds once, from the double count. When
166 * reciprocal space is distributed by stick exactly one rank holds the
167 * origin, and only that rank may remove it. Serial and replicated layouts
168 * always hold it, so the default
169 * is true; initialize_kpoint_data() sets it from the list actually kept.
170 * Meaningful only when is_gamma_only().
171 */
172 [[nodiscard]] bool gamma_origin_is_local() const {
173 return gamma_origin_is_local_;
174 }
175
176 // ─── Per-k-point plane waves ───────────────────────────────────
177
178 void compute_gk(const Vector3D& k_coordinates,
179 const Crystal& crystal_structure,
180 std::vector<int>& g_vector_indices,
181 std::vector<Real>& kinetic_energy_squared,
182 int band_size = 1) const {
183 /**
184 * G-vector distribution: when band_size > 1, pass the MPI FFT grid so
185 * that PlaneWaveSelector only keeps G-vectors in this rank's slab.
186 */
187 const FFTGrid* fft_wave_ptr = (band_size > 1) ? &fft_pair_.fft_wave() : nullptr;
188 PlaneWaveSelector::select(g_vectors_,
189 k_coordinates,
190 crystal_structure,
191 energy_cutoff_wavefunction_,
192 g_vector_indices,
193 kinetic_energy_squared,
194 fft_wave_ptr,
195 band_size);
196 }
197
198 [[nodiscard]] int maximum_plane_waves_per_kpoint() const {
199 return maximum_plane_waves_per_kpoint_;
200 }
201
202 void initialize_kpoint_data(const Crystal& crystal_structure,
203 const KPointsGrid& kpoint_grid,
204 int band_size = 1) {
205 /**
206 * Under a shared pool the wavefunction grid must know its sticks before
207 * the selector asks it who owns what. A stick counts if any G-vector on
208 * it can fall inside the wavefunction cutoff at some k-point, which is
209 * |G| within sqrt(E_cut) plus the largest |k| on the grid -- a superset
210 * that costs a few empty sticks and never drops a plane wave.
211 */
212 if (band_size > 1) {
213 const auto& reciprocal = crystal_structure.reciprocal_lattice_vectors();
214 Real largest_k = 0.0;
215 for (int index = 0; index < kpoint_grid.number_of_kpoints(); ++index) {
216 const auto& k = kpoint_grid[index].coordinates;
217 Vector3D k_cartesian;
218 for (int dimension = 0; dimension < 3; ++dimension) {
219 k_cartesian[dimension] = k.x * reciprocal[0][dimension] +
220 k.y * reciprocal[1][dimension] +
221 k.z * reciprocal[2][dimension];
222 }
223 largest_k = std::max(largest_k, std::sqrt(k_cartesian.norm2()));
224 }
225 Real cutoff_radius =
226 std::sqrt(energy_cutoff_wavefunction_ /
227 crystal_structure.two_pi_over_lattice_parameter_squared());
228 Real radius_squared = (cutoff_radius + largest_k) * (cutoff_radius + largest_k) *
230 fft_pair_.fft_wave().assign_sticks(
231 stick_weights_for_grid(fft_pair_.fft_wave(), [radius_squared](const GVector& g) {
232 return g.g_cart.norm2() <= radius_squared;
233 }));
234 }
235
236 const FFTGrid* fft_wave_ptr = (band_size > 1) ? &fft_pair_.fft_wave() : nullptr;
237 plane_wave_table_.initialize(g_vectors_,
238 crystal_structure,
239 kpoint_grid,
240 energy_cutoff_wavefunction_,
241 fft_wave_ptr,
242 band_size);
243
244 /**
245 * Gamma-only means a single k-point, so one list decides: the origin is
246 * local when the first entry -- the list is sorted by |k+G|^2, and at
247 * Gamma nothing else has zero kinetic energy -- is G = 0.
248 */
249 gamma_origin_is_local_ = true;
250 if (is_gamma_only_ && kpoint_grid.number_of_kpoints() > 0) {
251 const auto& indices = plane_wave_table_.g_vector_indices_by_kpoint_index(0);
252 gamma_origin_is_local_ =
253 !indices.empty() &&
254 g_vectors_.g_vectors()[static_cast<std::size_t>(indices[0])].magnitude_squared <
256 }
257 }
258
259 [[nodiscard]] const std::vector<int>& g_vector_indices_by_kpoint_index(int kpoint_index) const {
260 return plane_wave_table_.g_vector_indices_by_kpoint_index(kpoint_index);
261 }
262
263 [[nodiscard]] int number_of_plane_waves_by_kpoint_index(int kpoint_index) const {
264 return plane_wave_table_.number_of_plane_waves_by_kpoint_index(kpoint_index);
265 }
266
267 [[nodiscard]] const std::vector<Real>&
269 return plane_wave_table_.kinetic_energy_squared_by_kpoint_index(kpoint_index);
270 }
271
272 [[nodiscard]] const std::vector<std::vector<int>>& kpoint_g_vector_indices() const {
273 return plane_wave_table_.kpoint_g_vector_indices();
274 }
275
276 [[nodiscard]] const std::vector<int>& kpoint_number_of_plane_waves() const {
277 return plane_wave_table_.kpoint_number_of_plane_waves();
278 }
279
280 [[nodiscard]] const std::vector<std::vector<Real>>& kpoint_kinetic_energy_squared() const {
281 return plane_wave_table_.kpoint_kinetic_energy_squared();
282 }
283
284 // ─── FFT grids ─────────────────────────────────────────────────
285
286 [[nodiscard]] FFTGrid& fft_rho() {
287 return fft_pair_.fft_rho();
288 }
289 [[nodiscard]] const FFTGrid& fft_rho() const {
290 return fft_pair_.fft_rho();
291 }
292 [[nodiscard]] FFTGrid& fft_wave() {
293 return fft_pair_.fft_wave();
294 }
295 [[nodiscard]] const FFTGrid& fft_wave() const {
296 return fft_pair_.fft_wave();
297 }
298 [[nodiscard]] const SerialFFTGrid& fft_wave_serial() const {
299 return fft_pair_.fft_wave_serial();
300 }
301
302 // ─── G ↔ R transforms ──────────────────────────────────────────
303
304 void g_to_r(const std::vector<Complex>& coefficients_g_space,
305 const std::vector<int>& g_vector_indices,
306 int number_of_plane_waves,
307 std::vector<Complex>& coefficients_real_space) const {
309 fft_pair_.fft_wave(),
310 coefficients_g_space,
311 g_vector_indices,
312 number_of_plane_waves,
313 coefficients_real_space);
314 }
315
316 void r_to_g(const std::vector<Complex>& coefficients_real_space,
317 const std::vector<int>& g_vector_indices,
318 int number_of_plane_waves,
319 std::vector<Complex>& coefficients_g_space) const {
321 fft_pair_.fft_wave(),
322 coefficients_real_space,
323 g_vector_indices,
324 number_of_plane_waves,
325 coefficients_g_space);
326 }
327
328 void rho_g_to_r(const std::vector<Complex>& charge_density_g_space,
329 std::vector<Real>& charge_density_real_space) const {
331 g_vectors_, fft_pair_.fft_rho(), charge_density_g_space, charge_density_real_space);
332 }
333
334 void rho_r_to_g(const std::vector<Real>& charge_density_real_space,
335 std::vector<Complex>& charge_density_g_space) const {
337 g_vectors_, fft_pair_.fft_rho(), charge_density_real_space, charge_density_g_space);
338 }
339
340 // ─── Cutoffs ───────────────────────────────────────────────────
341
342 [[nodiscard]] Real wavefunction_energy_cutoff() const {
343 return energy_cutoff_wavefunction_;
344 }
345 [[nodiscard]] Real charge_density_energy_cutoff() const {
346 return energy_cutoff_charge_density_;
347 }
348
349 // ─── Sub-component access ────────────────────────────────────────
350
351 [[nodiscard]] const GVectorsGrid& g_vectors_grid() const {
352 return g_vectors_;
353 }
354 [[nodiscard]] const PlanewaveFFTPair& fft_pair() const {
355 return fft_pair_;
356 }
357 [[nodiscard]] const PlaneWaveTable& plane_wave_table() const {
358 return plane_wave_table_;
359 }
360
361 private:
362 Real energy_cutoff_wavefunction_{};
363 Real energy_cutoff_charge_density_{};
364 PlanewaveFFTPair fft_pair_;
365 GVectorsGrid g_vectors_;
366 PlaneWaveTable plane_wave_table_;
367 int maximum_plane_waves_per_kpoint_{};
368 bool is_gamma_only_{false};
369 bool gamma_origin_is_local_{true};
370};
371
372} // namespace q3
static void rho_r_to_g(const GVectorsGrid &g_vectors_grid, const FFTGrid &fft_rho, const std::vector< Real > &charge_density_real_space, std::vector< Complex > &charge_density_g_space)
static void rho_g_to_r(const GVectorsGrid &g_vectors_grid, const FFTGrid &fft_rho, const std::vector< Complex > &charge_density_g_space, std::vector< Real > &charge_density_real_space)
Real two_pi_over_lattice_parameter_squared() const
Definition crystal.hpp:199
const Matrix3x3 & reciprocal_lattice_vectors() const
Definition crystal.hpp:205
virtual void assign_sticks(const std::vector< int > &)
Definition fft_grid.hpp:106
virtual int grid_dimension_1() const =0
virtual int grid_dimension_3() const =0
virtual int grid_dimension_2() const =0
const std::vector< Real > & g_vector_magnitudes() const
void generate(const Crystal &crystal_structure, Real energy_cutoff_charge_density, int grid_dimension_1, int grid_dimension_2, int grid_dimension_3, bool is_gamma_only=false, int rank=0, int size=1, int fft_local_start=-1, int fft_local_dimension_1=-1)
int estimate_maximum_plane_waves_per_kpoint(Real energy_cutoff_wavefunction, Real two_pi_over_lattice_parameter_squared) const
const std::vector< GVector > & g_vectors() const
int number_of_g_vectors() const
int first_nonzero_g_index() const
int number_of_kpoints() const
static void select(const GVectorsGrid &g_vectors_grid, const Vector3D &k_coordinates, const Crystal &crystal_structure, Real energy_cutoff_wavefunction, std::vector< int > &g_vector_indices, std::vector< Real > &kinetic_energy_squared, const FFTGrid *fft_wave=nullptr, int band_size=1)
const std::vector< std::vector< Real > > & kpoint_kinetic_energy_squared() const
const std::vector< int > & g_vector_indices_by_kpoint_index(int kpoint_index) const
void initialize(const GVectorsGrid &g_vectors_grid, const Crystal &crystal_structure, const KPointsGrid &kpoint_grid, Real energy_cutoff_wavefunction, const FFTGrid *fft_wave=nullptr, int band_size=1)
const std::vector< std::vector< int > > & kpoint_g_vector_indices() const
const std::vector< Real > & kinetic_energy_squared_by_kpoint_index(int kpoint_index) const
const std::vector< int > & kpoint_number_of_plane_waves() const
int number_of_plane_waves_by_kpoint_index(int kpoint_index) const
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
std::vector< int > stick_weights_for_grid(const FFTGrid &grid, Predicate accept) const
Definition init.hpp:109
const std::vector< GVector > & g_vectors() const
Definition init.hpp:148
const PlanewaveFFTPair & fft_pair() const
Definition init.hpp:354
Real wavefunction_energy_cutoff() const
Definition init.hpp:342
const PlaneWaveTable & plane_wave_table() const
Definition init.hpp:357
const FFTGrid & fft_wave() const
Definition init.hpp:295
void initialize_kpoint_data(const Crystal &crystal_structure, const KPointsGrid &kpoint_grid, int band_size=1)
Definition init.hpp:202
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
void g_to_r(const std::vector< Complex > &coefficients_g_space, const std::vector< int > &g_vector_indices, int number_of_plane_waves, std::vector< Complex > &coefficients_real_space) const
Definition init.hpp:304
FFTGrid & fft_rho()
Definition init.hpp:286
const std::vector< int > & g_vector_indices_by_kpoint_index(int kpoint_index) const
Definition init.hpp:259
const std::vector< std::vector< int > > & kpoint_g_vector_indices() const
Definition init.hpp:272
Real charge_density_energy_cutoff() const
Definition init.hpp:345
int number_of_plane_waves_by_kpoint_index(int kpoint_index) const
Definition init.hpp:263
const GVectorsGrid & g_vectors_grid() const
Definition init.hpp:351
int maximum_plane_waves_per_kpoint() const
Definition init.hpp:198
const SerialFFTGrid & fft_wave_serial() const
Definition init.hpp:298
void r_to_g(const std::vector< Complex > &coefficients_real_space, const std::vector< int > &g_vector_indices, int number_of_plane_waves, std::vector< Complex > &coefficients_g_space) const
Definition init.hpp:316
FFTGrid & fft_wave()
Definition init.hpp:292
void generate(const FFTGridFactory &fft_grid_factory, const Crystal &crystal_structure, Real wavefunction_energy_cutoff, Real charge_density_energy_cutoff, bool is_gamma_only=false, bool use_measured_fft_planning=true, bool use_exact_wavefunction_grid=false)
Definition init.hpp:58
bool gamma_origin_is_local() const
Definition init.hpp:172
const FFTGrid & fft_rho() const
Definition init.hpp:289
void compute_gk(const Vector3D &k_coordinates, const Crystal &crystal_structure, std::vector< int > &g_vector_indices, std::vector< Real > &kinetic_energy_squared, int band_size=1) const
Definition init.hpp:178
const std::vector< Real > & kinetic_energy_squared_by_kpoint_index(int kpoint_index) const
Definition init.hpp:268
const std::vector< std::vector< Real > > & kpoint_kinetic_energy_squared() const
Definition init.hpp:280
const std::vector< int > & kpoint_number_of_plane_waves() const
Definition init.hpp:276
bool is_gamma_only() const
Definition init.hpp:157
PlanewaveBasis(PlanewaveBasisConfig config)
Definition init.hpp:47
PlanewaveBasis()=default
const std::vector< Real > & g_vector_magnitudes() const
Definition init.hpp:154
const SerialFFTGrid & fft_wave_serial() const
Definition pair.hpp:121
FFTGrid & fft_wave()
Definition pair.hpp:111
FFTGrid & fft_rho()
Definition pair.hpp:105
void initialize(const FFTGridFactory &fft_grid_factory, const Crystal &crystal_structure, Real energy_cutoff_charge_density, Real energy_cutoff_wavefunction, bool use_measured_fft_planning=true, bool use_exact_wavefunction_grid=false)
Definition pair.hpp:23
static void r_to_g(const GVectorsGrid &g_vectors_grid, const FFTGrid &fft_wave, const std::vector< Complex > &coefficients_real_space, const std::vector< int > &g_vector_indices, int number_of_plane_waves, std::vector< Complex > &coefficients_g_space)
static void g_to_r(const GVectorsGrid &g_vectors_grid, const FFTGrid &fft_wave, const std::vector< Complex > &coefficients_g_space, const std::vector< int > &g_vector_indices, int number_of_plane_waves, std::vector< Complex > &coefficients_real_space)
constexpr double numerical_zero_threshold
constexpr double energy_cutoff_rounding_tolerance
double Real
A reciprocal lattice vector with Miller indices, used in plane-wave calculations.
Definition g_vector.hpp:17
Real energy_cutoff_wavefunction
Wavefunction energy cutoff in Rydberg.
Definition init.hpp:38
Real energy_cutoff_charge_density
Charge density energy cutoff in Rydberg (typically 4× wavefunction cutoff for NC PP).
Definition init.hpp:40
Real norm2() const
Definition vector_3d.hpp:65