Loading...
Searching...
No Matches
atomic_orbital_builder.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * AtomicOrbitalGSpaceBuilder — shared construction of atomic pseudo-orbitals in G-space.
5 *
6 * Builds |φ_Ilm(k+G)⟩ = i^l · S_I(k+G) · Y_lm(k+G) · χ_nl(|k+G|) for every
7 * occupied atomic wavefunction (n,l) on every atom I and every m in the shell.
8 * Every factor is one factor of the Rayleigh expansion
9 * e^{iq·r} = 4π Σ_lm i^l j_l(qr) Y*_lm(q̂) Y_lm(r̂)
10 * applied to a spherical function centred at τ_I: i^l and Y_lm from the
11 * expansion, S_I = e^{-i(k+G)·τ_I} from the displacement, χ_nl from the radial
12 * transform tabulated in wavefunction_atomic.hpp. This is the construction
13 * shared by two otherwise-unrelated consumers:
14 * - The SCF starting-guess (NormConservingPlanewaveWavefunctionInitializer),
15 * which pads the orbital set with random vectors up to the band count.
16 * - PDOS / Löwdin projection (layer_5_properties/projections), which needs
17 * the orbitals themselves (no padding) plus stable per-orbital labels.
18 * Extracted here so both consumers call one proven implementation instead of
19 * two independently-maintained copies of the same physics.
20 */
21
22#include <cmath>
23#include <complex>
24#include <string>
25#include <vector>
26
35
36namespace q3 {
37
38/// Identifies one atomic orbital in the ordering produced by build_atomic_orbitals_g_space().
42 std::string shell_label; // e.g. "3D", the UPF PP_CHI label verbatim
44 int magnetic_quantum_number; // m, 0..2l in the same order as Y_lm storage (l² + m)
45 /**
46 * The dataset's own occupancy for this shell.
47 *
48 * Carried so a shell can be selected without a label: UPF makes PP_CHI's
49 * label optional and PseudoDojo's PAW files omit it, leaving `l` and this
50 * as the only distinguishing information. A Hubbard U applies to the
51 * partially-filled valence shell, which is the one with the occupancy.
52 */
54 /**
55 * The shell's 0-based position among its species' atomic wavefunctions, as the file lists
56 * them.
57 */
59};
60
61/**
62 * Total number of atomic orbitals: sum of (2l+1) over every occupied UPF PP_CHI
63 * shell, over every atom.
64 *
65 * "Occupied" is the UPF file-format convention, not a physical statement: a
66 * negative PP_CHI occupancy marks a shell that is not to be used as a starting
67 * orbital, and the UPF reader writes that marker itself for any shell whose
68 * radial function has zero norm. Any code reading the same file gets the same
69 * count, which is why this agrees with what other plane-wave codes report.
70 */
71[[nodiscard]] inline int count_atomic_orbitals(const Crystal& crystal_structure,
72 const PseudopotentialSet& pseudopotential_set) {
73 int number_of_atomic_orbitals = 0;
74 for (int atom_index = 0; atom_index < crystal_structure.number_of_atoms(); atom_index++) {
75 int type_index = crystal_structure.species_type_index(atom_index);
76 const auto& pseudopotential = pseudopotential_set[type_index];
77 for (const auto& atomic_wavefunction : pseudopotential.wavefunctions_atomic) {
78 if (atomic_wavefunction.occupations >= 0.0) {
79 number_of_atomic_orbitals += 2 * atomic_wavefunction.angular_momentum + 1;
80 }
81 }
82 }
83 return number_of_atomic_orbitals;
84}
85
86/**
87 * Per-orbital labels, in the same atom-major / shell-major / m-minor order as
88 * build_atomic_orbitals_g_space() fills its output.
89 */
90[[nodiscard]] inline std::vector<AtomicOrbitalLabel>
91enumerate_atomic_orbitals(const Crystal& crystal_structure,
92 const PseudopotentialSet& pseudopotential_set) {
93 std::vector<AtomicOrbitalLabel> labels;
94 labels.reserve(count_atomic_orbitals(crystal_structure, pseudopotential_set));
95 for (int atom_index = 0; atom_index < crystal_structure.number_of_atoms(); atom_index++) {
96 int type_index = crystal_structure.species_type_index(atom_index);
97 const auto& pseudopotential = pseudopotential_set[type_index];
98 const auto& shells = pseudopotential.wavefunctions_atomic;
99 for (std::size_t shell_index = 0; shell_index < shells.size(); shell_index++) {
100 const auto& atomic_wavefunction = shells[shell_index];
101 if (atomic_wavefunction.occupations < 0.0)
102 continue;
103 int angular_momentum = atomic_wavefunction.angular_momentum;
104 for (int magnetic_index = 0; magnetic_index < 2 * angular_momentum + 1;
105 magnetic_index++) {
106 labels.push_back({atom_index,
107 type_index,
108 atomic_wavefunction.label,
109 angular_momentum,
110 magnetic_index,
111 atomic_wavefunction.occupations,
112 static_cast<int>(shell_index)});
113 }
114 }
115 }
116 return labels;
117}
118
119/**
120 * Build G-space atomic orbitals for one k-point: orbitals[orbital_index][g_index],
121 * exactly count_atomic_orbitals(...) rows (no padding), ordered identically to
122 * enumerate_atomic_orbitals(). `atomic_wavefunction_table` is the q-space radial
123 * table from tabulate_atomic_wavefunctions_in_q_space().
124 */
126 int kpoint_index,
127 int number_of_plane_waves,
128 const std::vector<int>& g_vector_indices,
129 const Crystal& crystal_structure,
130 const KPointsGrid& kpoint_grid,
131 const PlanewaveBasis& planewave_basis,
132 const PseudopotentialSet& pseudopotential_set,
133 const std::vector<std::vector<std::vector<Real>>>& atomic_wavefunction_table,
134 Real q_spacing,
135 int number_of_q_points,
136 std::vector<std::vector<Complex>>& orbitals) {
137 Real two_pi_over_lattice_parameter = crystal_structure.two_pi_over_lattice_parameter();
138 Vector3D kpoint_crystal = kpoint_grid[kpoint_index].coordinates;
139
140 const Matrix3x3& reciprocal_lattice = crystal_structure.reciprocal_lattice_vectors();
141 Vector3D kpoint_cartesian;
142 for (int dimension = 0; dimension < 3; dimension++) {
143 kpoint_cartesian[dimension] = kpoint_crystal.x * reciprocal_lattice[0][dimension] +
144 kpoint_crystal.y * reciprocal_lattice[1][dimension] +
145 kpoint_crystal.z * reciprocal_lattice[2][dimension];
146 }
147
148 int maximum_angular_momentum = 0;
149 for (int species_index = 0; species_index < pseudopotential_set.size(); species_index++) {
150 for (const auto& atomic_wavefunction :
151 pseudopotential_set[species_index].wavefunctions_atomic) {
152 if (atomic_wavefunction.occupations >= 0.0) {
153 maximum_angular_momentum =
154 std::max(maximum_angular_momentum, atomic_wavefunction.angular_momentum);
155 }
156 }
157 }
158 int number_of_lm_components = (maximum_angular_momentum + 1) * (maximum_angular_momentum + 1);
159
160 std::vector<std::array<Real, 3>> gk_vectors(number_of_plane_waves);
161 std::vector<Real> q_plus_g_magnitude_squared(number_of_plane_waves);
162 std::vector<Real> q_plus_g_magnitude(number_of_plane_waves);
163
164 for (int g_index = 0; g_index < number_of_plane_waves; g_index++) {
165 const auto& g_vector = planewave_basis.g_vectors()[g_vector_indices[g_index]];
166 gk_vectors[g_index][0] = kpoint_cartesian.x + g_vector.g_cart.x;
167 gk_vectors[g_index][1] = kpoint_cartesian.y + g_vector.g_cart.y;
168 gk_vectors[g_index][2] = kpoint_cartesian.z + g_vector.g_cart.z;
169 q_plus_g_magnitude_squared[g_index] = gk_vectors[g_index][0] * gk_vectors[g_index][0] +
170 gk_vectors[g_index][1] * gk_vectors[g_index][1] +
171 gk_vectors[g_index][2] * gk_vectors[g_index][2];
172 q_plus_g_magnitude[g_index] =
173 std::sqrt(q_plus_g_magnitude_squared[g_index]) * two_pi_over_lattice_parameter;
174 }
175
176 std::vector<std::vector<Real>> spherical_harmonics_values;
177 compute_real_spherical_harmonics_batch(number_of_lm_components,
178 number_of_plane_waves,
179 gk_vectors,
180 q_plus_g_magnitude_squared,
181 spherical_harmonics_values);
182
183 int number_of_species = static_cast<int>(pseudopotential_set.size());
184 // atomic_wavefunction_values[g_index][type_index][wavefunction_index]
185 std::vector<std::vector<std::vector<Real>>> atomic_wavefunction_values(number_of_plane_waves);
186 for (int g_index = 0; g_index < number_of_plane_waves; g_index++) {
187 atomic_wavefunction_values[g_index].resize(0);
188 for (int type_index = 0; type_index < number_of_species; type_index++) {
189 int number_of_wavefunctions_for_type =
190 static_cast<int>(pseudopotential_set[type_index].wavefunctions_atomic.size());
191 std::vector<Real> atomic_wavefunction_values_for_type(number_of_wavefunctions_for_type,
192 0.0);
193 for (int wavefunction_index = 0; wavefunction_index < number_of_wavefunctions_for_type;
194 wavefunction_index++) {
195 if (pseudopotential_set[type_index]
196 .wavefunctions_atomic[wavefunction_index]
197 .occupations < 0.0)
198 continue;
199 atomic_wavefunction_values_for_type[wavefunction_index] =
201 atomic_wavefunction_table[type_index][wavefunction_index],
202 q_plus_g_magnitude[g_index],
203 q_spacing,
204 number_of_q_points);
205 }
206 atomic_wavefunction_values[g_index].push_back(
207 std::move(atomic_wavefunction_values_for_type));
208 }
209 }
210
211 int number_of_atomic_orbitals = count_atomic_orbitals(crystal_structure, pseudopotential_set);
212 orbitals.assign(number_of_atomic_orbitals,
213 std::vector<Complex>(number_of_plane_waves, Complex(0.0, 0.0)));
214
215 int orbital_index = 0;
216 for (int atom_index = 0; atom_index < crystal_structure.number_of_atoms(); atom_index++) {
217 Vector3D atomic_position_cartesian =
218 crystal_structure.convert_from_crystal_to_cartesian(atom_index); // units of alat
219 int type_index = crystal_structure.species_type_index(atom_index);
220 const auto& pseudopotential = pseudopotential_set[type_index];
221
222 Real phase_argument = (kpoint_cartesian.x * atomic_position_cartesian.x +
223 kpoint_cartesian.y * atomic_position_cartesian.y +
224 kpoint_cartesian.z * atomic_position_cartesian.z) *
226 Complex kpoint_phase = compute_phase_factor(phase_argument);
227
228 std::vector<Complex> structure_factor(number_of_plane_waves);
229 for (int g_index = 0; g_index < number_of_plane_waves; g_index++) {
230 const auto& g_vector = planewave_basis.g_vectors()[g_vector_indices[g_index]];
231 Real phase_g = constants::two_pi * (g_vector.g_cart.x * atomic_position_cartesian.x +
232 g_vector.g_cart.y * atomic_position_cartesian.y +
233 g_vector.g_cart.z * atomic_position_cartesian.z);
234 structure_factor[g_index] = kpoint_phase * compute_phase_factor(phase_g);
235 }
236
237 for (int wavefunction_index = 0;
238 wavefunction_index < static_cast<int>(pseudopotential.wavefunctions_atomic.size());
239 wavefunction_index++) {
240 if (pseudopotential.wavefunctions_atomic[wavefunction_index].occupations < 0.0)
241 continue;
242
243 int angular_momentum =
244 pseudopotential.wavefunctions_atomic[wavefunction_index].angular_momentum;
245 // i^l phase factor (Condon-Shortley)
246 Complex angular_momentum_phase;
247 switch (angular_momentum % 4) {
248 case 0:
249 angular_momentum_phase = Complex(1.0, 0.0);
250 break;
251 case 1:
252 angular_momentum_phase = Complex(0.0, 1.0);
253 break;
254 case 2:
255 angular_momentum_phase = Complex(-1.0, 0.0);
256 break;
257 default:
258 angular_momentum_phase = Complex(0.0, -1.0);
259 break;
260 }
261
262 for (int magnetic_index = 0; magnetic_index < 2 * angular_momentum + 1;
263 magnetic_index++) {
264 int combined_lm_index = angular_momentum * angular_momentum + magnetic_index;
265 for (int g_index = 0; g_index < number_of_plane_waves; g_index++) {
266 orbitals[orbital_index][g_index] =
267 angular_momentum_phase * structure_factor[g_index] *
268 Complex(
269 spherical_harmonics_values[g_index][combined_lm_index] *
270 atomic_wavefunction_values[g_index][type_index][wavefunction_index],
271 0.0);
272 }
273 orbital_index++;
274 }
275 }
276 }
277}
278
279} // namespace q3
Vector3D convert_from_crystal_to_cartesian(int atom_index) const
Definition crystal.hpp:225
Real two_pi_over_lattice_parameter() const
Definition crystal.hpp:196
const Matrix3x3 & reciprocal_lattice_vectors() const
Definition crystal.hpp:205
int species_type_index(int atom_index) const
Definition crystal.hpp:273
int number_of_atoms() const
Definition crystal.hpp:209
const std::vector< GVector > & g_vectors() const
Definition init.hpp:148
void push_back(std::unique_ptr< Pseudopotential > pseudopotential)
Move a unique_ptr into the set.
Definition set.hpp:39
int size() const
Definition set.hpp:98
constexpr Real two_pi
Definition constants.hpp:25
void compute_real_spherical_harmonics_batch(int number_of_lm_components, int number_of_g_vectors, const std::vector< std::array< Real, 3 > > &g_vectors, const std::vector< Real > &g_squared, std::vector< std::vector< Real > > &spherical_harmonics_values)
void build_atomic_orbitals_g_space(int kpoint_index, int number_of_plane_waves, const std::vector< int > &g_vector_indices, const Crystal &crystal_structure, const KPointsGrid &kpoint_grid, const PlanewaveBasis &planewave_basis, const PseudopotentialSet &pseudopotential_set, const std::vector< std::vector< std::vector< Real > > > &atomic_wavefunction_table, Real q_spacing, int number_of_q_points, std::vector< std::vector< Complex > > &orbitals)
int count_atomic_orbitals(const Crystal &crystal_structure, const PseudopotentialSet &pseudopotential_set)
double Real
Complex compute_phase_factor(Real phase)
Compute e^{-iφ} = cos(φ) - i·sin(φ) (standard structure factor phase).
std::vector< AtomicOrbitalLabel > enumerate_atomic_orbitals(const Crystal &crystal_structure, const PseudopotentialSet &pseudopotential_set)
std::complex< double > Complex
Real lagrange_interpolation_4point(const std::vector< Real > &table, Real q_value, Real q_spacing, int table_size)
Definition lagrange.hpp:31
Identifies one atomic orbital in the ordering produced by build_atomic_orbitals_g_space().
3×3 matrix stored row-major: row i = mat[i]