Loading...
Searching...
No Matches
grid_sizer.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * FFTGridSizer — determines FFT grid dimensions from energy cutoffs and crystal symmetry.
5 */
6
7#include <algorithm>
8#include <cmath>
9#include <vector>
10
16
17namespace q3 {
18
20 public:
21 /**
22 * Divisibility that one component of a fractional translation imposes on the
23 * FFT grid dimension along its axis.
24 *
25 * A translation by tau carries a grid point onto a grid point only when
26 * tau * N is a whole number of steps, so a component equal to 1/n asks for N
27 * divisible by n. The component is first folded into [-1/2, 1/2], since a
28 * translation and its negative are the same constraint and spglib reports
29 * translations in [0, 1) -- 3/4 there is -1/4 here, denominator 4 rather
30 * than 1.
31 *
32 * round(1/|tau|) is an integer for any tau whatsoever, so the candidate
33 * denominator is kept only when it reproduces the component it came from.
34 * Accepting it unchecked is what read the 0.375 between the two sublattices
35 * of the committed 2x2x2 silicon supercell -- no fraction of this form at
36 * all -- as 1/3, and asked for a grid divisible by 3 in a cell whose
37 * translations are halves.
38 *
39 * Returns 1, no constraint, for a component that is zero, that is not within
40 * tolerance of 1/n, or whose denominator is outside {2, 3, 4, 6}. Those four
41 * are the denominators a space group's own fractional translations can take
42 * (the screw axes and glide planes), and they are also the only ones
43 * get_good_fft_order() can be sure to satisfy, as it looks for multiples of
44 * the factor among products of 2, 3 and 5 -- a denominator of 7 would send it
45 * searching forever.
46 */
47 static int fft_factor_for_fractional_translation(Real translation_component) {
48 Real folded_component = translation_component - std::round(translation_component);
49 Real translation_magnitude = std::abs(folded_component);
51 return 1;
52 }
53 int fractional_denominator = static_cast<int>(std::round(1.0 / translation_magnitude));
54 if (std::abs(translation_magnitude - 1.0 / fractional_denominator) >
56 return 1;
57 }
58 if (fractional_denominator == 2 || fractional_denominator == 3 ||
59 fractional_denominator == 4 || fractional_denominator == 6) {
60 return fractional_denominator;
61 }
62 return 1;
63 }
64
65 /**
66 * Compute FFT grid divisibility factors from the space group's fractional
67 * translations, so that every operation the code applies moves the real-space
68 * grid onto itself (Quantum ESPRESSO's fft_fact).
69 *
70 * The translations are those of the symmetry operations spglib accepts, not
71 * differences between same-species atom pairs. Two like atoms separated by a
72 * fraction of a lattice vector need not be related by any operation of the
73 * group -- in a supercell most such pairs are not -- and the scan also had no
74 * way to tell a real fraction from a difference that is none, which is how
75 * the committed 2x2x2 silicon supercell ended up with factors (6, 6, 6)
76 * where its translations ask for (4, 4, 4).
77 *
78 * Reading the group also makes the factor scale with the cell: the primitive
79 * cell's own halves are quarters in a cell twice as long, so the supercell
80 * asks for twice the divisor and the two grids keep the same spacing. A
81 * factor read off atom pairs had no reason to respect that.
82 */
83 static void compute_fft_factors(const Crystal& crystal_structure, int fft_factors[3]) {
84 fft_factors[0] = fft_factors[1] = fft_factors[2] = 1;
85
86 const std::vector<SymmetryOperation> symmetry_operations =
88 for (const SymmetryOperation& operation : symmetry_operations) {
89 for (int dimension = 0; dimension < 3; dimension++) {
90 fft_factors[dimension] =
91 least_common_multiple(fft_factors[dimension],
93 operation.fractional_translation[dimension]));
94 }
95 }
96 }
97
98 /**
99 * Determine minimum FFT grid dimensions for a given energy cutoff.
100 */
101 static void calculate_grid_dimensions(Real energy_cutoff,
102 Real two_pi_over_lattice_parameter_squared,
103 const Matrix3x3& direct_lattice,
104 const Matrix3x3& reciprocal_lattice,
105 int& grid_dimension_1,
106 int& grid_dimension_2,
107 int& grid_dimension_3,
108 const int fft_factors[3] = nullptr) {
109 Real g_vector_cutoff_squared = energy_cutoff / two_pi_over_lattice_parameter_squared;
110 Real square_root_g_vector_cutoff_squared = std::sqrt(g_vector_cutoff_squared);
111
112 int grid_estimate_1 = static_cast<int>(std::floor(square_root_g_vector_cutoff_squared *
113 direct_lattice[0].norm())) +
114 1;
115 int grid_estimate_2 = static_cast<int>(std::floor(square_root_g_vector_cutoff_squared *
116 direct_lattice[1].norm())) +
117 1;
118 int grid_estimate_3 = static_cast<int>(std::floor(square_root_g_vector_cutoff_squared *
119 direct_lattice[2].norm())) +
120 1;
121
122 int maximum_miller_index_1 = 0;
123 int maximum_miller_index_2 = 0;
124 int maximum_miller_index_3 = 0;
125
126 for (int index_3 = -grid_estimate_3; index_3 <= grid_estimate_3; index_3++) {
127 for (int index_2 = -grid_estimate_2; index_2 <= grid_estimate_2; index_2++) {
128 for (int index_1 = -grid_estimate_1; index_1 <= grid_estimate_1; index_1++) {
129 Vector3D g_cartesian;
130 for (int dimension = 0; dimension < 3; dimension++) {
131 g_cartesian[dimension] = index_1 * reciprocal_lattice[0][dimension] +
132 index_2 * reciprocal_lattice[1][dimension] +
133 index_3 * reciprocal_lattice[2][dimension];
134 }
135 Real g_squared = g_cartesian.norm2();
136 if (g_squared < g_vector_cutoff_squared +
138 maximum_miller_index_1 =
139 std::max(maximum_miller_index_1, std::abs(index_1));
140 maximum_miller_index_2 =
141 std::max(maximum_miller_index_2, std::abs(index_2));
142 maximum_miller_index_3 =
143 std::max(maximum_miller_index_3, std::abs(index_3));
144 }
145 }
146 }
147 }
148
149 grid_dimension_1 = 2 * maximum_miller_index_1 + 1;
150 grid_dimension_2 = 2 * maximum_miller_index_2 + 1;
151 grid_dimension_3 = 2 * maximum_miller_index_3 + 1;
152
153 int factor_1 = (fft_factors != nullptr) ? fft_factors[0] : 1;
154 int factor_2 = (fft_factors != nullptr) ? fft_factors[1] : 1;
155 int factor_3 = (fft_factors != nullptr) ? fft_factors[2] : 1;
156
157 grid_dimension_1 = get_good_fft_order(grid_dimension_1, factor_1);
158 grid_dimension_2 = get_good_fft_order(grid_dimension_2, factor_2);
159 grid_dimension_3 = get_good_fft_order(grid_dimension_3, factor_3);
160 }
161
162 /**
163 * Find good FFT dimension (multiple of small primes).
164 */
165 static int get_good_fft_dimension(int number_of_points) {
166 return get_good_fft_order(number_of_points);
167 }
168
169 /**
170 * Find smallest n >= number_of_points that is a product of 2, 3, 5 and divisible by
171 * division_factor.
172 */
173 static int get_good_fft_order(int number_of_points, int division_factor = 1) {
174 int candidate_points = number_of_points;
175 while (true) {
176 if (division_factor > 1 && candidate_points % division_factor != 0) {
177 candidate_points++;
178 continue;
179 }
180 int remaining_factors = candidate_points;
181 while (remaining_factors % 2 == 0)
182 remaining_factors /= 2;
183 while (remaining_factors % 3 == 0)
184 remaining_factors /= 3;
185 while (remaining_factors % 5 == 0)
186 remaining_factors /= 5;
187 if (remaining_factors == 1)
188 return candidate_points;
189 candidate_points++;
190 }
191 }
192
193 private:
194 static int least_common_multiple(int a, int b) {
195 int greater_value = a;
196 int smaller_value = b;
197 while (smaller_value) {
198 int temporary = smaller_value;
199 smaller_value = greater_value % smaller_value;
200 greater_value = temporary;
201 }
202 return a / greater_value * b;
203 }
204};
205
206} // namespace q3
static void compute_fft_factors(const Crystal &crystal_structure, int fft_factors[3])
static int fft_factor_for_fractional_translation(Real translation_component)
static int get_good_fft_dimension(int number_of_points)
static int get_good_fft_order(int number_of_points, int division_factor=1)
static void calculate_grid_dimensions(Real energy_cutoff, Real two_pi_over_lattice_parameter_squared, const Matrix3x3 &direct_lattice, const Matrix3x3 &reciprocal_lattice, int &grid_dimension_1, int &grid_dimension_2, int &grid_dimension_3, const int fft_factors[3]=nullptr)
static std::vector< SymmetryOperation > get_symmetry_operations(const Crystal &crystal, Real symmetry_precision=math::tolerances::default_symmetry_precision)
Definition symmetry.hpp:484
constexpr double energy_cutoff_rounding_tolerance
constexpr double fractional_translation_rounding_tolerance
double Real
3×3 matrix stored row-major: row i = mat[i]
Real norm2() const
Definition vector_3d.hpp:65