Loading...
Searching...
No Matches
g_vectors_grid.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * GVectorsGrid — sorted charge-density G-vectors within the charge_density_energy_cutoff sphere.
5 */
6
7#include <algorithm>
8#include <cmath>
9#include <vector>
10
16
17namespace q3 {
18
20 public:
21 GVectorsGrid() = default;
22
23 void generate(const Crystal& crystal_structure,
24 Real energy_cutoff_charge_density,
26 int grid_dimension_2,
27 int grid_dimension_3,
28 bool is_gamma_only = false,
29 int rank = 0,
30 int size = 1,
31 int fft_local_start = -1,
32 int fft_local_dimension_1 = -1) {
34 grid_dimension_2_ = grid_dimension_2;
35 grid_dimension_3_ = grid_dimension_3;
37
38 Real two_pi_over_lattice_parameter_squared =
39 crystal_structure.two_pi_over_lattice_parameter_squared();
40 const Matrix3x3& reciprocal_lattice = crystal_structure.reciprocal_lattice_vectors();
41
43 energy_cutoff_charge_density / two_pi_over_lattice_parameter_squared;
44
46 int maximum_index_2 = grid_dimension_2 / 2;
47 int maximum_index_3 = grid_dimension_3 / 2;
48
49 g_vectors_.clear();
50 g_vectors_.reserve(grid_dimension_1 * grid_dimension_2 * grid_dimension_3 / 4);
51
55 /**
56 * For gamma-only, keep only the positive half-sphere:
57 * index_3 > 0, OR
58 * index_3 == 0 && index_2 > 0, OR
59 * index_3 == 0 && index_2 == 0 && index_1 >= 0
60 * The half itself is dictated: rho(-G) = conj(rho(G)) for a
61 * real density, so one of each +/-G pair is redundant, and
62 * G = 0 -- its own partner -- is kept once, which is why the
63 * last key above is >= 0 and not > 0. Which half is free, and
64 * this is not the one Quantum ESPRESSO keeps: its rule leads
65 * on the FIRST Miller index where this one leads on the third,
66 * so the two codes hold mirror-image halves. Read the rule
67 * above rather than assume the two lists are interchangeable.
68 */
69 if (is_gamma_only) {
70 if (index_3 < 0)
71 continue;
72 if (index_3 == 0 && index_2 < 0)
73 continue;
74 if (index_3 == 0 && index_2 == 0 && index_1 < 0)
75 continue;
76 }
77
79 for (int dimension = 0; dimension < 3; dimension++) {
80 g_cartesian[dimension] = index_1 * reciprocal_lattice[0][dimension] +
81 index_2 * reciprocal_lattice[1][dimension] +
82 index_3 * reciprocal_lattice[2][dimension];
83 }
84 Real g_squared = g_cartesian.norm2();
85 if (g_squared <= g_vector_cutoff_squared +
90 g_vector.magnitude_squared = g_squared;
91 g_vectors_.push_back(g_vector);
92 }
93 }
94 }
95 }
96
97 // Sort by magnitude (standard convention, important for kinetic energy).
98 std::sort(g_vectors_.begin(),
99 g_vectors_.end(),
100 [](const GVector& first_vector, const GVector& second_vector) {
101 return first_vector.magnitude_squared < second_vector.magnitude_squared;
102 });
103
104 // Store the global G-vector count before partitioning.
105 global_number_of_g_vectors_ = static_cast<int>(g_vectors_.size());
106
107 // Partition G-vectors across band ranks.
108 if (fft_local_start >= 0 && fft_local_dimension_1 >= 0 && size > 1) {
109 /**
110 * SLAB-BASED PARTITIONING: Each rank owns G-vectors that fall into its FFT slabs.
111 * This ensures all G-vectors are local when scattering to/from the FFT grid.
112 * Note: This replaces index-based partitioning when FFT slab info is provided.
113 */
114 std::vector<GVector> local_g_vectors;
115 for (const auto& g_vector : g_vectors_) {
116 int h = g_vector.miller.x;
117 int i = (h >= 0) ? h : (h + grid_dimension_1_);
119 local_g_vectors.push_back(g_vector);
120 }
121 }
122 /**
123 * Overwrite global g_vectors_ with local ones.
124 * WARNING: This rank now only knows about its local G-vectors.
125 * For dot_product and other collectives, this is fine as long as everyone
126 * knows it's a partition.
127 */
128 g_vectors_ = std::move(local_g_vectors);
129 local_count_ = static_cast<int>(g_vectors_.size());
130 local_start_ = 0; // Relative to the local array, but this is a bit ambiguous.
131 } else {
132 // INDEX-BASED PARTITIONING (fallback or np=1)
133 int total_g = static_cast<int>(g_vectors_.size());
134 local_count_ = total_g / size;
135 int remainder = total_g % size;
137 if (rank < remainder) {
138 local_count_++;
140 } else {
142 }
143
144 std::vector<GVector> local_g_vectors;
145 for (int i = 0; i < local_count_; ++i) {
147 }
148 g_vectors_ = std::move(local_g_vectors);
150 }
151
153 if (!g_vectors_.empty() &&
156 }
157
158 Real two_pi_over_lattice_parameter = crystal_structure.two_pi_over_lattice_parameter();
159 g_vector_magnitudes_.resize(g_vectors_.size());
160 for (size_t g_index = 0; g_index < g_vectors_.size(); g_index++) {
162 std::sqrt(g_vectors_[g_index].magnitude_squared) * two_pi_over_lattice_parameter;
163 }
164 }
165
166 /// Compute the FFT grid index for a G-vector from its Miller indices.
168 int index_1 = g_vector.miller[0];
169 int index_2 = g_vector.miller[1];
170 int index_3 = g_vector.miller[2];
171 if (index_1 < 0)
173 if (index_2 < 0)
175 if (index_3 < 0)
178 }
179
181 return static_cast<int>(g_vectors_.size());
182 }
183
184 [[nodiscard]] int grid_dimension_1() const {
185 return grid_dimension_1_;
186 }
187
188 /**
189 * Total number of G-vectors across all band ranks (before partitioning).
190 * Equal to number_of_g_vectors() when band_size == 1.
191 */
195
197 return local_count_;
198 }
199
201 return local_start_;
202 }
203
204 [[nodiscard]] const std::vector<GVector>& g_vectors() const {
205 return g_vectors_;
206 }
207
208 [[nodiscard]] const GVector& g_vector(int index) const {
209 return g_vectors_[index];
210 }
211
214 }
215
216 [[nodiscard]] const std::vector<Real>& g_vector_magnitudes() const {
218 }
219
220 [[nodiscard]] bool is_gamma_only() const {
221 return is_gamma_only_;
222 }
223
224 [[nodiscard]] int
226 Real two_pi_over_lattice_parameter_squared) const {
228 energy_cutoff_wavefunction / two_pi_over_lattice_parameter_squared;
229 int count = 0;
230 for (const auto& g_vector : g_vectors_) {
233 count++;
234 }
235 return count;
236 }
237
238 protected:
239 std::vector<GVector> g_vectors_;
240 std::vector<Real> g_vector_magnitudes_;
248 bool is_gamma_only_{false};
249};
250
251} // namespace q3
Real two_pi_over_lattice_parameter_squared() const
Definition crystal.hpp:199
Real two_pi_over_lattice_parameter() const
Definition crystal.hpp:196
const Matrix3x3 & reciprocal_lattice_vectors() const
Definition crystal.hpp:205
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 local_g_vector_start_index() const
int estimate_maximum_plane_waves_per_kpoint(Real energy_cutoff_wavefunction, Real two_pi_over_lattice_parameter_squared) const
const GVector & g_vector(int index) const
GVectorsGrid()=default
std::vector< Real > g_vector_magnitudes_
const std::vector< GVector > & g_vectors() const
int number_of_g_vectors() const
int g_vector_to_fft_grid_index(const GVector &g_vector) const
Compute the FFT grid index for a G-vector from its Miller indices.
int local_number_of_g_vectors() const
int first_nonzero_g_index() const
std::vector< GVector > g_vectors_
int global_number_of_g_vectors() const
int grid_dimension_1() const
bool is_gamma_only() const
A grid of reciprocal-space vectors.
const ReciprocalSpaceVector & vector(int index) const
constexpr double zero_vector_magnitude_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 magnitude_squared
Definition g_vector.hpp:28
IntegerVector3D miller
Integer Miller indices (h, k, l).
Definition g_vector.hpp:19
Vector3D g_cart
Cartesian coordinates in units of 2π/alat.
Definition g_vector.hpp:22
3×3 matrix stored row-major: row i = mat[i]