Loading...
Searching...
No Matches
utilities.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * Shared UPF parsing utilities.
5 *
6 * Low-level helpers used by both UPF v1 and v2 readers for string manipulation,
7 * numeric extraction, XML attribute parsing, and post-read validation.
8 */
9
10#include <cctype>
11#include <cmath>
12#include <cstddef>
13#include <fstream>
14#include <sstream>
15#include <stdexcept>
16#include <string>
17#include <vector>
18
19#include "io/logger.hpp"
22
23namespace q3 {
24namespace upf_utilities {
25
26/// Remove leading and trailing whitespace from a string.
27inline std::string trim(const std::string& input) {
28 size_t first_non_whitespace = input.find_first_not_of(" \t\r\n");
29 if (first_non_whitespace == std::string::npos)
30 return "";
31 size_t last_non_whitespace = input.find_last_not_of(" \t\r\n");
32 return input.substr(first_non_whitespace, last_non_whitespace - first_non_whitespace + 1);
33}
34
35/**
36 * Canonical kind name for a UPF `pseudo_type` header value.
37 *
38 * `Pseudopotential::pseudopotential_type` is documented to hold "NC", "US" or
39 * "PAW", and the rest of the tree relies on that: the reader factory picks the
40 * subclass to construct by comparing against those spellings, and
41 * `pseudopotential_kind_name` renders them into the provenance record. The v2
42 * reader, though, stored the header attribute verbatim, so whether a dataset
43 * got its augmentation apparatus depended on how its generator happened to
44 * spell the field.
45 *
46 * The spellings are not uniform in practice. The UPF header documents "NC",
47 * "SL", "US" and "PAW", but generators also emit "USPP" for an ultrasoft
48 * dataset — a UPF v2 carbon USPP written by the `atomic` code reports
49 * `pseudo_type="USPP"`, and against the exact-match test that read as "not
50 * ultrasoft". The file was then built as a plain norm-conserving
51 * `Pseudopotential`, silently dropping every Q_ij it carried, and the first
52 * code to ask for its augmentation data aborted on the `as_ultrasoft` cast.
53 * Matching on a canonical form rather than one blessed spelling is what keeps
54 * that from being a per-generator lottery.
55 *
56 * An unrecognized value is returned trimmed but otherwise untouched, so it
57 * still lands on the norm-conserving construction path exactly as before and
58 * the original label survives for diagnostics.
59 */
60inline std::string canonical_pseudopotential_type(const std::string& raw_type) {
61 std::string token = trim(raw_type);
62 std::string upper_token = token;
63 for (char& character : upper_token)
64 character = static_cast<char>(std::toupper(static_cast<unsigned char>(character)));
65
66 if (upper_token == "PAW")
67 return "PAW";
68 if (upper_token == "US" || upper_token == "USPP")
69 return "US";
70 // "SL" is the semilocal member of the norm-conserving family.
71 if (upper_token == "NC" || upper_token == "SL")
72 return "NC";
73 return token;
74}
75
76/// Read a specified number of real values from a stream.
77inline std::vector<Real> read_reals(std::ifstream& file_stream, int count) {
78 std::vector<Real> values;
79 values.reserve(count);
80 Real value;
81 while (static_cast<int>(values.size()) < count && file_stream >> value) {
82 values.push_back(value);
83 }
84 file_stream.clear(); // Clear failbit so getline works after hitting non-numeric text
85 return values;
86}
87
88/// Advance the stream until a line containing the given tag is found.
89inline bool find_tag(std::ifstream& file_stream, const std::string& tag) {
90 std::string line;
91 while (std::getline(file_stream, line)) {
92 if (line.find(tag) != std::string::npos)
93 return true;
94 }
95 return false;
96}
97
98/// Extract an XML attribute value from a block of text (e.g. attr="value" or attr='value').
99inline std::string xml_attribute(const std::string& text, const std::string& attribute_name) {
100 std::string patterns[] = {attribute_name + "=\"", attribute_name + "='"};
101 for (auto& pattern : patterns) {
102 auto position = text.find(pattern);
103 if (position != std::string::npos) {
104 position += pattern.size();
105 char delimiter = pattern.back();
106 auto end_position = text.find(delimiter, position);
107 if (end_position != std::string::npos) {
108 return text.substr(position, end_position - position);
109 }
110 }
111 }
112 return "";
113}
114
115/**
116 * Build the error a malformed required attribute produces.
117 *
118 * Named separately so the "missing" and "unparseable" paths report the same
119 * way: both are the same problem from a user's point of view — this file
120 * cannot be read, and here is the field to look at.
121 */
122inline std::runtime_error malformed_attribute_error(const std::string& attribute_name,
123 const std::string& source_description,
124 const std::string& expected_kind,
125 const std::string& found_value) {
126 std::string message = "Malformed UPF file " + source_description +
127 ": required PP_HEADER "
128 "attribute '" +
129 attribute_name + "' ";
130 if (found_value.empty()) {
131 message += "is missing";
132 } else {
133 message += "is not " + expected_kind + " (found \"" + found_value + "\")";
134 }
135 return std::runtime_error(message);
136}
137
138/**
139 * Read a required numeric attribute from a UPF header tag.
140 *
141 * xml_attribute() returns an empty string for an attribute that is not present,
142 * and handing that to the standard conversions yields "stoi: no conversion" —
143 * an error naming neither the file nor the field, which is no help to someone
144 * holding a pseudopotential that will not load. These two helpers name both.
145 *
146 * Trailing characters are rejected rather than ignored: std::stoi("20x0")
147 * happily returns 20, and a mesh size silently truncated from a corrupt
148 * attribute is worse than a refusal.
149 *
150 * Use only for attributes a file genuinely cannot omit. Optional attributes
151 * (number_of_wfc, core_correction) stay tolerant of absence at their call sites.
152 */
153inline int required_integer_attribute(const std::string& text,
154 const std::string& attribute_name,
155 const std::string& source_description) {
156 std::string value = trim(xml_attribute(text, attribute_name));
157 if (value.empty())
158 throw malformed_attribute_error(attribute_name, source_description, "an integer", "");
159
160 std::size_t characters_consumed = 0;
161 int parsed_value = 0;
162 try {
163 parsed_value = std::stoi(value, &characters_consumed);
164 } catch (const std::exception&) {
165 throw malformed_attribute_error(attribute_name, source_description, "an integer", value);
166 }
167 if (!trim(value.substr(characters_consumed)).empty())
168 throw malformed_attribute_error(attribute_name, source_description, "an integer", value);
169 return parsed_value;
170}
171
172/// Read a required real-valued attribute from a UPF header tag.
173inline Real required_real_attribute(const std::string& text,
174 const std::string& attribute_name,
175 const std::string& source_description) {
176 std::string value = trim(xml_attribute(text, attribute_name));
177 if (value.empty())
178 throw malformed_attribute_error(attribute_name, source_description, "a number", "");
179
180 std::size_t characters_consumed = 0;
181 Real parsed_value = 0.0;
182 try {
183 parsed_value = std::stod(value, &characters_consumed);
184 } catch (const std::exception&) {
185 throw malformed_attribute_error(attribute_name, source_description, "a number", value);
186 }
187 if (!trim(value.substr(characters_consumed)).empty())
188 throw malformed_attribute_error(attribute_name, source_description, "a number", value);
189 return parsed_value;
190}
191
192/// Extract numeric data from an XML section body, skipping lines that contain XML tags.
193inline std::vector<Real> xml_data(const std::string& section) {
194 std::vector<Real> values;
195 std::istringstream stream(section);
196 std::string line;
197 while (std::getline(stream, line)) {
198 // Skip XML tags (lines containing < or >)
199 if (line.find('<') != std::string::npos)
200 continue;
201 std::istringstream line_stream(line);
202 Real value;
203 while (line_stream >> value)
204 values.push_back(value);
205 }
206 return values;
207}
208
209/// Validate atomic wavefunction norms and mark zero-norm wavefunctions.
210inline void check_atomic_wavefunction_norm(Pseudopotential& pseudopotential) {
211 constexpr Real zero_norm_threshold = 1.0e-10;
212 for (int wavefunction_index = 0;
213 wavefunction_index < static_cast<int>(pseudopotential.wavefunctions_atomic.size());
214 wavefunction_index++) {
215 auto& wavefunction = pseudopotential.wavefunctions_atomic[wavefunction_index];
216 // Calculate norm of the smooth part: ∫ chi(r)^2 dr
217 std::vector<Real> integrand(pseudopotential.mesh_size);
218 for (int radial_index = 0; radial_index < pseudopotential.mesh_size; radial_index++) {
219 integrand[radial_index] =
220 wavefunction.radial_values[radial_index] * wavefunction.radial_values[radial_index];
221 }
222 Real norm = simpson_rule(
223 integrand, pseudopotential.radial_grid_derivative, pseudopotential.mesh_size);
224 if (norm < zero_norm_threshold) {
225 log::warn(" WARNING: atomic wfc # {} for atom type {} has zero norm",
226 wavefunction_index + 1,
227 pseudopotential.element);
228 wavefunction.occupations = -1.0e-10; // set to a small negative value to skip it
229 }
230 }
231}
232
233} // namespace upf_utilities
234} // namespace q3
Base class for all pseudopotential types.
Definition init.hpp:35
std::vector< Real > radial_grid_derivative
Definition init.hpp:77
std::vector< WavefunctionAtomic > wavefunctions_atomic
Definition init.hpp:82
std::string element
Definition init.hpp:66
void warn(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:187
void check_atomic_wavefunction_norm(Pseudopotential &pseudopotential)
Validate atomic wavefunction norms and mark zero-norm wavefunctions.
std::string trim(const std::string &input)
Remove leading and trailing whitespace from a string.
Definition utilities.hpp:27
Real required_real_attribute(const std::string &text, const std::string &attribute_name, const std::string &source_description)
Read a required real-valued attribute from a UPF header tag.
std::string canonical_pseudopotential_type(const std::string &raw_type)
Definition utilities.hpp:60
std::vector< Real > xml_data(const std::string &section)
Extract numeric data from an XML section body, skipping lines that contain XML tags.
std::vector< Real > read_reals(std::ifstream &file_stream, int count)
Read a specified number of real values from a stream.
Definition utilities.hpp:77
std::runtime_error malformed_attribute_error(const std::string &attribute_name, const std::string &source_description, const std::string &expected_kind, const std::string &found_value)
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
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
int required_integer_attribute(const std::string &text, const std::string &attribute_name, const std::string &source_description)
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