Loading...
Searching...
No Matches
ewald_summation.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <cmath>
4#include <complex>
5
6#include "io/logger.hpp"
12
13namespace q3 {
14
15/**
16 * Compute the Ewald ion-ion electrostatic energy using both
17 * G-space and real-space sums with optimal convergence parameter.
18 *
19 * Implements the Ewald summation of P. P. Ewald, Ann. Phys. 369, 253 (1921).
20 */
21inline Real compute_ewald(const Crystal& crystal_structure, const PlanewaveBasis& planewave_basis) {
22 using namespace constants;
23 int number_of_atoms = crystal_structure.number_of_atoms();
24 int number_of_g_vectors = planewave_basis.number_of_g_vectors();
25 Real two_pi_over_lattice_parameter_squared =
26 crystal_structure.two_pi_over_lattice_parameter_squared();
27 Real cell_volume = crystal_structure.cell_volume();
28 Real lattice_parameter = crystal_structure.lattice_parameter();
29 const Matrix3x3& lattice_vectors = crystal_structure.lattice_vectors();
30
31 Real charge = 0.0;
32 for (int atom_index = 0; atom_index < number_of_atoms; atom_index++) {
33 charge +=
34 crystal_structure.valence_charge(crystal_structure.species_type_index(atom_index));
35 }
36
37 // Find optimal alpha for convergence
38 Real g_cutoff_magnitude =
39 planewave_basis.charge_density_energy_cutoff() / two_pi_over_lattice_parameter_squared;
40 Real alpha = 2.9;
41 while (alpha > 0.1) {
42 alpha -= 0.1;
43 Real upperbound = 2.0 * charge * charge * std::sqrt(2.0 * alpha / pi) *
44 std::erfc(std::sqrt(two_pi_over_lattice_parameter_squared *
45 g_cutoff_magnitude / (4.0 * alpha)));
47 break;
48 }
49 if (alpha < 0.1)
50 alpha = 0.1;
51
52 // G-space sum
53 Real ewald_g_space_sum = 0.0;
54 int first_nonzero_g_index = planewave_basis.first_nonzero_g_index();
55
56 if (first_nonzero_g_index == 1) {
57 ewald_g_space_sum = -charge * charge / alpha / 4.0;
58 }
59
60 for (int g_index = first_nonzero_g_index; g_index < number_of_g_vectors; g_index++) {
61 Complex charge_density_coefficient(0.0, 0.0);
62 for (int atom_index = 0; atom_index < number_of_atoms; atom_index++) {
63 Vector3D atomic_position =
64 crystal_structure.convert_from_crystal_to_cartesian(atom_index);
65 const auto& g_vector = planewave_basis.g_vectors()[g_index];
66 Real phase_angle = two_pi * g_vector.g_cart.dot(atomic_position);
67 charge_density_coefficient +=
68 crystal_structure.valence_charge(crystal_structure.species_type_index(atom_index)) *
69 compute_phase_factor(phase_angle);
70 }
71 Real g_squared = planewave_basis.g_vectors()[g_index].magnitude_squared *
72 two_pi_over_lattice_parameter_squared;
73 Real term =
74 std::norm(charge_density_coefficient) * std::exp(-g_squared / alpha / 4.0) / g_squared;
75 /**
76 * For gamma-only half-sphere: each G≠0 represents both G and -G,
77 * and |ρ(-G)|² = |ρ(G)|² by Hermitian symmetry, so double the contribution.
78 */
79 if (planewave_basis.is_gamma_only()) {
80 term *= 2.0;
81 }
82 ewald_g_space_sum += term;
83 }
84 ewald_g_space_sum = 2.0 * two_pi / cell_volume * ewald_g_space_sum;
85
86 // Self-energy correction
87 if (first_nonzero_g_index == 1) {
88 for (int atom_index = 0; atom_index < number_of_atoms; atom_index++) {
89 ewald_g_space_sum -=
90 crystal_structure.valence_charge(crystal_structure.species_type_index(atom_index)) *
91 crystal_structure.valence_charge(crystal_structure.species_type_index(atom_index)) *
92 std::sqrt(8.0 / two_pi * alpha);
93 }
94 }
95
96 // R-space sum
97 Real ewald_r_space_sum = 0.0;
98 Real rmax_bohr = 4.0 / std::sqrt(alpha);
99 Real rmax_alat = rmax_bohr / lattice_parameter;
100 Real volume_alat =
101 std::abs(lattice_vectors[0].dot(lattice_vectors[1].cross(lattice_vectors[2])));
102
103 int limit_x = std::max(
104 1,
105 static_cast<int>(std::ceil(rmax_alat * lattice_vectors[1].cross(lattice_vectors[2]).norm() /
106 volume_alat)));
107 int limit_y = std::max(
108 1,
109 static_cast<int>(std::ceil(rmax_alat * lattice_vectors[2].cross(lattice_vectors[0]).norm() /
110 volume_alat)));
111 int limit_z = std::max(
112 1,
113 static_cast<int>(std::ceil(rmax_alat * lattice_vectors[0].cross(lattice_vectors[1]).norm() /
114 volume_alat)));
115
116 for (int atom_index_a = 0; atom_index_a < number_of_atoms; atom_index_a++) {
117 for (int atom_index_b = 0; atom_index_b < number_of_atoms; atom_index_b++) {
118 /**
119 * The image loop is sized for a separation in the home cell, and positions
120 * outside [0, 1) are legal input; see Crystal::home_cell_separation().
121 */
122 Vector3D position_difference =
123 crystal_structure.home_cell_separation(atom_index_a, atom_index_b);
124
125 for (int lattice_index_x = -limit_x; lattice_index_x <= limit_x; lattice_index_x++) {
126 for (int lattice_index_y = -limit_y; lattice_index_y <= limit_y;
127 lattice_index_y++) {
128 for (int lattice_index_z = -limit_z; lattice_index_z <= limit_z;
129 lattice_index_z++) {
130 if (atom_index_a == atom_index_b && lattice_index_x == 0 &&
131 lattice_index_y == 0 && lattice_index_z == 0)
132 continue;
133 Vector3D real_space_vector;
134 for (int dimension_index = 0; dimension_index < 3; dimension_index++) {
135 real_space_vector[dimension_index] =
136 position_difference[dimension_index] +
137 lattice_index_x * lattice_vectors[0][dimension_index] +
138 lattice_index_y * lattice_vectors[1][dimension_index] +
139 lattice_index_z * lattice_vectors[2][dimension_index];
140 }
141 Real real_space_distance = real_space_vector.norm() * lattice_parameter;
143 continue;
144 if (real_space_distance > rmax_bohr)
145 continue;
146 ewald_r_space_sum +=
147 crystal_structure.valence_charge(
148 crystal_structure.species_type_index(atom_index_a)) *
149 crystal_structure.valence_charge(
150 crystal_structure.species_type_index(atom_index_b)) *
151 std::erfc(std::sqrt(alpha) * real_space_distance) / real_space_distance;
152 }
153 }
154 }
155 }
156 }
157
158 return 0.5 * electron_charge_squared_rydberg * (ewald_g_space_sum + ewald_r_space_sum);
159}
160
161} // 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
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
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).
Real compute_ewald(const Crystal &crystal_structure, const PlanewaveBasis &planewave_basis)
std::complex< double > Complex
3×3 matrix stored row-major: row i = mat[i]
Real norm() const
Definition vector_3d.hpp:68