Loading...
Searching...
No Matches
paw.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * UPF v2 PAW reader.
5 *
6 * Reads the PAW-specific sections of a UPF v2 file:
7 *
8 * PP_FULL_WFC → PP_AEWFC.N (all-electron partial waves) and
9 * PP_PSWFC.N (pseudo partial waves)
10 * PP_PAW → PP_OCCUPATIONS, PP_AE_NLCC (all-electron core density),
11 * PP_AE_VLOC (all-electron local potential)
12 * PP_MULTIPOLES → multipole moments of the augmentation channels
13 * PP_AUGMENTATION attributes → shape, cutoff radius and index, l_max
14 *
15 * Must run after UPFv2Reader and UltrasoftUPFv2Reader have populated the base
16 * and augmentation fields: a PAW dataset carries the ultrasoft augmentation
17 * apparatus verbatim, so PP_Q and PP_QIJL are read by the ultrasoft reader.
18 *
19 * Note the tag collision this reader has to navigate: PP_PSWFC.N inside
20 * PP_FULL_WFC are the pseudo partial waves, while the top-level PP_PSWFC block
21 * holds PP_CHI.N atomic wavefunctions. Searches here are bounded to the
22 * PP_FULL_WFC block so the two never mix.
23 */
24
25#include <string>
26
27#include "io/logger.hpp"
30
31namespace q3 {
32
34 public:
35 /**
36 * @param source_description File path this content came from, used to name
37 * the file in errors about missing PAW sections.
38 */
40 const std::string& file_content,
41 const std::string& source_description)
42 : pseudopotential_(pseudopotential)
43 , content_(file_content)
44 , source_description_(source_description) {}
45
46 void read() {
47 read_augmentation_metadata();
48 read_partial_waves();
49 read_paw_block();
50 read_multipole_moments();
51 log_summary();
52 }
53
54 private:
55 void read_augmentation_metadata() {
56 using namespace upf_utilities;
57 auto position = content_.find("<PP_AUGMENTATION");
58 if (position == std::string::npos)
59 throw std::runtime_error("Malformed PAW UPF file " + source_description_ +
60 ": no PP_AUGMENTATION section");
61
62 auto tag_end = content_.find(">", position);
63 std::string tag_line = content_.substr(position, tag_end - position);
64
65 pseudopotential_.augmentation_shape = trim(xml_attribute(tag_line, "shape"));
66
67 std::string cutoff_radius = xml_attribute(tag_line, "cutoff_r");
68 if (!cutoff_radius.empty())
69 pseudopotential_.augmentation_cutoff_radius = std::stod(cutoff_radius);
70
71 /**
72 * cutoff_r_index and iraug carry the same grid index; prefer the former
73 * and fall back to the latter, which is what older writers emit.
74 */
75 std::string cutoff_index = xml_attribute(tag_line, "cutoff_r_index");
76 if (cutoff_index.empty())
77 cutoff_index = xml_attribute(tag_line, "iraug");
78 if (!cutoff_index.empty())
79 pseudopotential_.augmentation_cutoff_radius_index = std::stoi(cutoff_index);
80
81 std::string maximum_angular_momentum_augmentation = xml_attribute(tag_line, "lmax_aug");
82 if (maximum_angular_momentum_augmentation.empty())
83 maximum_angular_momentum_augmentation = xml_attribute(tag_line, "l_max_aug");
84 if (!maximum_angular_momentum_augmentation.empty())
86 std::stoi(maximum_angular_momentum_augmentation);
87
88 /**
89 * nqlc is the number of angular-momentum channels, 2·l_max + 1. Derive it
90 * from l_max when the attribute is absent so the two never disagree.
91 */
92 std::string number_of_channels = xml_attribute(tag_line, "nqlc");
94 number_of_channels.empty() ? 2 * pseudopotential_.maximum_angular_momentum + 1
95 : std::stoi(number_of_channels);
96 }
97
98 void read_partial_waves() {
99 using namespace upf_utilities;
100 auto block_start = content_.find("<PP_FULL_WFC");
101 if (block_start == std::string::npos)
102 throw std::runtime_error(
103 "Malformed PAW UPF file " + source_description_ +
104 ": no PP_FULL_WFC section, so the all-electron and pseudo partial waves "
105 "the one-center terms are built from are absent");
106 auto block_end = content_.find("</PP_FULL_WFC>", block_start);
107 if (block_end == std::string::npos)
108 throw std::runtime_error("Malformed PAW UPF file " + source_description_ +
109 ": unterminated PP_FULL_WFC section");
110
111 int number_of_channels = pseudopotential_.number_of_projectors;
112 pseudopotential_.partial_waves_all_electron.resize(number_of_channels);
113 pseudopotential_.partial_waves_pseudo.resize(number_of_channels);
114
115 for (int channel_index = 0; channel_index < number_of_channels; channel_index++) {
116 read_partial_wave("PP_AEWFC",
117 channel_index,
118 block_start,
119 block_end,
120 pseudopotential_.partial_waves_all_electron[channel_index]);
121 read_partial_wave("PP_PSWFC",
122 channel_index,
123 block_start,
124 block_end,
125 pseudopotential_.partial_waves_pseudo[channel_index]);
126 }
127 }
128
129 void read_partial_wave(const std::string& tag_prefix,
130 int channel_index,
131 size_t block_start,
132 size_t block_end,
133 PartialWave& partial_wave) {
134 using namespace upf_utilities;
135 std::string tag = "<" + tag_prefix + "." + std::to_string(channel_index + 1);
136 auto position = content_.find(tag, block_start);
137 if (position == std::string::npos || position >= block_end)
138 throw std::runtime_error("Malformed PAW UPF file " + source_description_ +
139 ": missing " + tag.substr(1) + " inside PP_FULL_WFC");
140
141 auto tag_end = content_.find(">", position);
142 std::string tag_line = content_.substr(position, tag_end - position);
143
144 std::string angular_momentum = xml_attribute(tag_line, " l");
145 if (angular_momentum.empty())
146 angular_momentum = xml_attribute(tag_line, "\nl");
147 partial_wave.angular_momentum = angular_momentum.empty() ? 0 : std::stoi(angular_momentum);
148
149 std::string close_tag = "</" + tag_prefix + "." + std::to_string(channel_index + 1) + ">";
150 auto close_position = content_.find(close_tag, position);
151 if (close_position == std::string::npos)
152 throw std::runtime_error("Malformed PAW UPF file " + source_description_ +
153 ": unterminated " + tag.substr(1));
154
155 std::string section = content_.substr(tag_end + 1, close_position - tag_end - 1);
156 partial_wave.radial_values = xml_data(section);
157 partial_wave.radial_values.resize(pseudopotential_.mesh_size, 0.0);
158 }
159
160 void read_paw_block() {
161 using namespace upf_utilities;
162 auto block_start = content_.find("<PP_PAW");
163 if (block_start == std::string::npos)
164 throw std::runtime_error("Malformed PAW UPF file " + source_description_ +
165 ": no PP_PAW section");
166
167 auto tag_end = content_.find(">", block_start);
168 std::string tag_line = content_.substr(block_start, tag_end - block_start);
169 std::string core_energy = xml_attribute(tag_line, "core_energy");
170 if (!core_energy.empty()) {
171 pseudopotential_.core_energy = std::stod(core_energy);
172 pseudopotential_.has_core_energy = true;
173 }
174
175 pseudopotential_.channel_occupations = read_section("PP_OCCUPATIONS", block_start);
176 pseudopotential_.channel_occupations.resize(pseudopotential_.number_of_projectors, 0.0);
177
178 pseudopotential_.charge_core_all_electron.radial_values =
179 read_section("PP_AE_NLCC", block_start);
180 pseudopotential_.charge_core_all_electron.radial_values.resize(pseudopotential_.mesh_size,
181 0.0);
182
184 read_section("PP_AE_VLOC", block_start);
185 pseudopotential_.potential_local_all_electron.radial_values.resize(
186 pseudopotential_.mesh_size, 0.0);
187 }
188
189 void read_multipole_moments() {
190 using namespace upf_utilities;
191 int number_of_channels = pseudopotential_.number_of_projectors;
192 int number_of_angular_momenta = pseudopotential_.number_of_augmentation_angular_momenta;
193
194 pseudopotential_.multipole_moments.assign(
195 number_of_angular_momenta,
196 std::vector<std::vector<Real>>(number_of_channels,
197 std::vector<Real>(number_of_channels, 0.0)));
198
199 std::vector<Real> flat = read_section("PP_MULTIPOLES", 0);
200 if (flat.empty())
201 return;
202
203 /**
204 * Stored angular-momentum-major: index = L·nbeta² + i·nbeta + j. Verified
205 * against both fixture datasets — the leading nbeta² block reproduces
206 * PP_Q exactly, and every entry matches ∫(φ_i φ_j − φ̃_i φ̃_j) r^L dr on
207 * the channels the triangle condition allows.
208 */
209 auto expected_size = static_cast<size_t>(number_of_angular_momenta) * number_of_channels *
210 number_of_channels;
211 if (flat.size() < expected_size)
212 throw std::runtime_error("Malformed PAW UPF file " + source_description_ +
213 ": PP_MULTIPOLES holds " + std::to_string(flat.size()) +
214 " values, expected " + std::to_string(expected_size));
215
216 for (int angular_momentum = 0; angular_momentum < number_of_angular_momenta;
217 angular_momentum++) {
218 for (int channel_1 = 0; channel_1 < number_of_channels; channel_1++) {
219 for (int channel_2 = 0; channel_2 < number_of_channels; channel_2++) {
220 size_t flat_index = static_cast<size_t>(angular_momentum) * number_of_channels *
221 number_of_channels +
222 static_cast<size_t>(channel_1) * number_of_channels +
223 channel_2;
224 pseudopotential_.multipole_moments[angular_momentum][channel_1][channel_2] =
225 flat[flat_index];
226 }
227 }
228 }
229 }
230
231 /// Read the numeric body of a section, searching from a starting offset.
232 std::vector<Real> read_section(const std::string& tag_name, size_t search_from) {
233 using namespace upf_utilities;
234 auto position = content_.find("<" + tag_name + " ", search_from);
235 if (position == std::string::npos)
236 position = content_.find("<" + tag_name + ">", search_from);
237 if (position == std::string::npos)
238 return {};
239
240 auto tag_end = content_.find(">", position);
241 auto close_position = content_.find("</" + tag_name + ">", tag_end);
242 if (close_position == std::string::npos)
243 return {};
244
245 return xml_data(content_.substr(tag_end + 1, close_position - tag_end - 1));
246 }
247
248 void log_summary() {
249 log::info(" UPFv2 PAW: {} channels, aug shape={} r_aug={:.4f} bohr (index {}) l_max={}",
250 pseudopotential_.number_of_projectors,
251 pseudopotential_.augmentation_shape,
252 pseudopotential_.augmentation_cutoff_radius,
253 pseudopotential_.augmentation_cutoff_radius_index,
255 }
256
257 PawPseudopotential& pseudopotential_;
258 const std::string& content_;
259 std::string source_description_;
260};
261
262} // namespace q3
PAW pseudopotential — extends ultrasoft with one-center (on-site) data.
Definition init.hpp:33
int maximum_angular_momentum_augmentation
Highest angular momentum carried by the augmentation channels.
Definition init.hpp:110
int augmentation_cutoff_radius_index
Grid index corresponding to augmentation_cutoff_radius.
Definition init.hpp:108
std::vector< PartialWave > partial_waves_pseudo
Pseudo partial waves φ̃_i(r), one per projector channel.
Definition init.hpp:76
Real core_energy
One-center core energy, when the dataset provides it.
Definition init.hpp:115
std::vector< PartialWave > partial_waves_all_electron
All-electron partial waves φ_i(r), one per projector channel.
Definition init.hpp:74
Real augmentation_cutoff_radius
Radius beyond which the augmentation charges vanish (bohr).
Definition init.hpp:106
PotentialLocal potential_local_all_electron
All-electron local potential (Ry); diverges as -2Z/r toward the origin.
Definition init.hpp:85
int number_of_augmentation_angular_momenta
Number of angular-momentum channels, 2·l_max + 1.
Definition init.hpp:112
std::vector< Real > channel_occupations
Reference occupation of each projector channel in the generating atom.
Definition init.hpp:78
std::vector< std::vector< std::vector< Real > > > multipole_moments
Definition init.hpp:99
std::string augmentation_shape
Shape function used to pseudize the augmentation charges (e.g. "BESSEL").
Definition init.hpp:104
ChargeCore charge_core_all_electron
All-electron core density (distinct from the pseudized NLCC core density).
Definition init.hpp:83
PawUPFv2Reader(PawPseudopotential &pseudopotential, const std::string &file_content, const std::string &source_description)
Definition paw.hpp:39
int number_of_projectors
Definition init.hpp:72
int maximum_angular_momentum
Definition init.hpp:70
void section(const std::string &title)
Definition logger.hpp:206
void info(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:183
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
std::vector< Real > radial_values
std::vector< Real > radial_values