Loading...
Searching...
No Matches
ultrasoft.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * UPF v1 ultrasoft reader.
5 *
6 * Reads the ultrasoft-specific sections of a UPF v1 format file:
7 * PP_QIJ (augmentation charges Q_ij), PP_RINNER (the L-dependent inner radii
8 * r_L), PP_QFCOEF (polynomial coefficients), and rebuilds the L-resolved
9 * augmentation functions from the tabulated Q_ij and those coefficients.
10 *
11 * That rebuild is what the record layout means rather than an extra step.
12 * PP_QIJ tabulates Q_ij(r) correctly only outside r_L; inside r_L it carries
13 * the generator's minimal-L form only, and PP_QFCOEF exists so the correct
14 * form can be recovered for each admissible L. The file states the rule on
15 * its own nqf line: "If not zero, Qij's inside rinner are computed using
16 * qfcoef's". See Vanderbilt, Phys. Rev. B 41, 7892 (1990) for the underlying
17 * decomposition of Q_ij into total-angular-momentum channels.
18 *
19 * Must be called after UPFv1Reader has populated the base fields.
20 */
21
22#include <cmath>
23#include <fstream>
24#include <sstream>
25#include <string>
26#include <vector>
27
28#include "io/logger.hpp"
31
32namespace q3 {
33
35 public:
36 UltrasoftUPFv1Reader(UltrasoftPseudopotential& pseudopotential, std::ifstream& file_stream)
37 : pseudopotential_(pseudopotential), file_stream_(file_stream) {}
38
39 void read() {
40 log::info(" PP {}: Ultrasoft pseudopotential", pseudopotential_.element);
41
42 read_qij_header();
43 read_rinner();
44 read_qij_data();
45 reconstruct_l_resolved_qfuncl();
46 }
47
48 private:
49 void read_qij_header() {
50 using namespace upf_utilities;
51 file_stream_.clear();
52 file_stream_.seekg(0);
53 if (!find_tag(file_stream_, "<PP_QIJ>"))
54 throw std::runtime_error("PP_QIJ section not found for ultrasoft PP");
55
56 std::string header_line;
57 std::getline(file_stream_, header_line);
58 {
59 std::istringstream stream(header_line);
60 stream >> pseudopotential_.number_of_q_coefficients;
61 }
62
63 int number_of_betas = pseudopotential_.number_of_projectors;
64 pseudopotential_.augmentation_function.resize(number_of_betas * number_of_betas);
65 pseudopotential_.augmentation_overlap_integrals.resize(
66 number_of_betas, std::vector<Real>(number_of_betas, 0.0));
67 pseudopotential_.augmentation_coefficients.resize(number_of_betas * number_of_betas);
68 pseudopotential_.inner_cutoff_radii.resize(
69 2 * pseudopotential_.maximum_angular_momentum + 1, 0.0);
70 }
71
72 void read_rinner() {
73 using namespace upf_utilities;
74 if (pseudopotential_.number_of_q_coefficients <= 0)
75 return;
76
77 file_stream_.clear();
78 file_stream_.seekg(0);
79 if (find_tag(file_stream_, "<PP_RINNER>")) {
80 int number_of_rinner_entries = 2 * pseudopotential_.maximum_angular_momentum + 1;
81 for (int angular_momentum = 0; angular_momentum < number_of_rinner_entries;
82 angular_momentum++) {
83 int angular_momentum_index;
84 Real inner_radius;
85 file_stream_ >> angular_momentum_index >> inner_radius;
86 pseudopotential_.inner_cutoff_radii[angular_momentum] = inner_radius;
87 }
88 file_stream_.clear();
89 }
90 }
91
92 void read_qij_data() {
93 using namespace upf_utilities;
94 /**
95 * Read all lines from the QIJ section, stripping XML tags and marking QFCOEF boundaries.
96 * This collects the raw data for sequential parsing of Q_ij pairs.
97 */
98 file_stream_.clear();
99 file_stream_.seekg(0);
100 find_tag(file_stream_, "<PP_QIJ>");
101
102 std::vector<std::string> qij_lines;
103 {
104 std::string line;
105 std::getline(file_stream_, line); // skip nqf line
106 bool inside_rinner_section = false;
107 while (std::getline(file_stream_, line)) {
108 if (line.find("</PP_QIJ>") != std::string::npos)
109 break;
110 if (line.find("<PP_RINNER>") != std::string::npos) {
111 inside_rinner_section = true;
112 continue;
113 }
114 if (line.find("</PP_RINNER>") != std::string::npos) {
115 inside_rinner_section = false;
116 continue;
117 }
118 if (inside_rinner_section)
119 continue;
120 if (line.find("<PP_QFCOEF>") != std::string::npos) {
121 qij_lines.push_back("__QFCOEF_BEGIN__");
122 continue;
123 }
124 if (line.find("</PP_QFCOEF>") != std::string::npos) {
125 qij_lines.push_back("__QFCOEF_END__");
126 continue;
127 }
128 qij_lines.push_back(line);
129 }
130 }
131
132 // Parse the collected lines sequentially
133 size_t line_index = 0;
134 auto next_non_empty_line = [&]() -> std::string {
135 while (line_index < qij_lines.size()) {
136 std::string trimmed = trim(qij_lines[line_index++]);
137 if (!trimmed.empty())
138 return trimmed;
139 }
140 return "";
141 };
142 auto read_reals_from_lines = [&](int count) -> std::vector<Real> {
143 std::vector<Real> values;
144 values.reserve(count);
145 while (static_cast<int>(values.size()) < count && line_index < qij_lines.size()) {
146 const std::string& raw = qij_lines[line_index];
147 if (raw == "__QFCOEF_BEGIN__" || raw == "__QFCOEF_END__") {
148 line_index++;
149 continue;
150 }
151 std::istringstream stream(raw);
152 Real value;
153 while (static_cast<int>(values.size()) < count && stream >> value) {
154 values.push_back(value);
155 }
156 line_index++;
157 }
158 return values;
159 };
160
161 int number_of_betas = pseudopotential_.number_of_projectors;
162 for (int beta_index_1 = 0; beta_index_1 < number_of_betas; beta_index_1++) {
163 for (int beta_index_2 = beta_index_1; beta_index_2 < number_of_betas; beta_index_2++) {
164 // Header line: i, j, l(j) (1-based indices) — not used
165 next_non_empty_line();
166
167 // Q_int line
168 std::string q_integral_line = next_non_empty_line();
169 {
170 std::istringstream stream(q_integral_line);
171 stream >>
172 pseudopotential_.augmentation_overlap_integrals[beta_index_1][beta_index_2];
173 pseudopotential_.augmentation_overlap_integrals[beta_index_2][beta_index_1] =
174 pseudopotential_.augmentation_overlap_integrals[beta_index_1][beta_index_2];
175 }
176
177 int pair_index = beta_index_1 * number_of_betas + beta_index_2;
178 int symmetric_pair_index = beta_index_2 * number_of_betas + beta_index_1;
179
180 // Q_ij(r) on the radial grid
181 pseudopotential_.augmentation_function[pair_index] =
182 read_reals_from_lines(pseudopotential_.mesh_size);
183 pseudopotential_.augmentation_function[symmetric_pair_index] =
184 pseudopotential_.augmentation_function[pair_index];
185
186 // Q function polynomial coefficients (inside PP_QFCOEF markers)
187 if (pseudopotential_.number_of_q_coefficients > 0) {
188 // Skip __QFCOEF_BEGIN__ marker
189 while (line_index < qij_lines.size() &&
190 qij_lines[line_index] == "__QFCOEF_BEGIN__")
191 line_index++;
192
193 pseudopotential_.augmentation_coefficients[pair_index].resize(
194 2 * pseudopotential_.maximum_angular_momentum + 1);
195 for (int angular_momentum = 0;
196 angular_momentum < 2 * pseudopotential_.maximum_angular_momentum + 1;
197 angular_momentum++) {
198 pseudopotential_.augmentation_coefficients[pair_index][angular_momentum] =
199 read_reals_from_lines(pseudopotential_.number_of_q_coefficients);
200 }
201 pseudopotential_.augmentation_coefficients[symmetric_pair_index] =
202 pseudopotential_.augmentation_coefficients[pair_index];
203
204 // Skip __QFCOEF_END__ marker
205 while (line_index < qij_lines.size() &&
206 qij_lines[line_index] == "__QFCOEF_END__")
207 line_index++;
208 }
209 }
210 }
211
212 log::info(" PP {}: {} Q_ij pairs read, nqf={}",
213 pseudopotential_.element,
214 number_of_betas * (number_of_betas + 1) / 2,
215 pseudopotential_.number_of_q_coefficients);
216 }
217
218 void reconstruct_l_resolved_qfuncl() {
219 /**
220 * Decompose the tabulated Q_ij(r) into its L-resolved channels.
221 *
222 * For each (nb1, nb2) pair and each L the triangle rule admits
223 * (L = |l1-l2|, |l1-l2|+2, ... , l1+l2):
224 * 1) Copy the tabulated Q_ij(r) into channel L. Outside r_L that
225 * function is already correct and does not depend on L.
226 * 2) If rinner[L] > 0, replace the points inside r_L with the
227 * polynomial PP_QFCOEF stores for that channel.
228 *
229 * A file with nqf = 0 carries no PP_QFCOEF block, so there is nothing
230 * to decompose with: this returns early and leaves the per-L array
231 * empty. That is not a missing input downstream —
232 * charge_density_augmentation.hpp reuses the single tabulated Q_ij(r)
233 * at *every* admissible L for such files, which is where their L > 0
234 * augmentation comes from.
235 */
236 int number_of_betas = pseudopotential_.number_of_projectors;
237 pseudopotential_.number_of_q_angular_momenta =
238 2 * pseudopotential_.maximum_angular_momentum + 1;
239
240 if (pseudopotential_.number_of_q_coefficients <= 0)
241 return;
242
243 pseudopotential_.augmentation_function_by_angular_momentum.resize(number_of_betas *
244 number_of_betas);
245 for (auto& entry : pseudopotential_.augmentation_function_by_angular_momentum)
246 entry.resize(pseudopotential_.number_of_q_angular_momenta);
247
248 for (int beta_index_1 = 0; beta_index_1 < number_of_betas; beta_index_1++) {
249 for (int beta_index_2 = beta_index_1; beta_index_2 < number_of_betas; beta_index_2++) {
250 int pair_index = beta_index_1 * number_of_betas + beta_index_2;
251 int symmetric_pair_index = beta_index_2 * number_of_betas + beta_index_1;
252 int angular_momentum_1 = pseudopotential_.projectors[beta_index_1].angular_momentum;
253 int angular_momentum_2 = pseudopotential_.projectors[beta_index_2].angular_momentum;
254
255 // Skip if total qfunc not available for this pair
256 if (pair_index >= static_cast<int>(pseudopotential_.augmentation_function.size()) ||
257 pseudopotential_.augmentation_function[pair_index].empty())
258 continue;
259
260 // Copy total qfunc to each valid L channel
261 for (int angular_momentum = std::abs(angular_momentum_1 - angular_momentum_2);
262 angular_momentum <= angular_momentum_1 + angular_momentum_2;
263 angular_momentum += 2) {
264 if (angular_momentum >= pseudopotential_.number_of_q_angular_momenta)
265 continue;
266 pseudopotential_
267 .augmentation_function_by_angular_momentum[pair_index][angular_momentum] =
268 pseudopotential_.augmentation_function[pair_index];
269 if (symmetric_pair_index != pair_index)
270 pseudopotential_
271 .augmentation_function_by_angular_momentum[symmetric_pair_index]
272 [angular_momentum] =
273 pseudopotential_.augmentation_function[pair_index];
274 }
275
276 // Replace the points inside r_L with the PP_QFCOEF polynomial
277 if (pair_index <
278 static_cast<int>(pseudopotential_.augmentation_coefficients.size()) &&
279 !pseudopotential_.augmentation_coefficients[pair_index].empty()) {
280 for (int angular_momentum = std::abs(angular_momentum_1 - angular_momentum_2);
281 angular_momentum <= angular_momentum_1 + angular_momentum_2;
282 angular_momentum += 2) {
283 if (angular_momentum >= pseudopotential_.number_of_q_angular_momenta)
284 continue;
285 if (angular_momentum >=
286 static_cast<int>(pseudopotential_.inner_cutoff_radii.size()) ||
287 pseudopotential_.inner_cutoff_radii[angular_momentum] <= 0.0)
288 continue;
289 if (angular_momentum >=
290 static_cast<int>(
291 pseudopotential_.augmentation_coefficients[pair_index].size()))
292 continue;
293 if (static_cast<int>(
294 pseudopotential_
295 .augmentation_coefficients[pair_index][angular_momentum]
296 .size()) < pseudopotential_.number_of_q_coefficients)
297 continue;
298
299 // Find last grid point inside rinner[L]
300 int last_inner_point = 0;
301 for (int radial_index = 0; radial_index < pseudopotential_.mesh_size;
302 radial_index++) {
303 if (pseudopotential_.radial_grid[radial_index] <
304 pseudopotential_.inner_cutoff_radii[angular_momentum]) {
305 last_inner_point = radial_index;
306 }
307 }
308
309 /**
310 * The polynomial PP_QFCOEF stores for this channel:
311 *
312 * Q_L(r) = r^(L+2) * Σ_i qfcoef[i] * r^(2*(i-1))
313 *
314 * The exponent is L+2, not L: r^L is the multipole's
315 * leading behaviour and the extra r^2 is this
316 * format's storage convention, since the tabulated
317 * radial functions are r*psi(r) and a product of two
318 * of them carries r^2. Consumers integrate these
319 * against j_L(qr) with no further r^2 — see
320 * charge_density_augmentation.hpp.
321 */
322 const auto& coefficients =
323 pseudopotential_
324 .augmentation_coefficients[pair_index][angular_momentum];
325 for (int radial_index = 0; radial_index <= last_inner_point;
326 radial_index++) {
327 Real radius_squared = pseudopotential_.radial_grid[radial_index] *
328 pseudopotential_.radial_grid[radial_index];
329 Real polynomial_value = coefficients[0];
330 for (int coefficient_index = 1;
331 coefficient_index < pseudopotential_.number_of_q_coefficients;
332 coefficient_index++) {
333 polynomial_value +=
334 coefficients[coefficient_index] *
335 std::pow(radius_squared, static_cast<Real>(coefficient_index));
336 }
337 // Multiply by r^(L+2) to get Q_L(r) on the grid
338 Real q_value = polynomial_value *
339 std::pow(pseudopotential_.radial_grid[radial_index],
340 angular_momentum + 2);
342 [pair_index][angular_momentum][radial_index] = q_value;
343 if (symmetric_pair_index != pair_index)
345 [symmetric_pair_index][angular_momentum][radial_index] =
346 q_value;
347 }
348 }
349 }
350 }
351 }
352 log::info(" PP {}: L-resolved qfuncl reconstructed from qfcoef (nqlc={})",
353 pseudopotential_.element,
354 pseudopotential_.number_of_q_angular_momenta);
355 }
356
357 UltrasoftPseudopotential& pseudopotential_;
358 std::ifstream& file_stream_;
359};
360
361} // namespace q3
std::vector< Projector > projectors
Definition init.hpp:80
int number_of_projectors
Definition init.hpp:72
std::string element
Definition init.hpp:66
std::vector< Real > radial_grid
Definition init.hpp:76
int maximum_angular_momentum
Definition init.hpp:70
Ultrasoft pseudopotential — extends base with augmentation data.
Definition init.hpp:27
std::vector< std::vector< std::vector< Real > > > augmentation_coefficients
Definition init.hpp:52
std::vector< Real > inner_cutoff_radii
Definition init.hpp:51
std::vector< std::vector< Real > > augmentation_overlap_integrals
Definition init.hpp:49
std::vector< std::vector< std::vector< Real > > > augmentation_function_by_angular_momentum
Definition init.hpp:47
std::vector< std::vector< Real > > augmentation_function
Definition init.hpp:45
UltrasoftUPFv1Reader(UltrasoftPseudopotential &pseudopotential, std::ifstream &file_stream)
Definition ultrasoft.hpp:36
void info(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:183
std::string trim(const std::string &input)
Remove leading and trailing whitespace from a string.
Definition utilities.hpp:27
bool find_tag(std::ifstream &file_stream, const std::string &tag)
Advance the stream until a line containing the given tag is found.
Definition utilities.hpp:89
double Real