Loading...
Searching...
No Matches
augmentation.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * The force from the compensation charge moving with its atom.
5 *
6 * F_α = -∂/∂τ_α Ω Σ_G V_eff*(G) n̂_I(G), n̂_I(G) = A_I(G) e^{-iG·τ}
7 *
8 * with A_I(G) = Σ_ij ρ_ij Q_ij(G) the same object `add_augmentation_charges`
9 * builds when it puts n̂ on the grid. Differentiating only the structure factor
10 * gives
11 *
12 * F_α = -Ω Σ_G G_α Im[ V_eff*(G) A_I(G) e^{-iG·τ} ]
13 *
14 * which is the local-potential force with V_loc → V_eff and the species form
15 * factor → A_I(G).
16 *
17 * Implements the third term of Eq. (43) of K. Laasonen, A. Pasquarello,
18 * R. Car, C. Lee & D. Vanderbilt, Phys. Rev. B 47, 10142 (1993), with ρ_ij
19 * their Eq. (38) and the position dependence carried by the phase factor of
20 * their Eq. (61).
21 *
22 * **This must be built from the same Q_ij(G) the density uses.** It used to be
23 * assembled from `interpolate_qfunc`, a radial-only table indexed by *beta
24 * projector* pairs, while ρ_ij is indexed by lm-expanded *channels*: the
25 * strides disagreed, and the angular (-i)^L Y_LM structure was missing
26 * entirely. The term came out at 3.4e-7 Ry/bohr against a nonlocal term of
27 * 4.3e-2 — five orders of magnitude down, for a dataset whose compensation
28 * charge carries most of the on-site density. Q3's PAW forces were a uniform
29 * 1.24x overestimate as a result, matching its own energy derivative only to
30 * that factor.
31 *
32 * For norm-conserving datasets there is no augmentation charge and this is
33 * identically zero.
34 */
35
36#include <cmath>
37#include <complex>
38#include <stdexcept>
39#include <vector>
40
49
50namespace q3 {
51
53 public:
54 UltrasoftForceCalculator(const Crystal& crystal_structure,
55 const PlanewaveBasis& planewave_basis)
56 : crystal_structure_(crystal_structure), planewave_basis_(planewave_basis) {}
57
58 /**
59 * @param potential_g_by_spin One effective potential per spin channel.
60 * @param coefficients_by_spin One coefficient matrix per spin channel, same order.
61 *
62 * Spin is summed *inside*, channel against matching channel. Passing the
63 * spin-averaged potential and the spin-summed coefficients instead — which is
64 * what this took until 2026-08-09 — drops
65 * ∫(V↑−V↓)(ρ↑−ρ↓) dQ/dR / 2. That vanishes for a non-magnetic cell, which
66 * is why it survived every unpolarized test, and does not vanish for a
67 * magnetic one, where it left this term inconsistent with the
68 * spin-resolved nonlocal force it must cancel against.
69 */
70 std::vector<Vector3D>
72 const std::vector<std::vector<Complex>>& potential_g_by_spin,
73 const std::vector<Matrix<Real>>& coefficients_by_spin) {
74 int number_of_atoms = crystal_structure_.number_of_atoms();
75 std::vector<Vector3D> forces(number_of_atoms, {0.0, 0.0, 0.0});
76
77 if (!quantum_system.has_ultrasoft())
78 return forces;
79 if (potential_g_by_spin.size() != coefficients_by_spin.size())
80 throw std::runtime_error(
81 "augmentation force: " + std::to_string(potential_g_by_spin.size()) +
82 " potential channels against " + std::to_string(coefficients_by_spin.size()) +
83 " occupancy channels; they must pair up spin by spin");
84
85 const auto& ultrasoft_basis =
86 static_cast<const UltrasoftPlanewaveKohnShamQuantumSystem&>(quantum_system)
87 .ultrasoft_basis();
88
89 int number_of_g_vectors = planewave_basis_.number_of_g_vectors();
90 Real two_pi_over_lattice_parameter = crystal_structure_.two_pi_over_lattice_parameter();
91 Real cell_volume = crystal_structure_.cell_volume();
92 /**
93 * Gamma-only keeps only the positive half sphere, and this summand is
94 * even under G -> -G, so the omitted half contributes as much again.
95 * The same factor appears in the local, NLCC and Ewald forces.
96 */
97 Real half_sphere_scale = planewave_basis_.is_gamma_only() ? 2.0 : 1.0;
98
99 std::vector<Complex> augmentation_g(number_of_g_vectors);
100
101 for (size_t spin_index = 0; spin_index < potential_g_by_spin.size(); spin_index++) {
102 const std::vector<Complex>& total_potential_g = potential_g_by_spin[spin_index];
103 const Matrix<Real>& projector_overlap_coefficients = coefficients_by_spin[spin_index];
104
105 for (int atom_index = 0; atom_index < number_of_atoms; atom_index++) {
106 int species_index = crystal_structure_.species_type_index(atom_index);
107 if (!ultrasoft_basis.has_q_functions_g(species_index))
108 continue;
109
110 int number_of_channels =
112 species_index);
113
114 /**
115 * A_I(G), assembled exactly as `add_augmentation_charges` does:
116 * channel-indexed ρ_ij against the precomputed Q_ij(G), with the
117 * Q_ij = Q_ji symmetry folded into a single upper-triangle pass.
118 */
119 std::fill(augmentation_g.begin(), augmentation_g.end(), Complex(0.0, 0.0));
120 for (int channel_i = 0; channel_i < number_of_channels; channel_i++) {
121 int diagonal_pair = channel_i * number_of_channels + channel_i;
122 Real diagonal_projector_overlap_coefficient =
123 projector_overlap_coefficients(atom_index, diagonal_pair);
124 if (std::abs(diagonal_projector_overlap_coefficient) >=
126 const Complex* q_g = ultrasoft_basis.q_function_g_data(
127 species_index, diagonal_pair, number_of_g_vectors);
128 for (int g_index = 0; g_index < number_of_g_vectors; g_index++)
129 augmentation_g[g_index] +=
130 diagonal_projector_overlap_coefficient * q_g[g_index];
131 }
132
133 for (int channel_j = channel_i + 1; channel_j < number_of_channels;
134 channel_j++) {
135 int pair_ij = channel_i * number_of_channels + channel_j;
136 int pair_ji = channel_j * number_of_channels + channel_i;
137 Real combined_projector_overlap_coefficient =
138 projector_overlap_coefficients(atom_index, pair_ij) +
139 projector_overlap_coefficients(atom_index, pair_ji);
140 if (std::abs(combined_projector_overlap_coefficient) <
142 continue;
143 const Complex* q_g = ultrasoft_basis.q_function_g_data(
144 species_index, pair_ij, number_of_g_vectors);
145 for (int g_index = 0; g_index < number_of_g_vectors; g_index++)
146 augmentation_g[g_index] +=
147 combined_projector_overlap_coefficient * q_g[g_index];
148 }
149 }
150
151 Vector3D atomic_position =
152 crystal_structure_.convert_from_crystal_to_cartesian(atom_index);
153
154 /**
155 * G = 0 carries no force (G_α is zero there), but *which* index
156 * that is depends on the rank: once G-vectors are partitioned the
157 * local list does not start at G = 0, and
158 * `first_nonzero_g_index()` returns 0 on every rank that does not
159 * own it. Hardcoding 1 silently drops one legitimate G-vector per
160 * rank under band parallelism.
161 */
162 for (int g_index = planewave_basis_.first_nonzero_g_index();
163 g_index < number_of_g_vectors;
164 g_index++) {
165 const auto& g_vector = planewave_basis_.g_vectors()[g_index];
166 Real phase = constants::two_pi * g_vector.g_cart.dot(atomic_position);
167 Complex contribution = std::conj(total_potential_g[g_index]) *
168 augmentation_g[g_index] * compute_phase_factor(phase);
169 Real weight = half_sphere_scale * cell_volume * contribution.imag();
170
171 for (int direction = 0; direction < 3; direction++) {
172 Real g_cartesian_component =
173 g_vector.g_cart[direction] * two_pi_over_lattice_parameter;
174 forces[atom_index][direction] -= g_cartesian_component * weight;
175 }
176 }
177 }
178 }
179
180 return forces;
181 }
182
183 private:
184 const Crystal& crystal_structure_;
185 const PlanewaveBasis& planewave_basis_;
186};
187
188} // namespace q3
Vector3D convert_from_crystal_to_cartesian(int atom_index) const
Definition crystal.hpp:225
Real cell_volume() const
Definition crystal.hpp:193
Real two_pi_over_lattice_parameter() const
Definition crystal.hpp:196
int species_type_index(int atom_index) const
Definition crystal.hpp:273
int number_of_atoms() const
Definition crystal.hpp:209
int number_of_projector_channels(int species_type) const
Definition init.hpp:203
const NormConservingPlanewaveBasis & pseudopotential_basis() const
Definition init.hpp:65
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
bool is_gamma_only() const
Definition init.hpp:157
std::vector< Vector3D > compute(const NormConservingPlanewaveKohnShamQuantumSystem &quantum_system, const std::vector< std::vector< Complex > > &potential_g_by_spin, const std::vector< Matrix< Real > > &coefficients_by_spin)
UltrasoftForceCalculator(const Crystal &crystal_structure, const PlanewaveBasis &planewave_basis)
constexpr Real two_pi
Definition constants.hpp:25
constexpr double d_matrix_element_threshold
double Real
Complex compute_phase_factor(Real phase)
Compute e^{-iφ} = cos(φ) - i·sin(φ) (standard structure factor phase).
std::complex< double > Complex