Loading...
Searching...
No Matches
writer.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * Wannier90FormatWriter — writes a WannierInputs out as the four
5 * Wannier90 interface files (`.mmn`, `.amn`, `.eig`, `.nnkp`).
6 *
7 * This is the half that makes Q3's own overlaps and projections usable: the
8 * interface kernels compute M_mn(k,b) and A_mn(k) in memory, the localization
9 * engine reads them from disk, and without a writer the two never meet.
10 * Emitting the Wannier90 formats rather than only a private one is deliberate
11 * -- the same files can be handed to `wannier90.x` itself, which is how Q3's
12 * matrices get checked against an independent implementation rather than only
13 * against Q3's own reader.
14 *
15 * The reader in reader.hpp was written from the user guide
16 * before this writer existed, and neither consults the other. A round trip
17 * through both therefore tests something real: a shared misreading of the
18 * format cannot cancel out between a writer and a reader derived from it,
19 * because this pair was not derived that way.
20 *
21 * Conventions, all of them the file format's rather than Q3's:
22 *
23 * - Indices are written 1-based (Fortran origin).
24 * - A `.mmn` block's num_bands^2 elements go out with the FIRST index m
25 * varying fastest, which is the order WannierInputs already stores
26 * them in, so blocks stream straight out with no transpose.
27 * - `.eig` is in ELECTRON VOLTS; the record holds Rydberg, and this converts.
28 * - `.nnkp` lattice blocks are Angstrom (real) and inverse Angstrom
29 * (reciprocal); the caller passes them in those units.
30 *
31 * The per-(k,b) neighbour identities the `.mmn` and `.nnkp` need are not
32 * stored in the record -- it holds the b-vector shell, which is the same for
33 * every k-point -- so they are recomputed here with
34 * KPointNeighborShells::resolve_neighbor, the same exact-integer routine the
35 * overlap kernel used when it built those blocks.
36 */
37
38#include <fstream>
39#include <iomanip>
40#include <stdexcept>
41#include <string>
42#include <vector>
43
49
50namespace q3 {
51
53 public:
54 /// eV per Rydberg; the record is Rydberg, `.eig` is eV.
56
57 /**
58 * Write all four files for `seedname`, e.g. `si` -> `si.mmn`, `si.amn`,
59 * `si.eig`, `si.nnkp`.
60 *
61 * `real_lattice` rows are a1, a2, a3 in Angstrom and `reciprocal_lattice`
62 * rows b1, b2, b3 in inverse Angstrom -- the units the `.nnkp` format
63 * specifies, which are not Q3's internal atomic units, so the caller
64 * converts rather than this guessing.
65 */
66 static void write_all(const std::string& seedname,
67 const WannierInputs& record,
68 const Matrix3x3& real_lattice,
69 const Matrix3x3& reciprocal_lattice) {
70 write_overlaps(seedname + ".mmn", record);
71 write_projections(seedname + ".amn", record);
72 write_eigenvalues(seedname + ".eig", record);
73 write_neighbor_list(seedname + ".nnkp", record, real_lattice, reciprocal_lattice);
74 }
75
76 static void write_overlaps(const std::string& file_path, const WannierInputs& record) {
77 std::ofstream stream = open(file_path);
78 const int number_of_kpoints = record.number_of_kpoints();
79 const int number_of_directions = static_cast<int>(record.neighbor_directions.size());
80 require_overlaps_present(record);
81
82 stream << "Written by Q3\n";
83 stream << " " << record.number_of_bands << " " << number_of_kpoints << " "
84 << number_of_directions << "\n";
85
86 std::vector<KPoint> kpoints = build_kpoints(record);
87 for (int kpoint_index = 0; kpoint_index < number_of_kpoints; kpoint_index++) {
88 for (int direction_index = 0; direction_index < number_of_directions;
89 direction_index++) {
91 kpoint_index,
92 record.neighbor_directions[direction_index].grid_step,
94 kpoints);
95 stream << " " << kpoint_index + 1 << " " << resolved.neighbor_kpoint_index + 1
96 << " " << resolved.periodic_image_shift.x << " "
97 << resolved.periodic_image_shift.y << " " << resolved.periodic_image_shift.z
98 << "\n";
99
100 const std::vector<Complex>& block =
101 record.overlap_matrices[kpoint_index][direction_index];
102 for (const Complex& value : block) {
103 write_complex(stream, value);
104 }
105 }
106 }
107 }
108
109 static void write_projections(const std::string& file_path, const WannierInputs& record) {
110 std::ofstream stream = open(file_path);
111 const int number_of_kpoints = record.number_of_kpoints();
112 if (static_cast<int>(record.projection_matrices.size()) != number_of_kpoints) {
113 throw std::invalid_argument(
114 "Wannier90FormatWriter::write_projections: the record holds no projections");
115 }
116
117 stream << "Written by Q3\n";
118 stream << " " << record.number_of_bands << " " << number_of_kpoints << " "
119 << record.number_of_trial_orbitals << "\n";
120
121 for (int kpoint_index = 0; kpoint_index < number_of_kpoints; kpoint_index++) {
122 const std::vector<Complex>& block = record.projection_matrices[kpoint_index];
123 for (int orbital_index = 0; orbital_index < record.number_of_trial_orbitals;
124 orbital_index++) {
125 for (int band_index = 0; band_index < record.number_of_bands; band_index++) {
126 stream << " " << band_index + 1 << " " << orbital_index + 1 << " "
127 << kpoint_index + 1;
128 const Complex& value =
129 block[static_cast<std::size_t>(orbital_index) * record.number_of_bands +
130 band_index];
131 write_complex(stream, value);
132 }
133 }
134 }
135 }
136
137 static void write_eigenvalues(const std::string& file_path, const WannierInputs& record) {
138 if (static_cast<int>(record.eigenvalues_rydberg.size()) != record.number_of_kpoints()) {
139 throw std::invalid_argument(
140 "Wannier90FormatWriter::write_eigenvalues: the record holds no eigenvalues");
141 }
142 std::ofstream stream = open(file_path);
143 for (int kpoint_index = 0; kpoint_index < record.number_of_kpoints(); kpoint_index++) {
144 const std::vector<Real>& row = record.eigenvalues_rydberg[kpoint_index];
145 if (static_cast<int>(row.size()) < record.number_of_bands) {
146 throw std::invalid_argument("Wannier90FormatWriter::write_eigenvalues: k-point " +
147 std::to_string(kpoint_index) + " carries " +
148 std::to_string(row.size()) +
149 " eigenvalues, fewer than number_of_bands = " +
150 std::to_string(record.number_of_bands));
151 }
152 for (int band_index = 0; band_index < record.number_of_bands; band_index++) {
153 stream << " " << band_index + 1 << " " << kpoint_index + 1 << " " << std::scientific
154 << std::setprecision(12) << row[band_index] * electron_volts_per_rydberg
155 << "\n";
156 }
157 }
158 }
159
160 static void write_neighbor_list(const std::string& file_path,
161 const WannierInputs& record,
162 const Matrix3x3& real_lattice,
163 const Matrix3x3& reciprocal_lattice) {
164 std::ofstream stream = open(file_path);
165 const int number_of_kpoints = record.number_of_kpoints();
166 const int number_of_directions = static_cast<int>(record.neighbor_directions.size());
167
168 stream << "File written by Q3\n\n";
169 stream << "calc_only_A : F\n\n";
170
171 write_lattice_block(stream, "real_lattice", real_lattice);
172 write_lattice_block(stream, "recip_lattice", reciprocal_lattice);
173
174 stream << "begin kpoints\n";
175 stream << " " << number_of_kpoints << "\n";
176 for (const Vector3D& kpoint : record.kpoint_coordinates) {
177 stream << " " << std::fixed << std::setprecision(12) << kpoint.x << " " << kpoint.y
178 << " " << kpoint.z << "\n";
179 }
180 stream << "end kpoints\n\n";
181
182 std::vector<KPoint> kpoints = build_kpoints(record);
183 stream << "begin nnkpts\n";
184 stream << " " << number_of_directions << "\n";
185 for (int kpoint_index = 0; kpoint_index < number_of_kpoints; kpoint_index++) {
186 for (int direction_index = 0; direction_index < number_of_directions;
187 direction_index++) {
189 kpoint_index,
190 record.neighbor_directions[direction_index].grid_step,
192 kpoints);
193 stream << " " << kpoint_index + 1 << " " << resolved.neighbor_kpoint_index + 1
194 << " " << resolved.periodic_image_shift.x << " "
195 << resolved.periodic_image_shift.y << " " << resolved.periodic_image_shift.z
196 << "\n";
197 }
198 }
199 stream << "end nnkpts\n";
200 }
201
202 private:
203 static std::ofstream open(const std::string& file_path) {
204 std::ofstream stream(file_path);
205 if (!stream) {
206 throw std::runtime_error("Wannier90FormatWriter: cannot write " + file_path);
207 }
208 return stream;
209 }
210
211 static void write_complex(std::ofstream& stream, const Complex& value) {
212 stream << " " << std::scientific << std::setprecision(12) << value.real() << " "
213 << value.imag() << "\n";
214 }
215
216 static void
217 write_lattice_block(std::ofstream& stream, const std::string& name, const Matrix3x3& lattice) {
218 stream << "begin " << name << "\n";
219 for (int row = 0; row < 3; row++) {
220 stream << " " << std::fixed << std::setprecision(12) << lattice[row].x << " "
221 << lattice[row].y << " " << lattice[row].z << "\n";
222 }
223 stream << "end " << name << "\n\n";
224 }
225
226 /**
227 * resolve_neighbor works on a KPoint list; the record stores bare
228 * coordinates. Weights are irrelevant to the neighbour arithmetic, which
229 * is pure integer grid indexing plus an exact fractional comparison.
230 */
231 static std::vector<KPoint> build_kpoints(const WannierInputs& record) {
232 std::vector<KPoint> kpoints(record.kpoint_coordinates.size());
233 for (std::size_t index = 0; index < record.kpoint_coordinates.size(); index++) {
234 kpoints[index].coordinates = record.kpoint_coordinates[index];
235 kpoints[index].weight = 0.0;
236 }
237 return kpoints;
238 }
239
240 static void require_overlaps_present(const WannierInputs& record) {
241 if (static_cast<int>(record.overlap_matrices.size()) != record.number_of_kpoints()) {
242 throw std::invalid_argument(
243 "Wannier90FormatWriter::write_overlaps: the record holds no overlaps");
244 }
245 for (const std::vector<std::vector<Complex>>& per_kpoint : record.overlap_matrices) {
246 if (per_kpoint.size() != record.neighbor_directions.size()) {
247 throw std::invalid_argument(
248 "Wannier90FormatWriter::write_overlaps: a k-point carries a different number "
249 "of neighbour blocks than there are neighbour directions");
250 }
251 }
252 }
253};
254
255} // namespace q3
static ResolvedKPointNeighbor resolve_neighbor(int starting_kpoint_index, const IntegerVector3D &grid_step, const std::array< int, 3 > &grid_dimensions, const std::vector< KPoint > &physical_kpoints)
static void write_projections(const std::string &file_path, const WannierInputs &record)
Definition writer.hpp:109
static void write_eigenvalues(const std::string &file_path, const WannierInputs &record)
Definition writer.hpp:137
static constexpr Real electron_volts_per_rydberg
eV per Rydberg; the record is Rydberg, .eig is eV.
Definition writer.hpp:55
static void write_neighbor_list(const std::string &file_path, const WannierInputs &record, const Matrix3x3 &real_lattice, const Matrix3x3 &reciprocal_lattice)
Definition writer.hpp:160
static void write_overlaps(const std::string &file_path, const WannierInputs &record)
Definition writer.hpp:76
static void write_all(const std::string &seedname, const WannierInputs &record, const Matrix3x3 &real_lattice, const Matrix3x3 &reciprocal_lattice)
Definition writer.hpp:66
constexpr Real RY_TO_EV
double Real
std::complex< double > Complex
3×3 matrix stored row-major: row i = mat[i]
std::vector< KPointNeighborDirection > neighbor_directions
Definition inputs.hpp:49
std::vector< Vector3D > kpoint_coordinates
Definition inputs.hpp:42
std::vector< std::vector< Real > > eigenvalues_rydberg
Eigenvalues in Rydberg, indexed [kpoint_index][band_index].
Definition inputs.hpp:74
int number_of_kpoints() const
Definition inputs.hpp:89
std::array< int, 3 > kpoint_grid_dimensions
Definition inputs.hpp:37
int number_of_trial_orbitals
Definition inputs.hpp:72
std::vector< std::vector< Complex > > projection_matrices
Definition inputs.hpp:68
std::vector< std::vector< std::vector< Complex > > > overlap_matrices
Definition inputs.hpp:60