Loading...
Searching...
No Matches
header.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * BerkeleyGWCommonHeader — the 8 binary records shared by BerkeleyGW's WFN
5 * and RHO mean-field files.
6 *
7 * Framing: Fortran `form='unformatted', access='sequential'` via
8 * QEBinaryRecordFormat -- every record is `[int32 length][payload][int32
9 * length]`, matching QE's own I/O. All reals are float64, all ints int32.
10 *
11 * Authority. The record order, contents and units are those BerkeleyGW
12 * publishes: J. Deslippe et al., Comput. Phys. Commun. 183, 1269 (2012),
13 * Appendix 11.1, "Specification of file formats" (arXiv:1111.4429) -- "the
14 * BerkeleyGW specification" below. What it leaves open -- record framing,
15 * integer and real kinds, the width of the character fields, the count
16 * repeated before each body block, the exact conventions of the symmetry
17 * records -- follows Quantum ESPRESSO's pw2bgw.x converter, whose output
18 * BerkeleyGW reads. Citations of the converter name the subroutine of
19 * `PP/src/pw2bgw.f90` in QE 7.5 (release tag `qe-7.5`) -- write_wfng,
20 * write_rhog, write_vxc_g, write_vxc_r -- and the record, never a line
21 * number: the line numbers first cited here were read off a 2026-07-27
22 * `develop` checkout and moved with that branch. The same convention holds
23 * in wavefunction.hpp, charge_density.hpp and exchange_correlation.hpp.
24 *
25 * Arrays are stored column-major (Fortran order): the FIRST index varies
26 * fastest, so an N x M array a[i][j] is laid out a[1][1], a[2][1], ...,
27 * a[N][1], a[1][2], ... -- this is why `avec`/`bvec` (first index the
28 * Cartesian component, second the lattice vector) flatten "vector-major"
29 * (all 3 components of vector 1, then all 3 of vector 2, ...) while `mtrx`
30 * (first index the row, second the column, third the operation) flattens
31 * "column-major within each 3x3 block" (all 3 rows of column 1, then all 3
32 * rows of column 2, ...) -- both are the SAME general rule, just applied to
33 * arrays whose first index means something different. (The rotation matrix
34 * the converter writes is itself the transpose of Q3's rotation, so mtrx
35 * comes out as Q3's rotation flattened row by row; see record 6.)
36 * Mixed-type records (2, 3, 8) are built as manual byte buffers rather than
37 * plain C structs: a struct interleaving int32 and
38 * double fields gets compiler-inserted alignment padding before each double
39 * (Q3's own QE-format wavefunction checkpoint writer,
40 * io/wavefunction/kohn_sham/planewave/checkpoint/qe.hpp:83-88, declares
41 * exactly such a struct -- see docs/io_reference.md §4.8 on why the
42 * mean-field record writes every scalar component-by-component instead).
43 *
44 * Records 1-8 (the BerkeleyGW specification's first eight header records;
45 * write_wfng for WFN, and write_rhog for RHO, which is byte-identical except
46 * that records 2-3 omit the k-point fields). Shapes in brackets, first index
47 * fastest:
48 * 1. stitle[32] sdate[32] stime[32] -- char, 96 bytes
49 * 2. ns ng ntran cell_symmetry nat ecutrho
50 * {nk nbands ngkmax ecutwfc} -- mixed, k-point
51 * fields in braces
52 * 3. FFTgrid[3] {kgrid[3] kshift[3]} -- mixed
53 * 4. celvol alat avec[3,3] adot[3,3] -- float64
54 * 5. recvol blat bvec[3,3] bdot[3,3] -- float64
55 * 6. mtrx[3,3,ntran] -- int32
56 * 7. tnp[3,ntran] -- float64
57 * 8. (apos[3], Z) per atom, for each of the nat atoms -- mixed, interleaved
58 *
59 * The Kleinman-Bylander projector file VKB, which the specification does not
60 * cover, carries the WFN header with one change: record 2's nbands is replaced
61 * by the projector dimensions, `... nk nsp nkb nhm ngkmax ecutwfc` -- the
62 * record BerkeleyGW 4.0's ParaBands reads (`pp_read_header` in
63 * `MeanField/ParaBands/pseudopot_vkb.f90`). See the projector-dimensions
64 * constructor below.
65 */
66
67#include <algorithm>
68#include <array>
69#include <cmath>
70#include <complex>
71#include <cstdint>
72#include <cstring>
73#include <ctime>
74#include <fstream>
75#include <optional>
76#include <set>
77#include <stdexcept>
78#include <string>
79#include <utility>
80#include <vector>
81
82#include "io/checkpoint/qe.hpp"
83#include "io/logger.hpp"
94
95namespace q3 {
96
98 public:
99 /**
100 * @param stitle 32-byte title, e.g. "WFN-Complex" or
101 * "RHO-Complex" (space-padded/truncated to
102 * 32 bytes; pw2bgw.x's write_wfng and
103 * write_rhog always pad with spaces, never
104 * a NUL terminator).
105 * @param include_kpoint_fields true for WFN (record 2 carries
106 * nk/nbands/ngkmax/ecutwfc and record 3
107 * carries kgrid/kshift), false for RHO
108 * (the BerkeleyGW specification's
109 * wavefunction-only fields; write_rhog
110 * records 2-3).
111 */
113 std::string stitle,
114 bool include_kpoint_fields)
115 : record_(record)
116 , stitle_(std::move(stitle))
117 , include_kpoint_fields_(include_kpoint_fields) {}
118
119 /**
120 * The VKB header: the WFN header with record 2's nbands replaced by
121 * `projector_dimensions` = {nsp, nkb, nhm} -- the number of species, of
122 * projector channels over all atoms, and the most channels any one atom
123 * has.
124 */
126 std::string stitle,
127 const std::array<int32_t, 3>& projector_dimensions)
128 : record_(record)
129 , stitle_(std::move(stitle))
130 , include_kpoint_fields_(true)
131 , projector_dimensions_(projector_dimensions) {}
132
133 /// See the writers' own deleted rvalue constructors: this holds a reference.
134 BerkeleyGWCommonHeader(const MeanFieldRecord&&, std::string, bool) = delete;
136 std::string,
137 const std::array<int32_t, 3>&) = delete;
138
139 /**
140 * kgrid and kshift for an explicit k-point list (crystal coordinates) --
141 * the numbers pw2bgw.x takes as its wfng_nk and wfng_dk inputs for the
142 * same points.
143 *
144 * Only a grid the list pins down is returned:
145 * 1. the list unfolded by the crystal's rotations and time reversal,
146 * when that star fills a grid exactly -- a symmetry-reduced wedge, or
147 * a full grid, of a grid the rotations preserve;
148 * 2. otherwise the list as given, when it is a full grid -- every
149 * point once, all with the same weight -- e.g. the WFNq list, the
150 * full 4x4x2 grid displaced by q0 = (0.001, 0, 0), which gives 4 4 2
151 * with shift (0.004, 0, 0);
152 * 3. otherwise 0 0 0 / 0 0 0, what the record carried before.
153 * A reduced list also fits coarser grids than its own, sometimes exactly:
154 * the two-point wedge of the shifted 2x2x2 FCC grid is a complete 2x1x1
155 * grid. Its weights are orbit sizes, though, not equal, which is what
156 * keeps it out of case 2. Case 3 is then such a wedge of a grid the
157 * rotations do not preserve -- a shifted FCC grid -- which no rule
158 * recovers from the points alone.
159 *
160 * Weights are compared exactly: a full list is written with one weight
161 * repeated, and the same scaling of equal numbers stays equal.
162 */
163 [[nodiscard]] static std::pair<std::array<int32_t, 3>, std::array<double, 3>>
164 kpoint_grid_from_list(const std::vector<Vector3D>& kpoint_coordinates,
165 const std::vector<Real>& kpoint_weights,
166 const std::vector<SymmetryOperation>& symmetry_operations) {
167 std::vector<Vector3D> star;
168 star.reserve(2 * kpoint_coordinates.size() * (symmetry_operations.size() + 1));
169 for (const Vector3D& kpoint : kpoint_coordinates) {
170 star.push_back(kpoint);
171 star.push_back(-kpoint);
172 for (const auto& operation : symmetry_operations) {
173 Vector3D rotated{0.0, 0.0, 0.0};
174 for (int row = 0; row < 3; row++)
175 for (int column = 0; column < 3; column++)
176 rotated[row] += operation.reciprocal_rotation[row][column] * kpoint[column];
177 star.push_back(rotated);
178 star.push_back(-rotated);
179 }
180 }
181 const auto star_grid = complete_kpoint_grid(star);
182 if (star_grid.first[0] > 0)
183 return star_grid;
184
185 const auto listed_grid = complete_kpoint_grid(kpoint_coordinates);
186 const auto& [dimensions, shifts] = listed_grid;
187 const bool equal_weights =
188 std::all_of(kpoint_weights.begin(), kpoint_weights.end(), [&](Real weight) {
189 return weight == kpoint_weights.front();
190 });
191 if (equal_weights &&
192 static_cast<int64_t>(kpoint_coordinates.size()) ==
193 static_cast<int64_t>(dimensions[0]) * dimensions[1] * dimensions[2])
194 return listed_grid;
195 return {{0, 0, 0}, {0.0, 0.0, 0.0}};
196 }
197
198 /**
199 * The smallest grid holding every point of `kpoint_coordinates`, if the
200 * points fill it exactly (every grid point present); 0 0 0 / 0 0 0
201 * otherwise. Per axis, the smallest n at which every n * k sits the same
202 * distance from an integer, that common offset being the shift (in units
203 * of the grid step, wrapped to (-0.5, 0.5]); an axis no grid of up to
204 * `maximum_grid_dimension` divisions fits -- a band-structure path, say --
205 * leaves the whole result unset.
206 */
207 [[nodiscard]] static std::pair<std::array<int32_t, 3>, std::array<double, 3>>
208 complete_kpoint_grid(const std::vector<Vector3D>& kpoint_coordinates) {
209 constexpr int maximum_grid_dimension = 1000;
211 auto distance_from_integer = [](Real value) { return value - std::round(value); };
212 const std::pair<std::array<int32_t, 3>, std::array<double, 3>> unset = {{0, 0, 0},
213 {0.0, 0.0, 0.0}};
214
215 std::array<int32_t, 3> dimensions = {0, 0, 0};
216 std::array<double, 3> shifts = {0.0, 0.0, 0.0};
217 if (kpoint_coordinates.empty())
218 return unset;
219 for (int axis = 0; axis < 3; axis++) {
220 for (int grid_dimension = 1; grid_dimension <= maximum_grid_dimension;
221 grid_dimension++) {
222 const Real offset =
223 distance_from_integer(grid_dimension * kpoint_coordinates.front()[axis]);
224 const bool on_grid = std::all_of(
225 kpoint_coordinates.begin(), kpoint_coordinates.end(), [&](const Vector3D& k) {
226 return std::abs(distance_from_integer(grid_dimension * k[axis] - offset)) <=
227 tolerance;
228 });
229 if (on_grid) {
230 dimensions[axis] = grid_dimension;
231 shifts[axis] = offset <= -0.5 + tolerance ? offset + 1.0 : offset;
232 break;
233 }
234 }
235 if (dimensions[axis] == 0)
236 return unset;
237 }
238
239 std::set<std::array<int64_t, 3>> occupied_points;
240 for (const Vector3D& kpoint : kpoint_coordinates) {
241 std::array<int64_t, 3> index{};
242 for (int axis = 0; axis < 3; axis++) {
243 const int64_t on_grid =
244 std::llround(dimensions[axis] * kpoint[axis] - shifts[axis]);
245 index[axis] = ((on_grid % dimensions[axis]) + dimensions[axis]) % dimensions[axis];
246 }
247 occupied_points.insert(index);
248 }
249 if (static_cast<int64_t>(occupied_points.size()) !=
250 static_cast<int64_t>(dimensions[0]) * dimensions[1] * dimensions[2])
251 return unset;
252 return {dimensions, shifts};
253 }
254
255 /// Write records 1 through 8 to `output_file`.
256 void write(std::ofstream& output_file) const {
257 const std::vector<SymmetryOperation>& symmetry_operations = record_.symmetry_operations;
258
259 write_title_record(output_file);
260 write_dimension_record(output_file, static_cast<int>(symmetry_operations.size()));
261 write_grid_record(output_file);
262 write_direct_lattice_record(output_file);
263 write_reciprocal_lattice_record(output_file);
264 write_symmetry_rotation_record(output_file, symmetry_operations);
265 write_symmetry_translation_record(output_file, symmetry_operations);
266 write_atom_record(output_file);
267 }
268
269 private:
270 const MeanFieldRecord& record_;
271 std::string stitle_;
272 bool include_kpoint_fields_;
273 /// Set for VKB only: nsp, nkb, nhm, written in place of nbands.
274 std::optional<std::array<int32_t, 3>> projector_dimensions_;
275 QEBinaryRecordFormat record_format_;
276
277 /**
278 * cell_symmetry: 0 for the cubic family, 1 for the hexagonal one. From the
279 * Bravais index when the input named a lattice, which follows QE's ibrav
280 * (see Crystal::set_from_lattice_type()), and from the lattice vectors
281 * for a free lattice; see berkeleygw/cell_symmetry.hpp.
282 */
283 [[nodiscard]] int cell_symmetry() const {
285 }
286
287 /**
288 * Metric tensor `dot[i][j] = vectors[i] . vectors[j] * scale^2` -- used
289 * for both `adot` (direct lattice, a_i . a_j scaled by alat^2) and `bdot`
290 * (reciprocal lattice, b_i . b_j scaled by (2*pi/alat)^2), the real- and
291 * reciprocal-space metric tensors of the BerkeleyGW specification's
292 * records 4 and 5 (a symmetric Gram matrix, so row-major vs.
293 * column-major flattening are byte-identical either way).
294 */
295 [[nodiscard]] static Matrix3x3 metric_tensor(const Matrix3x3& vectors, Real scale) {
296 Matrix3x3 result;
297 Real scale_squared = scale * scale;
298 for (int row = 0; row < 3; row++) {
299 for (int column = 0; column < 3; column++) {
300 result[row][column] = vectors[row].dot(vectors[column]) * scale_squared;
301 }
302 }
303 return result;
304 }
305
306 /**
307 * Pad/truncate `text` to exactly `length` bytes with trailing spaces (Fortran CHARACTER
308 * convention).
309 */
310 static void write_padded_string(char* destination, const std::string& text, size_t length) {
311 size_t copy_length = std::min(text.size(), length);
312 std::memcpy(destination, text.data(), copy_length);
313 std::memset(destination + copy_length, ' ', length - copy_length);
314 }
315
316 /**
317 * Record 1: stitle(32) sdate(32) stime(32) -- 96 bytes of padded chars.
318 * sdate/stime mirror the "DD-Mon-YYYY"/"HH:MM:SS" formats pw2bgw.x writes;
319 * BerkeleyGW does not parse these fields back, so exact QE locale
320 * behavior is not required, only well-formed fixed-width padding.
321 */
322 void write_title_record(std::ofstream& output_file) const {
323 std::vector<char> buffer(96, ' ');
324 write_padded_string(buffer.data(), stitle_, 32);
325
326 std::time_t now = std::time(nullptr);
327 std::tm local_time{};
328 localtime_r(&now, &local_time);
329 char date_text[16];
330 std::strftime(date_text, sizeof(date_text), "%d-%b-%Y", &local_time);
331 char time_text[16];
332 std::strftime(time_text, sizeof(time_text), "%H:%M:%S", &local_time);
333
334 write_padded_string(buffer.data() + 32, date_text, 32);
335 write_padded_string(buffer.data() + 64, time_text, 32);
336
337 record_format_.write_record(
338 output_file, buffer.data(), static_cast<int32_t>(buffer.size()));
339 }
340
341 /**
342 * Record 2 (the BerkeleyGW specification's record 2; write_wfng for WFN,
343 * write_rhog for RHO):
344 * WFN: ns ng ntran cell_symmetry nat ecutrho nk nbands ngkmax ecutwfc
345 * RHO: ns ng ntran cell_symmetry nat ecutrho
346 * ecutrho/ecutwfc are already in Rydberg in Q3 (PlanewaveBasisConfig
347 * docstring), the unit the BerkeleyGW specification gives for both cutoffs
348 * -- no conversion.
349 */
350 void write_dimension_record(std::ofstream& output_file,
351 int number_of_symmetry_operations) const {
352 int32_t number_of_spin_components = record_.number_of_spin_components;
353 int32_t number_of_g_vectors = record_.number_of_g_vectors();
354 int32_t number_of_symmetry_operations_i32 = number_of_symmetry_operations;
355 int32_t cell_symmetry_value = cell_symmetry();
356 int32_t number_of_atoms = record_.number_of_atoms();
357 double charge_density_cutoff_ry = record_.charge_density_energy_cutoff;
358
359 size_t fixed_size = 4 * sizeof(int32_t) + sizeof(int32_t) + sizeof(double);
360 size_t kpoint_fields_size =
361 include_kpoint_fields_ ? (3 * sizeof(int32_t) + sizeof(double)) : 0;
362 if (projector_dimensions_) {
363 kpoint_fields_size += 2 * sizeof(int32_t); // nsp nkb nhm in place of nbands
364 }
365 std::vector<char> buffer(fixed_size + kpoint_fields_size);
366
367 size_t offset = 0;
368 auto append = [&](const void* value, size_t size) {
369 std::memcpy(buffer.data() + offset, value, size);
370 offset += size;
371 };
372 append(&number_of_spin_components, sizeof(int32_t));
373 append(&number_of_g_vectors, sizeof(int32_t));
374 append(&number_of_symmetry_operations_i32, sizeof(int32_t));
375 append(&cell_symmetry_value, sizeof(int32_t));
376 append(&number_of_atoms, sizeof(int32_t));
377 append(&charge_density_cutoff_ry, sizeof(double));
378
379 if (include_kpoint_fields_) {
380 int32_t number_of_kpoints = record_.number_of_kpoints();
381 int32_t number_of_bands = record_.number_of_bands;
382 const auto& plane_waves_per_kpoint = record_.plane_waves_per_kpoint;
383 int32_t maximum_plane_waves = plane_waves_per_kpoint.empty()
384 ? 0
385 : *std::max_element(plane_waves_per_kpoint.begin(),
386 plane_waves_per_kpoint.end());
387 double wavefunction_cutoff_ry = record_.wavefunction_energy_cutoff;
388
389 append(&number_of_kpoints, sizeof(int32_t));
390 if (projector_dimensions_) {
391 append(projector_dimensions_->data(), 3 * sizeof(int32_t));
392 } else {
393 append(&number_of_bands, sizeof(int32_t));
394 }
395 append(&maximum_plane_waves, sizeof(int32_t));
396 append(&wavefunction_cutoff_ry, sizeof(double));
397 }
398
399 record_format_.write_record(
400 output_file, buffer.data(), static_cast<int32_t>(buffer.size()));
401 }
402
403 /**
404 * Record 3 (the BerkeleyGW specification's record 3; write_wfng for WFN,
405 * write_rhog for RHO):
406 * WFN: FFTgrid[3] kgrid[3] kshift[3]
407 * RHO: FFTgrid[3]
408 * FFTgrid is the DENSE (charge-density) FFT grid in pw2bgw.x's output, not
409 * the smooth wavefunction grid -- Q3's analog is fft_rho(), not
410 * fft_wave(). For a Monkhorst-Pack run kshift = 0.5 * (grid shift flag)
411 * on each axis, matching what write_wfng writes from QE's 0/1 K_POINTS
412 * offsets (Q3's KPointsGrid::grid_shifts() stores the same 0/1 shift-flag
413 * convention). An explicit
414 * k-point list -- how BerkeleyGW's WFN and WFNq are normally produced --
415 * has no grid in the record, so kgrid/kshift are recovered from the
416 * points themselves (kpoint_grid_from_list()); BerkeleyGW's epsilon
417 * refuses a WFN whose kgrid is 0 0 0 ("kgrid out of bounds in wfn").
418 */
419 void write_grid_record(std::ofstream& output_file) const {
420 int32_t fft_dimensions[3] = {record_.fft_grid_dimensions[0],
421 record_.fft_grid_dimensions[1],
422 record_.fft_grid_dimensions[2]};
423
424 if (!include_kpoint_fields_) {
425 record_format_.write_record(output_file, fft_dimensions, sizeof(fft_dimensions));
426 return;
427 }
428
429 const auto& grid_dimensions = record_.kpoint_grid_dimensions;
430 const auto& grid_shifts = record_.kpoint_grid_shifts;
431 int32_t kpoint_grid_dimensions[3] = {
432 grid_dimensions[0], grid_dimensions[1], grid_dimensions[2]};
433 double kpoint_shifts[3] = {
434 0.5 * grid_shifts[0], 0.5 * grid_shifts[1], 0.5 * grid_shifts[2]};
435 if (grid_dimensions[0] == 0 && grid_dimensions[1] == 0 && grid_dimensions[2] == 0) {
436 const auto [inferred_dimensions, inferred_shifts] = kpoint_grid_from_list(
437 record_.kpoint_coordinates, record_.kpoint_weights, record_.symmetry_operations);
438 for (int axis = 0; axis < 3; axis++) {
439 kpoint_grid_dimensions[axis] = inferred_dimensions[axis];
440 kpoint_shifts[axis] = inferred_shifts[axis];
441 }
442 if (inferred_dimensions[0] == 0)
443 log::warn(" BerkeleyGW header: kgrid left at 0 0 0 -- the k-point list is "
444 "neither a complete grid nor the symmetry-reduced wedge of one, so "
445 "BerkeleyGW's epsilon will not accept this WFN. List every point of "
446 "the grid instead.");
447 }
448
449 std::vector<char> buffer(sizeof(fft_dimensions) + sizeof(kpoint_grid_dimensions) +
450 sizeof(kpoint_shifts));
451 size_t offset = 0;
452 std::memcpy(buffer.data() + offset, fft_dimensions, sizeof(fft_dimensions));
453 offset += sizeof(fft_dimensions);
454 std::memcpy(buffer.data() + offset, kpoint_grid_dimensions, sizeof(kpoint_grid_dimensions));
455 offset += sizeof(kpoint_grid_dimensions);
456 std::memcpy(buffer.data() + offset, kpoint_shifts, sizeof(kpoint_shifts));
457
458 record_format_.write_record(
459 output_file, buffer.data(), static_cast<int32_t>(buffer.size()));
460 }
461
462 /**
463 * Record 4: celvol alat avec[3,3] adot[3,3] -- all float64, so a flat
464 * vector is safe (no interleaved int32 to trigger alignment padding).
465 * avec flattens vector-major (the Cartesian component is the fast index,
466 * i.e. all 3 components of lattice vector i before moving to vector
467 * i+1) -- Q3's lattice_vectors() is already stored row-per-vector, so
468 * writing rows in order is exactly this flattening. avec is in units of
469 * alat (Crystal::lattice_vectors() docstring); adot is the metric tensor,
470 * a_i . a_j scaled by alat^2 (metric_tensor()).
471 */
472 void write_direct_lattice_record(std::ofstream& output_file) const {
473 const Matrix3x3& lattice_vectors = record_.lattice_vectors;
474 Real lattice_parameter = record_.lattice_parameter;
475 Matrix3x3 direct_metric = metric_tensor(lattice_vectors, lattice_parameter);
476
477 std::vector<double> buffer;
478 buffer.reserve(20);
479 buffer.push_back(record_.cell_volume);
480 buffer.push_back(lattice_parameter);
481 for (int vector_index = 0; vector_index < 3; vector_index++) {
482 buffer.push_back(lattice_vectors[vector_index].x);
483 buffer.push_back(lattice_vectors[vector_index].y);
484 buffer.push_back(lattice_vectors[vector_index].z);
485 }
486 for (int column = 0; column < 3; column++) {
487 for (int row = 0; row < 3; row++) {
488 buffer.push_back(direct_metric[row][column]);
489 }
490 }
491
492 record_format_.write_record(
493 output_file, buffer.data(), static_cast<int32_t>(buffer.size() * sizeof(double)));
494 }
495
496 /**
497 * Record 5: recvol blat bvec(3,3) bdot(3,3) -- same shape as record 4,
498 * for the reciprocal lattice. recvol = (2*pi)^3 / celvol, the Brillouin-zone
499 * volume; blat = 2*pi/alat. bvec is in units of 2*pi/alat (Crystal's
500 * reciprocal_lattice_vectors() docstring).
501 */
502 void write_reciprocal_lattice_record(std::ofstream& output_file) const {
503 const Matrix3x3& reciprocal_lattice_vectors = record_.reciprocal_lattice_vectors;
504 Real two_pi_over_lattice_parameter = record_.two_pi_over_lattice_parameter;
505 Matrix3x3 reciprocal_metric =
506 metric_tensor(reciprocal_lattice_vectors, two_pi_over_lattice_parameter);
507
508 std::vector<double> buffer;
509 buffer.reserve(20);
510 buffer.push_back(8.0 * constants::pi * constants::pi * constants::pi / record_.cell_volume);
511 buffer.push_back(two_pi_over_lattice_parameter);
512 for (int vector_index = 0; vector_index < 3; vector_index++) {
513 buffer.push_back(reciprocal_lattice_vectors[vector_index].x);
514 buffer.push_back(reciprocal_lattice_vectors[vector_index].y);
515 buffer.push_back(reciprocal_lattice_vectors[vector_index].z);
516 }
517 for (int column = 0; column < 3; column++) {
518 for (int row = 0; row < 3; row++) {
519 buffer.push_back(reciprocal_metric[row][column]);
520 }
521 }
522
523 record_format_.write_record(
524 output_file, buffer.data(), static_cast<int32_t>(buffer.size() * sizeof(double)));
525 }
526
527 /**
528 * Record 6: mtrx(3,3,ntran) -- int32 rotation matrices in crystal
529 * coordinates, carrying QE's crystal-coordinate rotation matrix S exactly
530 * as write_wfng writes it: first index fastest, so within each 3x3 block
531 * mtrx[1][1], mtrx[2][1], mtrx[3][1], mtrx[1][2], ... S is the TRANSPOSE
532 * of Q3's real-space `rotation` (QE rotates crystal coordinates as
533 * x'_j = sum_i S_ij x_i), so mtrx[k][j] = rotation[j][k] and the bytes are
534 * `rotation` flattened row by row. The BerkeleyGW specification describes
535 * mtrx only as rotation matrices in the reciprocal-lattice basis; the
536 * exact convention -- transposition, and pairing with record 7 -- is
537 * pinned by the comparisons that follow, not by the specification.
538 * Checked operation by operation against QE 7.5's data-file-schema.xml and pw2bgw output for
539 * wurtzite ZnO, diamond Si and trigonal Te. BerkeleyGW reads mtrx as the
540 * same matrix QE uses, so writing `rotation` itself hands it R^T: the
541 * group is unchanged, but each matrix is paired with the wrong record-7
542 * translation, and epsilon stops with "G-vectors are falling outside of
543 * the sphere in gmap" on any lattice where R^T is not R^-1.
544 */
545 void write_symmetry_rotation_record(
546 std::ofstream& output_file,
547 const std::vector<SymmetryOperation>& symmetry_operations) const {
548 std::vector<int32_t> buffer;
549 buffer.reserve(9 * symmetry_operations.size());
550 for (const auto& operation : symmetry_operations) {
551 for (int row = 0; row < 3; row++) {
552 for (int column = 0; column < 3; column++) {
553 buffer.push_back(operation.rotation[row][column]);
554 }
555 }
556 }
557 record_format_.write_record(
558 output_file, buffer.data(), static_cast<int32_t>(buffer.size() * sizeof(int32_t)));
559 }
560
561 /**
562 * Record 7: tnp(3,ntran) -- float64 fractional translations in crystal
563 * coordinates times 2*pi, NOT SymmetryOperation::fractional_translation
564 * copied verbatim: the BerkeleyGW specification gives tnp in units of the
565 * lattice vectors times 2*pi, and write_wfng derives it from QE's rotation
566 * S and fractional translation f as (S^-1)^T f, reduced modulo 1, then
567 * scaled by 2*pi. In Q3's terms S = rotation^T (record 6) and
568 * f = -fractional_translation (QE
569 * stores its translations with the opposite sign; the same check against
570 * QE 7.5's XML confirms it for every operation), so
571 *
572 * tnp / 2*pi = -rotation^-1 . fractional_translation
573 *
574 * -- the translation of the inverse operation {R|t}^-1 = {R^-1|-R^-1 t}.
575 * Q3 already stores rotation^-1 transposed, as reciprocal_rotation =
576 * (rotation^-1)^T (operation.hpp), so rotation^-1 . t contracts t with
577 * reciprocal_rotation's FIRST index. The trigonal-Te test pins the sign
578 * against pw2bgw.x's own output: there the 3_1 screw's c/3 and -c/3 differ.
579 *
580 * The result is then reduced to a canonical representative modulo 1. The
581 * 1e-6 is added to BOTH comparison thresholds rather than forming a
582 * symmetric dead-band, so the interval is (-0.5, 0.5] widened at the top
583 * by 1e-6: an input already in [0.5, 0.5 + 1e-6) is left alone, and an
584 * input of exactly -0.5 is mapped to +0.5. The effect is that a
585 * translation sitting exactly on the boundary -- 0.5 is common, e.g.
586 * glide planes and the diamond structure -- stays put instead of flipping
587 * sign under rounding noise. Truncation toward zero rather than
588 * round-to-nearest is what places the closed end at +0.5; pw2bgw.x makes
589 * the same choice.
590 */
591 void write_symmetry_translation_record(
592 std::ofstream& output_file,
593 const std::vector<SymmetryOperation>& symmetry_operations) const {
594 constexpr Real boundary_tolerance = 1.0e-6; // the same 1e-6 pw2bgw.x widens by
595 std::vector<double> buffer;
596 buffer.reserve(3 * symmetry_operations.size());
597 for (const auto& operation : symmetry_operations) {
598 Real rotated_translation[3];
599 for (int component = 0; component < 3; component++) {
600 Real sum = 0.0;
601 for (int contracted = 0; contracted < 3; contracted++) {
602 sum -= static_cast<Real>(operation.reciprocal_rotation[contracted][component]) *
603 operation.fractional_translation[contracted];
604 }
605 rotated_translation[component] = sum;
606 }
607 for (int component = 0; component < 3; component++) {
608 Real& value = rotated_translation[component];
609 if (value >= boundary_tolerance + 0.5)
610 value -= std::trunc(value + 0.5);
611 if (value < boundary_tolerance - 0.5)
612 value -= std::trunc(value - 0.5);
613 }
614 for (int component = 0; component < 3; component++) {
615 buffer.push_back(rotated_translation[component] * constants::two_pi);
616 }
617 }
618 record_format_.write_record(
619 output_file, buffer.data(), static_cast<int32_t>(buffer.size() * sizeof(double)));
620 }
621
622 /**
623 * Record 8: (apos(3,i), Z_i) for i=1..nat -- interleaved per atom (3
624 * float64 then 1 int32, repeated), NOT a uniform array of one type, so a
625 * manual byte buffer is required (a `struct{double[3];int32_t;}` would
626 * get 4 bytes of trailing alignment padding per atom to keep the array
627 * element size a multiple of 8, corrupting the interleaving for atom 2
628 * onward). Positions are Cartesian in units of alat
629 * (Crystal::convert_from_crystal_to_cartesian()); Z is the atomic number
630 * (the BerkeleyGW specification's atomic numbers, as Crystal::atomic_number()
631 * gives them, and as write_wfng writes them).
632 */
633 void write_atom_record(std::ofstream& output_file) const {
634 int number_of_atoms = record_.number_of_atoms();
635 std::vector<char> buffer(number_of_atoms * (3 * sizeof(double) + sizeof(int32_t)));
636
637 size_t offset = 0;
638 for (int atom_index = 0; atom_index < number_of_atoms; atom_index++) {
639 const Vector3D& position = record_.atom_positions_cartesian[atom_index];
640 double position_components[3] = {position.x, position.y, position.z};
641 std::memcpy(buffer.data() + offset, position_components, sizeof(position_components));
642 offset += sizeof(position_components);
643
644 int32_t atomic_number = record_.atomic_numbers[atom_index];
645 std::memcpy(buffer.data() + offset, &atomic_number, sizeof(atomic_number));
646 offset += sizeof(atomic_number);
647 }
648
649 record_format_.write_record(
650 output_file, buffer.data(), static_cast<int32_t>(buffer.size()));
651 }
652};
653
654} // namespace q3
static std::pair< std::array< int32_t, 3 >, std::array< double, 3 > > complete_kpoint_grid(const std::vector< Vector3D > &kpoint_coordinates)
Definition header.hpp:208
static std::pair< std::array< int32_t, 3 >, std::array< double, 3 > > kpoint_grid_from_list(const std::vector< Vector3D > &kpoint_coordinates, const std::vector< Real > &kpoint_weights, const std::vector< SymmetryOperation > &symmetry_operations)
Definition header.hpp:164
BerkeleyGWCommonHeader(const MeanFieldRecord &record, std::string stitle, const std::array< int32_t, 3 > &projector_dimensions)
Definition header.hpp:125
void write(std::ofstream &output_file) const
Write records 1 through 8 to output_file.
Definition header.hpp:256
BerkeleyGWCommonHeader(const MeanFieldRecord &&, std::string, bool)=delete
See the writers' own deleted rvalue constructors: this holds a reference.
BerkeleyGWCommonHeader(const MeanFieldRecord &&, std::string, const std::array< int32_t, 3 > &)=delete
BerkeleyGWCommonHeader(const MeanFieldRecord &record, std::string stitle, bool include_kpoint_fields)
Definition header.hpp:112
void write_record(std::ofstream &output_file, const void *data, int32_t number_of_bytes) const override
Definition qe.hpp:16
constexpr Real pi
Definition constants.hpp:24
constexpr Real two_pi
Definition constants.hpp:25
void warn(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:187
constexpr double kpoint_grid_offset_tolerance
double Real
int berkeleygw_cell_symmetry(int bravais_lattice_index, const Matrix3x3 &lattice_vectors)
std::vector< Vector3D > kpoint_coordinates
Crystal (fractional) coordinates, one per physical k-point.
Definition init.hpp:118
int number_of_atoms() const
Definition init.hpp:188
Real wavefunction_energy_cutoff
Definition init.hpp:108
int number_of_spin_components
Definition init.hpp:127
std::array< int, 3 > kpoint_grid_shifts
Definition init.hpp:116
std::vector< SymmetryOperation > symmetry_operations
Definition init.hpp:92
Matrix3x3 reciprocal_lattice_vectors
Definition init.hpp:79
int number_of_kpoints() const
Definition init.hpp:184
Real lattice_parameter
Definition init.hpp:75
int bravais_lattice_index
QE ibrav convention; 0 means explicit/free lattice vectors.
Definition init.hpp:74
Matrix3x3 lattice_vectors
Definition init.hpp:78
std::vector< int > plane_waves_per_kpoint
Definition init.hpp:121
std::vector< int > atomic_numbers
Definition init.hpp:84
std::array< int, 3 > fft_grid_dimensions
Charge-density ("dense") FFT grid dimensions.
Definition init.hpp:96
std::array< int, 3 > kpoint_grid_dimensions
Definition init.hpp:115
Real charge_density_energy_cutoff
Plane-wave kinetic-energy cutoffs, Rydberg (BGW's ecutrho/ecutwfc).
Definition init.hpp:107
int number_of_g_vectors() const
Definition init.hpp:192
std::vector< Vector3D > atom_positions_cartesian
Cartesian positions in alat units, parallel to atomic_numbers.
Definition init.hpp:83
Real two_pi_over_lattice_parameter
Definition init.hpp:76
std::vector< Real > kpoint_weights
Engine-convention weights; formats renormalize as they require.
Definition init.hpp:120