Loading...
Searching...
No Matches
ewald.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * Ewald (ion-ion) force contribution.
5 *
6 * Computes the Hellmann-Feynman force contribution from the Ewald summation
7 * of the ion-ion Coulomb interaction. Split into G-space and R-space sums
8 * with an optimal convergence parameter α.
9 *
10 * Differentiates the Ewald summation of P. P. Ewald, Ann. Phys. 369, 253
11 * (1921), with respect to the atomic positions.
12 *
13 * This contribution is independent of pseudopotential type.
14 */
15
16#include <algorithm>
17#include <cmath>
18#include <vector>
19
26
27namespace q3 {
28
30 public:
31 EwaldForceCalculator(const Crystal& crystal_structure, const PlanewaveBasis& planewave_basis)
32 : crystal_structure_(crystal_structure), planewave_basis_(planewave_basis) {}
33
34 [[nodiscard]] std::vector<Vector3D> compute() const {
35 using namespace constants;
36 Real lattice_parameter = crystal_structure_.lattice_parameter();
37 const Matrix3x3& lattice_vectors = crystal_structure_.lattice_vectors();
38
39 Real total_charge = 0.0;
40 for (int atom_index = 0; atom_index < crystal_structure_.number_of_atoms(); atom_index++) {
41 total_charge += crystal_structure_.valence_charge(
42 crystal_structure_.species_type_index(atom_index));
43 }
44
45 // Optimal convergence parameter
46 Real cutoff_g_space = planewave_basis_.charge_density_energy_cutoff() /
47 crystal_structure_.two_pi_over_lattice_parameter_squared();
48 static constexpr Real initial_ewald_convergence_parameter = 2.9;
49 static constexpr Real ewald_convergence_parameter_step = 0.1;
50 Real alpha = initial_ewald_convergence_parameter;
51 while (alpha > ewald_convergence_parameter_step) {
52 alpha -= ewald_convergence_parameter_step;
53 Real upper_bound =
54 2.0 * total_charge * total_charge * std::sqrt(2.0 * alpha / pi) *
55 std::erfc(std::sqrt(crystal_structure_.two_pi_over_lattice_parameter_squared() *
56 cutoff_g_space / (4.0 * alpha)));
58 break;
59 }
60 }
61 if (alpha < ewald_convergence_parameter_step) {
62 alpha = ewald_convergence_parameter_step;
63 }
64
65 std::vector<Vector3D> forces(crystal_structure_.number_of_atoms(), {0.0, 0.0, 0.0});
66 int first_nonzero_g_index = planewave_basis_.first_nonzero_g_index();
67
68 // G-space sum
69 for (int g_vector_index = first_nonzero_g_index;
70 g_vector_index < planewave_basis_.number_of_g_vectors();
71 g_vector_index++) {
72 Real g_vector_squared = planewave_basis_.g_vectors()[g_vector_index].magnitude_squared *
73 crystal_structure_.two_pi_over_lattice_parameter_squared();
74 /**
75 * factor = 2 · exp(-G²/4α) / G², doubled again on a half sphere.
76 *
77 * The 2 is not about the sphere, and it is not the Rydberg e²
78 * either — that is applied separately below. It is the factor from
79 * differentiating a modulus squared: ∂|ρ(G)|²/∂τ = 2 Re[ρ* ∂ρ/∂τ].
80 * So it is correct for the full-sphere G list Q3 normally builds.
81 * Gamma-only stores only the positive half sphere
82 * (`g_vectors_grid.hpp`), and
83 * the summand here is *even* under G → −G (the imaginary part is
84 * odd and G_α is odd), so the missing half contributes exactly as
85 * much again.
86 *
87 * Without this the Ewald force is exactly half at Gamma while the
88 * Ewald *energy* — which depends only on geometry — is unchanged:
89 * measured -0.02549078 against a −dE_ewald/dy of -0.05098040.
90 */
91 Real half_sphere_scale = planewave_basis_.is_gamma_only() ? 2.0 : 1.0;
92 Real factor = std::exp(-g_vector_squared / alpha / 4.0) / g_vector_squared * 2.0 *
93 half_sphere_scale;
94
95 Complex valence_charge_density_g(0.0, 0.0);
96 for (int atom_index = 0; atom_index < crystal_structure_.number_of_atoms();
97 atom_index++) {
98 Vector3D atomic_position =
99 crystal_structure_.convert_from_crystal_to_cartesian(atom_index);
100 Real phase = two_pi * planewave_basis_.g_vectors()[g_vector_index].g_cart.dot(
101 atomic_position);
102 valence_charge_density_g += crystal_structure_.valence_charge(
103 crystal_structure_.species_type_index(atom_index)) *
105 }
106
107 for (int atom_index = 0; atom_index < crystal_structure_.number_of_atoms();
108 atom_index++) {
109 Vector3D atomic_position =
110 crystal_structure_.convert_from_crystal_to_cartesian(atom_index);
111 Real phase = two_pi * planewave_basis_.g_vectors()[g_vector_index].g_cart.dot(
112 atomic_position);
113 Complex structure_factor_i = compute_phase_factor(phase);
114 Real valence_charge_i = crystal_structure_.valence_charge(
115 crystal_structure_.species_type_index(atom_index));
116
117 for (int direction = 0; direction < 3; direction++) {
118 Real g_cartesian_component =
119 planewave_basis_.g_vectors()[g_vector_index].g_cart[direction] *
120 crystal_structure_.two_pi_over_lattice_parameter();
121 Real force_prefactor = valence_charge_i * factor * g_cartesian_component;
122 Complex complex_product =
123 valence_charge_density_g * std::conj(structure_factor_i);
124 /**
125 * F_{G,α} = +(e²⋅2π/Ω) factor Z_i G_α Im[S(G) e^{-iGτ_i}]
126 * complex_product = S(G) × conj(e^{iGτ_i}) = S(G) × e^{-iGτ_i}.
127 * Im[S e^{-iGτ}] = -Im[e^{+iGτ} S*] (conjugate of correct formula),
128 * combined with the F = -∂E/∂τ sign, the result is +=.
129 */
130 forces[atom_index][direction] += electron_charge_squared_rydberg * two_pi /
131 crystal_structure_.cell_volume() *
132 force_prefactor * complex_product.imag();
133 }
134 }
135 }
136
137 // R-space sum
138 Real rmax_bohr = 4.0 / std::sqrt(alpha);
139 Real rmax_alat = rmax_bohr / lattice_parameter;
140 Real volume_alat =
141 std::abs(lattice_vectors[0].dot(lattice_vectors[1].cross(lattice_vectors[2])));
142
143 int limit_x = std::max(
144 1,
145 static_cast<int>(std::ceil(
146 rmax_alat * lattice_vectors[1].cross(lattice_vectors[2]).norm() / volume_alat)));
147 int limit_y = std::max(
148 1,
149 static_cast<int>(std::ceil(
150 rmax_alat * lattice_vectors[2].cross(lattice_vectors[0]).norm() / volume_alat)));
151 int limit_z = std::max(
152 1,
153 static_cast<int>(std::ceil(
154 rmax_alat * lattice_vectors[0].cross(lattice_vectors[1]).norm() / volume_alat)));
155
156 for (int atom_index_a = 0; atom_index_a < crystal_structure_.number_of_atoms();
157 atom_index_a++) {
158 for (int atom_index_b = 0; atom_index_b < crystal_structure_.number_of_atoms();
159 atom_index_b++) {
160 /**
161 * The image loop is sized for a separation in the home cell, and positions
162 * outside [0, 1) are legal input; see Crystal::home_cell_separation().
163 */
164 Vector3D position_difference =
165 crystal_structure_.home_cell_separation(atom_index_a, atom_index_b);
166
167 for (int lattice_vector_multiplier_1 = -limit_x;
168 lattice_vector_multiplier_1 <= limit_x;
169 lattice_vector_multiplier_1++) {
170 for (int lattice_vector_multiplier_2 = -limit_y;
171 lattice_vector_multiplier_2 <= limit_y;
172 lattice_vector_multiplier_2++) {
173 for (int lattice_vector_multiplier_3 = -limit_z;
174 lattice_vector_multiplier_3 <= limit_z;
175 lattice_vector_multiplier_3++) {
176 if (atom_index_a == atom_index_b && lattice_vector_multiplier_1 == 0 &&
177 lattice_vector_multiplier_2 == 0 &&
178 lattice_vector_multiplier_3 == 0) {
179 continue;
180 }
181 Vector3D displacement_vector;
182 for (int direction = 0; direction < 3; direction++) {
183 displacement_vector[direction] =
184 position_difference[direction] +
185 lattice_vector_multiplier_1 * lattice_vectors[0][direction] +
186 lattice_vector_multiplier_2 * lattice_vectors[1][direction] +
187 lattice_vector_multiplier_3 * lattice_vectors[2][direction];
188 }
189 Real distance_magnitude =
190 displacement_vector.norm() * lattice_parameter;
191 if (distance_magnitude <
193 continue;
194 }
195 if (distance_magnitude > rmax_bohr) {
196 continue;
197 }
198
199 Real argument = std::sqrt(alpha) * distance_magnitude;
200 Real factor =
201 crystal_structure_.valence_charge(
202 crystal_structure_.species_type_index(atom_index_a)) *
203 crystal_structure_.valence_charge(
204 crystal_structure_.species_type_index(atom_index_b)) *
205 (2.0 * argument / std::sqrt(pi) * std::exp(-argument * argument) +
206 std::erfc(argument)) /
207 (distance_magnitude * distance_magnitude);
208
209 for (int direction = 0; direction < 3; direction++) {
210 forces[atom_index_a][direction] -=
211 electron_charge_squared_rydberg * factor *
212 displacement_vector[direction] * lattice_parameter /
213 distance_magnitude;
214 }
215 }
216 }
217 }
218 }
219 }
220
221 return forces;
222 }
223
224 private:
225 const Crystal& crystal_structure_;
226 const PlanewaveBasis& planewave_basis_;
227};
228
229} // namespace q3
Vector3D convert_from_crystal_to_cartesian(int atom_index) const
Definition crystal.hpp:225
Real two_pi_over_lattice_parameter_squared() const
Definition crystal.hpp:199
Real valence_charge(int species_index) const
Definition crystal.hpp:317
Real cell_volume() const
Definition crystal.hpp:193
Real two_pi_over_lattice_parameter() const
Definition crystal.hpp:196
const Matrix3x3 & lattice_vectors() const
Definition crystal.hpp:202
int species_type_index(int atom_index) const
Definition crystal.hpp:273
Vector3D home_cell_separation(int atom_index_a, int atom_index_b) const
Definition crystal.hpp:253
int number_of_atoms() const
Definition crystal.hpp:209
Real lattice_parameter() const
Definition crystal.hpp:180
std::vector< Vector3D > compute() const
Definition ewald.hpp:34
EwaldForceCalculator(const Crystal &crystal_structure, const PlanewaveBasis &planewave_basis)
Definition ewald.hpp:31
int number_of_g_vectors() const
Definition init.hpp:145
const std::vector< GVector > & g_vectors() const
Definition init.hpp:148
int first_nonzero_g_index() const
Definition init.hpp:151
Real charge_density_energy_cutoff() const
Definition init.hpp:345
bool is_gamma_only() const
Definition init.hpp:157
constexpr double ewald_convergence_threshold
constexpr double zero_vector_magnitude_threshold
double Real
Complex compute_phase_factor(Real phase)
Compute e^{-iφ} = cos(φ) - i·sin(φ) (standard structure factor phase).
std::complex< double > Complex
3×3 matrix stored row-major: row i = mat[i]
Real norm() const
Definition vector_3d.hpp:68