Loading...
Searching...
No Matches
kleinman_bylander_projectors.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * BerkeleyGWKleinmanBylanderProjectorFile — writes BerkeleyGW's `VKB` file: the
5 * nonlocal pseudopotential at every k-point, from which ParaBands builds the
6 * nonlocal part of the Hamiltonian it diagonalizes,
7 *
8 * V_NL(k+G, k+G') = Σ_atom Σ_ij <k+G|β_i> D_ij <β_j|k+G'>.
9 *
10 * The BerkeleyGW specification does not cover this file. Its authority is the
11 * reader BerkeleyGW 4.0 ships (BSD-licensed): `pp_read_header` and
12 * `pp_read_beta` in `MeanField/ParaBands/pseudopot_vkb.f90`, with the
13 * contraction above from `ham_build_column_nl_col` in
14 * `MeanField/ParaBands/hamiltonian.f90`. Shapes in brackets, first index fastest:
15 *
16 * records 1-8: BerkeleyGWCommonHeader, projector-dimensions variant:
17 * record 2 is ns ng ntran cell_symmetry nat ecutrho
18 * nk nsp nkb nhm ngkmax ecutwfc; record 3 as WFN's
19 * record 9: ngk[nk]
20 * record 10: kw[nk] -- normalized to sum to 1, as WFN's
21 * record 11: kpt[3,nk] -- crystal coordinates
22 * record 12: ityp[nat] -- 1-based species of each atom
23 * record 13: nh[nsp] -- projector channels per species
24 * record 14: deeq[nhm,nhm,nat,ns] -- D_ij in Rydberg, zero-padded to nhm
25 * then: [nrecord=1][ng][gvec[3,ng]] -- the charge-density G-set
26 * body: per k-point, [nrecord][ngk][gvec_k], then per spin, per
27 * projector channel, [nrecord][ngk][<k+G|β>[ngk]]
28 *
29 * Records 9-11 and the k-point G-vectors must match the WFN they go with:
30 * ParaBands compares k-points, weights, plane-wave counts, kgrid and kshift and
31 * stops on any difference. Both files write them through
32 * BerkeleyGWRecordWriter, so they cannot drift apart.
33 *
34 * Order of the body's channels. deeq is indexed by atom in the crystal's own
35 * order, but the reader takes the projectors grouped by species -- every atom
36 * of species 1 in order, then species 2 -- and maps them back. Within an atom
37 * the channels follow the record's projector source, the order D was written
38 * in, so the pair is consistent whatever that order is.
39 *
40 * Units: <k+G|β> is the plane-wave matrix element of the projector in Q3's
41 * normalization, Ω^(-1/2) β(|k+G|) Y_lm (−i)^l e^(−i(k+G)·τ) (projector.hpp),
42 * which with D in Rydberg gives V_NL in Rydberg, as ParaBands' kinetic energy
43 * and V_sc are.
44 */
45
46#include <algorithm>
47#include <array>
48#include <cstdint>
49#include <fstream>
50#include <stdexcept>
51#include <string>
52#include <vector>
53
54#include "io/checkpoint/qe.hpp"
55#include "io/logger.hpp"
59
60namespace q3 {
61
62/// Writes `VKB` from a mean-field record that carries projectors; see the file comment.
64 public:
66 : record_(record) {}
67 /// See BerkeleyGWChargeDensityFile: this holds a reference.
69
70 /// Writes the file, or throws -- before creating it -- when the record carries no projectors.
71 void write_to_file(const std::string& file_path) const {
72 check_record();
73
74 std::ofstream output_file(file_path, std::ios::binary);
75 if (!output_file.is_open()) {
76 throw std::runtime_error("cannot open " + file_path + " for writing");
77 }
78
79 BerkeleyGWCommonHeader header(record_, "VKB-Complex", projector_dimensions());
80 header.write(output_file);
81
82 BerkeleyGWRecordWriter record_writer(record_);
83 record_writer.write_plane_wave_counts(output_file);
84 record_writer.write_normalized_kpoint_weights(output_file);
85 record_writer.write_kpoint_coordinates(output_file);
86 write_atom_species_record(output_file);
87 write_projectors_per_species_record(output_file);
88 write_nonlocal_coefficients_record(output_file);
89 record_writer.write_charge_density_gvectors(output_file);
90 write_body(output_file, record_writer);
91
92 log::info(" Wrote BerkeleyGW Kleinman-Bylander projector file: {}", file_path);
93 }
94
95 private:
96 const MeanFieldRecord& record_;
97 QEBinaryRecordFormat record_format_;
98
99 void check_record() const {
100 if (!record_.projector_source) {
101 throw std::runtime_error("the mean-field record carries no projectors; request them "
102 "with control.mean_field_export_kleinman_bylander_projectors");
103 }
104 if (static_cast<int>(record_.atom_species_indices.size()) != record_.number_of_atoms() ||
105 static_cast<int>(record_.nonlocal_coefficients.size()) !=
107 throw std::runtime_error("the mean-field record's nonlocal pseudopotential does not "
108 "match its atoms and spin channels");
109 }
110 }
111
112 /// {nsp, nkb, nhm}.
113 [[nodiscard]] std::array<int32_t, 3> projector_dimensions() const {
114 int32_t number_of_projectors = 0;
115 for (int species_index : record_.atom_species_indices) {
116 number_of_projectors += record_.projectors_per_species[species_index];
117 }
118 int32_t maximum_projectors_per_atom =
119 record_.projectors_per_species.empty()
120 ? 0
121 : *std::max_element(record_.projectors_per_species.begin(),
122 record_.projectors_per_species.end());
123 return {static_cast<int32_t>(record_.projectors_per_species.size()),
124 number_of_projectors,
125 maximum_projectors_per_atom};
126 }
127
128 /// Record 12: ityp(nat), 1-based.
129 void write_atom_species_record(std::ofstream& output_file) const {
130 std::vector<int32_t> species(record_.atom_species_indices.size());
131 for (size_t atom_index = 0; atom_index < species.size(); atom_index++) {
132 species[atom_index] = record_.atom_species_indices[atom_index] + 1;
133 }
134 record_format_.write_record(
135 output_file, species.data(), static_cast<int32_t>(species.size() * sizeof(int32_t)));
136 }
137
138 /// Record 13: nh(nsp).
139 void write_projectors_per_species_record(std::ofstream& output_file) const {
140 std::vector<int32_t> counts(record_.projectors_per_species.begin(),
141 record_.projectors_per_species.end());
142 record_format_.write_record(
143 output_file, counts.data(), static_cast<int32_t>(counts.size() * sizeof(int32_t)));
144 }
145
146 /// Record 14: deeq(nhm,nhm,nat,ns), each atom's block zero-padded to nhm x nhm.
147 void write_nonlocal_coefficients_record(std::ofstream& output_file) const {
148 const size_t maximum_channels = static_cast<size_t>(projector_dimensions()[2]);
149 const size_t number_of_atoms = static_cast<size_t>(record_.number_of_atoms());
150 const size_t block_size = maximum_channels * maximum_channels;
151 std::vector<double> coefficients(
152 block_size * number_of_atoms * record_.number_of_spin_components, 0.0);
153 for (int spin_index = 0; spin_index < record_.number_of_spin_components; spin_index++) {
154 for (size_t atom_index = 0; atom_index < number_of_atoms; atom_index++) {
155 const auto& block = record_.nonlocal_coefficients[spin_index][atom_index];
156 size_t number_of_channels = static_cast<size_t>(
157 record_.projectors_per_species[record_.atom_species_indices[atom_index]]);
158 double* destination =
159 coefficients.data() + block_size * (atom_index + number_of_atoms * spin_index);
160 for (size_t channel_j = 0; channel_j < number_of_channels; channel_j++) {
161 for (size_t channel_i = 0; channel_i < number_of_channels; channel_i++) {
162 destination[channel_i + maximum_channels * channel_j] =
163 block[channel_i + number_of_channels * channel_j];
164 }
165 }
166 }
167 }
168 record_format_.write_record(output_file,
169 coefficients.data(),
170 static_cast<int32_t>(coefficients.size() * sizeof(double)));
171 }
172
173 /**
174 * Per k-point: the k-point's G-vectors, then every projector channel as its
175 * own block, atoms grouped by species. The projectors do not depend on spin
176 * (D carries any spin dependence), so each spin channel repeats them, as
177 * the reader expects one set per spin.
178 */
179 void write_body(std::ofstream& output_file, const BerkeleyGWRecordWriter& record_writer) const {
180 std::vector<int> first_projector_of_atom(record_.number_of_atoms() + 1, 0);
181 for (int atom_index = 0; atom_index < record_.number_of_atoms(); atom_index++) {
182 first_projector_of_atom[atom_index + 1] =
183 first_projector_of_atom[atom_index] +
184 record_.projectors_per_species[record_.atom_species_indices[atom_index]];
185 }
186
187 for (int kpoint_index = 0; kpoint_index < record_.number_of_kpoints(); kpoint_index++) {
188 record_writer.write_kpoint_gvectors(output_file, kpoint_index);
189
190 int32_t number_of_plane_waves = record_.plane_waves_per_kpoint[kpoint_index];
191 const Matrix<Complex>& projectors = record_.projector_source->projectors(kpoint_index);
192 if (static_cast<int>(projectors.rows()) != first_projector_of_atom.back() ||
193 static_cast<int32_t>(projectors.cols()) != number_of_plane_waves) {
194 throw std::runtime_error("the projector source returned a " +
195 std::to_string(projectors.rows()) + " x " +
196 std::to_string(projectors.cols()) + " matrix at k-point " +
197 std::to_string(kpoint_index) + ", expected " +
198 std::to_string(first_projector_of_atom.back()) + " x " +
199 std::to_string(number_of_plane_waves));
200 }
201
202 for (int spin_index = 0; spin_index < record_.number_of_spin_components; spin_index++) {
203 for (int species_index = 0;
204 species_index < static_cast<int>(record_.projectors_per_species.size());
205 species_index++) {
206 for (int atom_index = 0; atom_index < record_.number_of_atoms(); atom_index++) {
207 if (record_.atom_species_indices[atom_index] != species_index) {
208 continue;
209 }
210 for (int projector_index = first_projector_of_atom[atom_index];
211 projector_index < first_projector_of_atom[atom_index + 1];
212 projector_index++) {
213 record_writer.write_complex_block(output_file,
214 number_of_plane_waves,
215 projectors[projector_index],
216 number_of_plane_waves);
217 }
218 }
219 }
220 }
221 }
222 }
223};
224
225} // namespace q3
void write(std::ofstream &output_file) const
Write records 1 through 8 to output_file.
Definition header.hpp:256
Writes VKB from a mean-field record that carries projectors; see the file comment.
BerkeleyGWKleinmanBylanderProjectorFile(const MeanFieldRecord &record)
BerkeleyGWKleinmanBylanderProjectorFile(const MeanFieldRecord &&)=delete
See BerkeleyGWChargeDensityFile: this holds a reference.
void write_to_file(const std::string &file_path) const
Writes the file, or throws – before creating it – when the record carries no projectors.
Writes the records BerkeleyGW's files share; see the file comment.
void write_plane_wave_counts(std::ofstream &output_file) const
ngk(nk) – plane waves per k-point.
void write_charge_density_gvectors(std::ofstream &output_file) const
[nrecord=1][ng][gvec(3,ng)] – the charge-density G-set, in the record's order.
void write_kpoint_coordinates(std::ofstream &output_file) const
void write_normalized_kpoint_weights(std::ofstream &output_file) const
void write_record(std::ofstream &output_file, const void *data, int32_t number_of_bytes) const override
Definition qe.hpp:16
void info(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:183
int number_of_atoms() const
Definition init.hpp:188
int number_of_spin_components
Definition init.hpp:127
int number_of_kpoints() const
Definition init.hpp:184
std::vector< std::vector< std::vector< Real > > > nonlocal_coefficients
Definition init.hpp:181
std::vector< int > projectors_per_species
Projector channels – one per (β, m) – of each species.
Definition init.hpp:175
std::vector< int > plane_waves_per_kpoint
Definition init.hpp:121
std::vector< int > atom_species_indices
0-based species index of each atom, parallel to atomic_numbers.
Definition init.hpp:173
std::shared_ptr< const MeanFieldProjectorSource > projector_source
Definition init.hpp:182