Loading...
Searching...
No Matches
potential_local.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * Local potential tabulation free functions.
5 *
6 * Pure functions that compute the local pseudopotential in G-space:
7 * - Direct radial evaluation at a single q value
8 * - Per-species tabulation at all G-vectors
9 * - Total local potential with structure factors
10 */
11
12#include <cmath>
13#include <complex>
14#include <vector>
15
25
26namespace q3 {
27
28/**
29 * Whether a dataset gives its local potential reciprocally, with the bare Coulomb tail
30 * already subtracted, rather than radially.
31 *
32 * A dataset may tabulate V_loc reciprocally for the plane-wave grid as well as giving a
33 * radial potential that describes something else (the potential inside the augmentation
34 * sphere), so this is a branch on what the dataset carries, not a shortcut. Such a table
35 * stores V(q) + 4πZe²/q², the divergence subtracted; at q = 0 its entry is the finite
36 * α_Z constant.
37 */
39 const auto* paw = dynamic_cast<const PawPseudopotential*>(&pseudopotential);
40 return paw != nullptr && paw->has_reciprocal_local_potential();
41}
42
43/**
44 * The long-range part of V_loc(q): the transform of the Coulomb tail -Z e²/r, which
45 * diverges as 1/q² at small q.
46 *
47 * A radially given potential has the Gaussian-screened tail -Z e² erf(r)/r subtracted in
48 * real space (see calculate_local_potential_short_range_at_q), and this is its exact
49 * transform, -4πZe² exp(-q²/4)/(Ωq²). A reciprocal table subtracted the bare tail, so for
50 * it this is -4πZe²/(Ωq²). Zero at q = 0, where the finite α_Z constant stands for the
51 * whole potential.
52 *
53 * It is kept out of every interpolation table. A 4-point Lagrange fit across a 1/q²
54 * divergence is off by roughly (dq/q)⁴ of the term itself — a relative 1e-3 at the
55 * smallest |G| of a 20 Å cell with dq = 0.01 — and the error lands in every G-vector
56 * near the origin, which only cells with a long vacuum direction have.
57 */
59 bool bare_coulomb_tail,
60 Real q_value,
61 Real cell_volume) {
63 return 0.0;
64 if (bare_coulomb_tail)
66 (cell_volume * q_value * q_value);
68 std::exp(-q_value * q_value * 0.25) / (cell_volume * q_value * q_value);
69}
70
71/**
72 * d/dq of calculate_local_potential_long_range_at_q, which the local stress needs:
73 * 2C/q³ for the bare tail -C/q², and C exp(-q²/4) (2/q³ + 1/(2q)) for the screened one,
74 * with C = 4πZe²/Ω. Zero at q = 0.
75 */
77 bool bare_coulomb_tail,
78 Real q_value,
79 Real cell_volume) {
81 return 0.0;
82 Real coulomb_over_q_squared = valence_charge * constants::electron_charge_squared_rydberg *
83 constants::four_pi / (cell_volume * q_value * q_value);
84 if (bare_coulomb_tail)
85 return 2.0 * coulomb_over_q_squared / q_value;
86 return coulomb_over_q_squared * std::exp(-q_value * q_value * 0.25) *
87 (2.0 / q_value + 0.5 * q_value);
88}
89
90/**
91 * The short-range part of V_loc(q): everything but the long-range tail.
92 *
93 * It is smooth in q down to and including q = 0, which is what lets it be tabulated
94 * on a uniform q-grid and interpolated. For a radially given potential it is the
95 * transform of r V_loc(r) + Z e² erf(r), which vanishes beyond the core, integrated with
96 * Simpson's rule over the truncated mesh; at q = 0 the kernel sin(qr)/q is its limit r.
97 * For a reciprocal table it is the table itself, resampled at q.
98 */
100 int mesh_size,
101 Real q_value,
102 Real cell_volume) {
103 if (local_potential_is_tabulated_reciprocally(pseudopotential)) {
104 const auto& paw = dynamic_cast<const PawPseudopotential&>(pseudopotential);
105 std::vector<Real> single_q{q_value};
106 std::vector<Real> tabulated;
107 resample_uniform_reciprocal_table(paw.potential_local_reciprocal,
108 paw.reciprocal_potential_grid_spacing,
109 0,
110 single_q,
111 tabulated);
112 return tabulated[0] / cell_volume;
113 }
114
115 bool at_zero_q = q_value < math::tolerances::zero_vector_magnitude_threshold;
116 std::vector<Real> integrand(mesh_size);
117 for (int radial_index = 0; radial_index < mesh_size; radial_index++) {
118 Real radius = pseudopotential.radial_grid[radial_index];
119 Real screened_potential =
120 radius * pseudopotential.potential_local.radial_values[radial_index] +
122 std::erf(radius);
123 integrand[radial_index] = at_zero_q
124 ? screened_potential * radius
125 : screened_potential * std::sin(q_value * radius) / q_value;
126 }
127 Real integrated_sum =
128 simpson_rule(integrand, pseudopotential.radial_grid_derivative, mesh_size);
129 return constants::four_pi * integrated_sum / cell_volume;
130}
131
132/**
133 * Compute local pseudopotential V_loc(q) via direct radial integration.
134 *
135 * This evaluates the Fourier-transformed local potential at a single |G| value: the
136 * short-range part plus the long-range part for q > 0, and the finite α_Z constant at
137 * q = 0, the G = 0 term of a neutral cell.
138 */
140 int mesh_size,
141 Real q_value,
142 Real cell_volume) {
143 bool bare_coulomb_tail = local_potential_is_tabulated_reciprocally(pseudopotential);
146 pseudopotential, mesh_size, q_value, cell_volume) +
148 pseudopotential.valence_charge, bare_coulomb_tail, q_value, cell_volume);
149 }
150
151 // A reciprocal table's own q = 0 entry is already α_Z.
152 if (bare_coulomb_tail)
154 pseudopotential, mesh_size, q_value, cell_volume);
155
156 std::vector<Real> integrand(mesh_size);
157 for (int radial_index = 0; radial_index < mesh_size; radial_index++) {
158 integrand[radial_index] =
159 pseudopotential.radial_grid[radial_index] *
160 (pseudopotential.radial_grid[radial_index] *
161 pseudopotential.potential_local.radial_values[radial_index] +
163 }
164 Real integrated_sum =
165 simpson_rule(integrand, pseudopotential.radial_grid_derivative, mesh_size);
166 return constants::four_pi * integrated_sum / cell_volume;
167}
168
169/// Result of local potential tabulation: per-species V_loc(G) and total with structure factors.
171 std::vector<std::vector<Real>> per_species;
172 std::vector<Complex> total;
173};
174
175/**
176 * Tabulate local potential in G-space for all species and compute the total.
177 *
178 * Evaluates V_loc(|G|) directly at each G-vector (no intermediate q-grid),
179 * then combines with structure factors to produce the total local potential.
180 */
183 const PlanewaveBasis& planewave_basis,
184 const PseudopotentialSet& pseudopotential_set,
185 const std::vector<int>& mesh_sizes,
186 const std::vector<Real>& g_vector_magnitudes,
187 Real cell_volume) {
188
189 int number_of_species_types = static_cast<int>(pseudopotential_set.size());
190 int number_of_g_vectors = planewave_basis.number_of_g_vectors();
191
193
194 // Compute Vloc(G) directly at actual G-vectors
195 result.per_species.resize(number_of_species_types);
196 for (int type_index = 0; type_index < number_of_species_types; type_index++) {
197 result.per_species[type_index].resize(number_of_g_vectors);
198 for (int g_vector_index = 0; g_vector_index < number_of_g_vectors; g_vector_index++) {
199 Real q_value = g_vector_magnitudes[g_vector_index];
200 result.per_species[type_index][g_vector_index] = calculate_local_potential_at_q(
201 pseudopotential_set[type_index], mesh_sizes[type_index], q_value, cell_volume);
202 }
203 }
204
205 // Compute structure factor and total Vloc(G)
206 result.total.resize(number_of_g_vectors, Complex(0.0, 0.0));
207 for (int g_vector_index = 0; g_vector_index < number_of_g_vectors; g_vector_index++) {
208 for (int type_index = 0; type_index < number_of_species_types; type_index++) {
209 Complex structure_factor(0.0, 0.0);
210 for (int atom_index = 0; atom_index < crystal_structure.number_of_atoms();
211 atom_index++) {
212 if (crystal_structure.species_type_index(atom_index) == type_index) {
213 Vector3D atomic_position =
214 crystal_structure.convert_from_crystal_to_cartesian(atom_index);
215 const auto& g_vector = planewave_basis.g_vectors()[g_vector_index];
216 Real phase = constants::two_pi * g_vector.g_cart.dot(atomic_position);
217 structure_factor += compute_phase_factor(phase);
218 }
219 }
220 result.total[g_vector_index] +=
221 result.per_species[type_index][g_vector_index] * structure_factor;
222 }
223 }
224
225 return result;
226}
227
228} // namespace q3
Vector3D convert_from_crystal_to_cartesian(int atom_index) const
Definition crystal.hpp:225
int species_type_index(int atom_index) const
Definition crystal.hpp:273
int number_of_atoms() const
Definition crystal.hpp:209
PAW pseudopotential — extends ultrasoft with one-center (on-site) data.
Definition init.hpp:33
bool has_reciprocal_local_potential() const
Definition init.hpp:197
int number_of_g_vectors() const
Definition init.hpp:145
const std::vector< GVector > & g_vectors() const
Definition init.hpp:148
int size() const
Definition set.hpp:98
Base class for all pseudopotential types.
Definition init.hpp:35
std::vector< Real > radial_grid_derivative
Definition init.hpp:77
std::vector< Real > radial_grid
Definition init.hpp:76
PotentialLocal potential_local
Definition init.hpp:78
constexpr Real electron_charge_squared_rydberg
Definition constants.hpp:32
constexpr Real two_pi
Definition constants.hpp:25
constexpr Real four_pi
Definition constants.hpp:26
constexpr double zero_vector_magnitude_threshold
Real calculate_local_potential_short_range_at_q(const Pseudopotential &pseudopotential, int mesh_size, Real q_value, Real cell_volume)
Real simpson_rule(const std::vector< Real > &function_values, const std::vector< Real > &integration_factors, int number_of_grid_points)
Definition simpson.hpp:23
double Real
Real calculate_local_potential_long_range_derivative_at_q(Real valence_charge, bool bare_coulomb_tail, Real q_value, Real cell_volume)
Complex compute_phase_factor(Real phase)
Compute e^{-iφ} = cos(φ) - i·sin(φ) (standard structure factor phase).
bool local_potential_is_tabulated_reciprocally(const Pseudopotential &pseudopotential)
Real calculate_local_potential_long_range_at_q(Real valence_charge, bool bare_coulomb_tail, Real q_value, Real cell_volume)
void resample_uniform_reciprocal_table(const std::vector< Real > &samples, Real spacing, int angular_momentum, const std::vector< Real > &q_values, std::vector< Real > &result)
Real calculate_local_potential_at_q(const Pseudopotential &pseudopotential, int mesh_size, Real q_value, Real cell_volume)
std::complex< double > Complex
LocalPotentialTabulationResult tabulate_local_potential_in_g_space(const Crystal &crystal_structure, const PlanewaveBasis &planewave_basis, const PseudopotentialSet &pseudopotential_set, const std::vector< int > &mesh_sizes, const std::vector< Real > &g_vector_magnitudes, Real cell_volume)
Result of local potential tabulation: per-species V_loc(G) and total with structure factors.
std::vector< std::vector< Real > > per_species
std::vector< Real > radial_values