Loading...
Searching...
No Matches
ultrasoft.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * UPF v2 ultrasoft reader.
5 *
6 * Reads the ultrasoft-specific sections of a UPF v2 format file:
7 * PP_Q (precomputed qqq integrals), PP_QIJL (L-resolved augmentation charges
8 * on the radial grid), PP_QIJ.N (fallback total augmentation charges),
9 * and computes missing qqq integrals by numerical integration if needed.
10 *
11 * Must be called after UPFv2Reader has populated the base fields.
12 */
13
14#include <cmath>
15#include <sstream>
16#include <string>
17#include <vector>
18
19#include "io/logger.hpp"
23
24namespace q3 {
25
27 public:
28 UltrasoftUPFv2Reader(UltrasoftPseudopotential& pseudopotential, const std::string& file_content)
29 : pseudopotential_(pseudopotential), content_(file_content) {}
30
31 void read() {
32 int number_of_betas = pseudopotential_.number_of_projectors;
33 int number_of_pairs = number_of_betas * number_of_betas;
34 pseudopotential_.augmentation_function.resize(number_of_pairs);
35 pseudopotential_.augmentation_overlap_integrals.resize(
36 number_of_betas, std::vector<Real>(number_of_betas, 0.0));
37 pseudopotential_.number_of_q_coefficients =
38 0; // UPF v2 uses tabulated Q on mesh, no polynomial coefficients
39
40 read_precomputed_qqq();
41 read_l_resolved_augmentation();
42 read_total_augmentation_fallback();
43 compute_missing_qqq_integrals();
44 }
45
46 private:
47 void read_precomputed_qqq() {
48 using namespace upf_utilities;
49 int number_of_betas = pseudopotential_.number_of_projectors;
50
51 auto position = content_.find("<PP_Q>");
52 if (position == std::string::npos)
53 position = content_.find("<PP_Q ");
54 if (position == std::string::npos)
55 return;
56
57 auto end_position = content_.find("</PP_Q>", position);
58 if (end_position == std::string::npos)
59 return;
60
61 auto tag_end = content_.find(">", position);
62 std::string section = content_.substr(tag_end + 1, end_position - tag_end - 1);
63 std::vector<Real> qqq_flat = xml_data(section);
64 if (static_cast<int>(qqq_flat.size()) >= number_of_betas * number_of_betas) {
65 for (int row = 0; row < number_of_betas; row++) {
66 for (int column = 0; column < number_of_betas; column++) {
67 pseudopotential_.augmentation_overlap_integrals[row][column] =
68 qqq_flat[row * number_of_betas + column];
69 }
70 }
71 }
72 }
73
74 void read_l_resolved_augmentation() {
75 using namespace upf_utilities;
76 int number_of_betas = pseudopotential_.number_of_projectors;
77 pseudopotential_.number_of_q_angular_momenta =
78 2 * pseudopotential_.maximum_angular_momentum + 1;
79 pseudopotential_.augmentation_function_by_angular_momentum.resize(number_of_betas *
80 number_of_betas);
81 for (auto& entry : pseudopotential_.augmentation_function_by_angular_momentum)
82 entry.resize(pseudopotential_.number_of_q_angular_momenta);
83
84 size_t search_position = 0;
85 while ((search_position = content_.find("<PP_QIJL", search_position)) !=
86 std::string::npos) {
87 auto tag_end = content_.find(">", search_position);
88 std::string tag_line = content_.substr(search_position, tag_end - search_position);
89
90 std::string angular_momentum_string = xml_attribute(tag_line, "angular_momentum");
91 std::string first_index_string = xml_attribute(tag_line, "first_index");
92 std::string second_index_string = xml_attribute(tag_line, "second_index");
93 if (first_index_string.empty() || second_index_string.empty() ||
94 angular_momentum_string.empty()) {
95 search_position = tag_end;
96 continue;
97 }
98 int beta_index_1 = std::stoi(first_index_string) - 1; // Convert 1-based to 0-based
99 int beta_index_2 = std::stoi(second_index_string) - 1;
100 int angular_momentum = std::stoi(angular_momentum_string);
101
102 if (beta_index_1 < 0 || beta_index_1 >= number_of_betas || beta_index_2 < 0 ||
103 beta_index_2 >= number_of_betas || angular_momentum < 0 ||
104 angular_momentum >= pseudopotential_.number_of_q_angular_momenta) {
105 search_position = tag_end;
106 continue;
107 }
108
109 // Extract the tag name for closing tag (e.g., PP_QIJL.1.1.0)
110 auto space_position = tag_line.find(" ");
111 std::string tag_name = tag_line.substr(1, space_position - 1);
112 std::string close_tag = "</" + tag_name + ">";
113
114 auto close_position = content_.find(close_tag, search_position);
115 if (close_position != std::string::npos) {
116 std::string section = content_.substr(tag_end + 1, close_position - tag_end - 1);
117 int pair_index = beta_index_1 * number_of_betas + beta_index_2;
118 int symmetric_pair_index = beta_index_2 * number_of_betas + beta_index_1;
119 auto data = xml_data(section);
120 data.resize(pseudopotential_.mesh_size, 0.0);
121 pseudopotential_
122 .augmentation_function_by_angular_momentum[pair_index][angular_momentum] = data;
123 pseudopotential_.augmentation_function_by_angular_momentum[symmetric_pair_index]
124 [angular_momentum] = data;
125
126 // Also populate legacy qfunc with L=0 component
127 if (angular_momentum == 0) {
128 pseudopotential_.augmentation_function[pair_index] = data;
129 pseudopotential_.augmentation_function[symmetric_pair_index] = data;
130 }
131 }
132
133 search_position = tag_end;
134 }
135 }
136
137 void read_total_augmentation_fallback() {
138 using namespace upf_utilities;
139 int number_of_betas = pseudopotential_.number_of_projectors;
140
141 /**
142 * Fall back: PP_QIJ blocks (the `q_with_l="false"` format: one
143 * un-decomposed radial function per (i, j) projector pair, used
144 * instead of the L-resolved PP_QIJL blocks read above).
145 *
146 * The block for pair (i, j) is *not* reliably named `<PP_QIJ.N>`
147 * with N a linear/composite index — real files in the wild name it
148 * `<PP_QIJ.i.j>` with i, j the
149 * 1-based `first_index`/`second_index`, and separately carry the
150 * linear index as a `composite_index` attribute. Guessing the tag
151 * name from a computed index and substring-searching for it — the
152 * previous approach — matches the wrong block for almost every pair
153 * (searching for "<PP_QIJ.2" finds "<PP_QIJ.2.2" before it ever
154 * reaches "<PP_QIJ.1.2"), silently cross-wiring one projector pair's
155 * augmentation charge onto another and breaking charge conservation.
156 * Reading `first_index`/`second_index` as attributes off whatever
157 * tag is actually there — the same approach `read_l_resolved_augmentation`
158 * already uses for PP_QIJL — sidesteps the naming convention entirely.
159 */
160 /**
161 * The trailing period in the search string is what keeps this off
162 * `<PP_QIJL...>` tags (already handled above): a real PP_QIJL tag has
163 * 'L' where this pattern requires '.', so `find` can never land on
164 * one -- no separate exclusion check is needed.
165 */
166 size_t search_position = 0;
167 while ((search_position = content_.find("<PP_QIJ.", search_position)) !=
168 std::string::npos) {
169 auto tag_end = content_.find(">", search_position);
170 std::string tag_line = content_.substr(search_position, tag_end - search_position);
171
172 std::string first_index_string = xml_attribute(tag_line, "first_index");
173 std::string second_index_string = xml_attribute(tag_line, "second_index");
174 /**
175 * Last-resort tolerance of a malformed file, and loudly so.
176 *
177 * The bug this reader exists to fix was a *silent* mis-association
178 * of augmentation data; skipping a block we cannot interpret
179 * without saying so is the same failure with a different cause, so
180 * every skip below names the tag and the reason.
181 */
182 if (first_index_string.empty() || second_index_string.empty()) {
183 log::warn(" PP: skipping augmentation block '{}' -- it carries no "
184 "first_index/second_index attributes to place it by",
185 tag_line);
186 search_position = tag_end;
187 continue;
188 }
189 int beta_index_1 = std::stoi(first_index_string) - 1; // Convert 1-based to 0-based
190 int beta_index_2 = std::stoi(second_index_string) - 1;
191
192 if (beta_index_1 < 0 || beta_index_1 >= number_of_betas || beta_index_2 < 0 ||
193 beta_index_2 >= number_of_betas) {
194 search_position = tag_end;
195 continue;
196 }
197
198 int pair_index = beta_index_1 * number_of_betas + beta_index_2;
199 int symmetric_pair_index = beta_index_2 * number_of_betas + beta_index_1;
200
201 // Extract the tag name for the matching closing tag (e.g. PP_QIJ.1.2)
202 auto space_position = tag_line.find(" ");
203 std::string tag_name = tag_line.substr(1, space_position - 1);
204 std::string close_tag = "</" + tag_name + ">";
205 auto close_position = content_.find(close_tag, search_position);
206 if (close_position == std::string::npos) {
207 search_position = tag_end;
208 continue;
209 }
210
211 if (pseudopotential_.augmentation_function[pair_index].empty()) {
212 // Not already loaded from PP_QIJL
213 std::string section = content_.substr(tag_end + 1, close_position - tag_end - 1);
214 pseudopotential_.augmentation_function[pair_index] = xml_data(section);
215 pseudopotential_.augmentation_function[pair_index].resize(
216 pseudopotential_.mesh_size, 0.0);
217 pseudopotential_.augmentation_function[symmetric_pair_index] =
218 pseudopotential_.augmentation_function[pair_index];
219 }
220
221 // Get Q_int from attributes if PP_Q was not found
222 if (pseudopotential_.augmentation_overlap_integrals[beta_index_1][beta_index_2] ==
223 0.0) {
224 std::string q_integral_string = xml_attribute(tag_line, "q_int");
225 if (!q_integral_string.empty()) {
226 pseudopotential_.augmentation_overlap_integrals[beta_index_1][beta_index_2] =
227 std::stod(q_integral_string);
228 pseudopotential_.augmentation_overlap_integrals[beta_index_2][beta_index_1] =
229 pseudopotential_.augmentation_overlap_integrals[beta_index_1][beta_index_2];
230 }
231 }
232
233 search_position = tag_end;
234 }
235 }
236
237 void compute_missing_qqq_integrals() {
238 int number_of_betas = pseudopotential_.number_of_projectors;
239
240 // Last resort: compute qqq by integrating Q(r) if still zero
241 for (int beta_index_1 = 0; beta_index_1 < number_of_betas; beta_index_1++) {
242 for (int beta_index_2 = beta_index_1; beta_index_2 < number_of_betas; beta_index_2++) {
243 if (pseudopotential_.augmentation_overlap_integrals[beta_index_1][beta_index_2] ==
244 0.0 &&
245 !pseudopotential_
246 .augmentation_function[beta_index_1 * number_of_betas + beta_index_2]
247 .empty()) {
248 /**
249 * qfunc from PP_QIJL already contains r² factor,
250 * so qqq = ∫ qfunc * radial_grid_derivative dr (no extra r²)
251 */
252 Real integral = simpson_rule(
253 pseudopotential_
254 .augmentation_function[beta_index_1 * number_of_betas + beta_index_2],
255 pseudopotential_.radial_grid_derivative,
256 pseudopotential_.mesh_size);
257 pseudopotential_.augmentation_overlap_integrals[beta_index_1][beta_index_2] =
258 integral;
259 pseudopotential_.augmentation_overlap_integrals[beta_index_2][beta_index_1] =
260 integral;
261 }
262 }
263 }
264 }
265
266 UltrasoftPseudopotential& pseudopotential_;
267 const std::string& content_;
268};
269
270} // namespace q3
std::vector< Real > radial_grid_derivative
Definition init.hpp:77
int number_of_projectors
Definition init.hpp:72
int maximum_angular_momentum
Definition init.hpp:70
Ultrasoft pseudopotential — extends base with augmentation data.
Definition init.hpp:27
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
UltrasoftUPFv2Reader(UltrasoftPseudopotential &pseudopotential, const std::string &file_content)
Definition ultrasoft.hpp:28
void section(const std::string &title)
Definition logger.hpp:206
void warn(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:187
std::vector< Real > xml_data(const std::string &section)
Extract numeric data from an XML section body, skipping lines that contain XML tags.
std::string xml_attribute(const std::string &text, const std::string &attribute_name)
Extract an XML attribute value from a block of text (e.g. attr="value" or attr='value').
Definition utilities.hpp:99
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