Loading...
Searching...
No Matches
qe_reader.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * QEPlanewaveKohnShamChargeDensityReader — read a `charge-density.dat` back.
5 *
6 * The counterpart of the writer in qe.hpp. A converged density is the one
7 * thing a plane-wave run saves that every downstream question needs, and until
8 * now nothing could read it: a density Quantum ESPRESSO produced could not be
9 * brought into Q3, and a density Q3 wrote could only be looked at by rerunning
10 * the calculation that produced it.
11 *
12 * Both dialects of the file are read, and they differ in two places.
13 *
14 * **The reciprocal-lattice record.** Quantum ESPRESSO writes
15 * `gamma_only, ngm, nspin`, then `b1, b2, b3`, then the Miller indices, then
16 * one rho(G) record per spin channel (the format is described in its
17 * `Modules/io_base.f90`; cited, not transcribed — see PROVENANCE.md). The
18 * writer in qe.hpp omits the reciprocal-lattice record. Which dialect a file
19 * is in is therefore decided by the length of the second record: nine doubles
20 * is the cell, anything else is already the Miller indices.
21 *
22 * **The Miller index layout.** Quantum ESPRESSO's array is `mill(3, ngm)`, so
23 * the record interleaves each G-vector's three components; Q3's writer emits
24 * the transpose, `mill(ngm, 3)` — every first component, then every second,
25 * then every third. There is no way to tell those apart from the record's
26 * length, since both hold `3 * ngm` integers, so the dialect established by
27 * the record above decides this too. That coupling is the reason the detection
28 * is stated here rather than buried: a file with a reciprocal-lattice record
29 * and Q3's index layout, or the reverse, would be read silently and wrongly.
30 * Neither producer emits one.
31 *
32 * A gamma-only file holds the half sphere {G : G·ẑ ≥ 0 by the engine's
33 * ordering}; the caller reconstructs -G by conjugation, which is what
34 * `real_space_field_from_reciprocal()` does.
35 */
36
37#include <cstdint>
38#include <fstream>
39#include <stdexcept>
40#include <string>
41#include <vector>
42
44
45namespace q3 {
46
48 public:
49 /// Read `path`, replacing whatever this reader held; throws on a malformed file.
50 void read(const std::string& path) {
51 std::ifstream stream(path, std::ios::binary);
52 if (!stream)
53 throw std::runtime_error("Cannot read charge density file: " + path);
54 path_ = path;
55
56 std::vector<char> header = read_record(stream);
57 if (header.size() != 3 * sizeof(int32_t))
58 throw_corrupt("its first record is not the three-integer header");
59 const auto* header_values = reinterpret_cast<const int32_t*>(header.data());
60 is_gamma_only_ = header_values[0] != 0;
61 const int number_of_g_vectors = header_values[1];
62 number_of_spin_components_ = header_values[2];
63 if (number_of_g_vectors <= 0 || number_of_spin_components_ <= 0 ||
64 number_of_spin_components_ > 2) {
65 throw_corrupt("its header declares " + std::to_string(number_of_g_vectors) +
66 " G-vector(s) and " + std::to_string(number_of_spin_components_) +
67 " spin component(s)");
68 }
69
70 std::vector<char> second = read_record(stream);
71 has_reciprocal_lattice_vectors_ = second.size() == 9 * sizeof(double);
72 if (has_reciprocal_lattice_vectors_) {
73 const auto* vectors = reinterpret_cast<const double*>(second.data());
74 for (int row = 0; row < 3; row++)
75 for (int column = 0; column < 3; column++)
76 reciprocal_lattice_vectors_[row][column] = vectors[row * 3 + column];
77 second = read_record(stream);
78 }
79
80 if (second.size() != 3 * static_cast<std::size_t>(number_of_g_vectors) * sizeof(int32_t))
81 throw_corrupt("its Miller-index record is the wrong length");
82 const auto* miller_values = reinterpret_cast<const int32_t*>(second.data());
83 miller_indices_.resize(number_of_g_vectors);
84 for (int g_index = 0; g_index < number_of_g_vectors; g_index++) {
85 if (has_reciprocal_lattice_vectors_) {
86 miller_indices_[g_index] = {miller_values[3 * g_index],
87 miller_values[3 * g_index + 1],
88 miller_values[3 * g_index + 2]};
89 } else {
90 miller_indices_[g_index] = {miller_values[g_index],
91 miller_values[number_of_g_vectors + g_index],
92 miller_values[2 * number_of_g_vectors + g_index]};
93 }
94 }
95
96 charge_density_g_.assign(number_of_spin_components_, {});
97 for (int spin = 0; spin < number_of_spin_components_; spin++) {
98 std::vector<char> density = read_record(stream);
99 if (density.size() != static_cast<std::size_t>(number_of_g_vectors) * sizeof(Complex))
100 throw_corrupt("its rho(G) record for spin channel " + std::to_string(spin + 1) +
101 " is the wrong length");
102 const auto* values = reinterpret_cast<const Complex*>(density.data());
103 charge_density_g_[spin].assign(values, values + number_of_g_vectors);
104 }
105 }
106
107 [[nodiscard]] bool is_gamma_only() const {
108 return is_gamma_only_;
109 }
110 [[nodiscard]] int number_of_spin_components() const {
111 return number_of_spin_components_;
112 }
113 [[nodiscard]] int number_of_g_vectors() const {
114 return static_cast<int>(miller_indices_.size());
115 }
116 [[nodiscard]] const std::vector<IntegerVector3D>& miller_indices() const {
117 return miller_indices_;
118 }
119 /// rho(G) per spin channel, each parallel to `miller_indices()`.
120 [[nodiscard]] const std::vector<std::vector<Complex>>& charge_density_g() const {
121 return charge_density_g_;
122 }
123 /// True only for a Quantum ESPRESSO file, which carries the cell; see the header.
124 [[nodiscard]] bool has_reciprocal_lattice_vectors() const {
125 return has_reciprocal_lattice_vectors_;
126 }
127 /// Rows b1, b2, b3 in 1/bohr; meaningful only when the flag above is set.
128 [[nodiscard]] const Matrix3x3& reciprocal_lattice_vectors() const {
129 return reciprocal_lattice_vectors_;
130 }
131
132 private:
133 std::string path_;
134 bool is_gamma_only_ = false;
135 int number_of_spin_components_ = 0;
136 bool has_reciprocal_lattice_vectors_ = false;
137 Matrix3x3 reciprocal_lattice_vectors_{};
138 std::vector<IntegerVector3D> miller_indices_;
139 std::vector<std::vector<Complex>> charge_density_g_;
140
141 [[noreturn]] void throw_corrupt(const std::string& reason) const {
142 throw std::runtime_error("'" + path_ +
143 "' is not a readable charge-density file: " + reason + ".");
144 }
145
146 /**
147 * One Fortran unformatted sequential record, whose payload length the
148 * caller does not know: the leading marker is what says which dialect the
149 * file is in, so it is read rather than asserted.
150 */
151 std::vector<char> read_record(std::ifstream& stream) const {
152 int32_t prefix_length = 0;
153 stream.read(reinterpret_cast<char*>(&prefix_length), sizeof(prefix_length));
154 if (!stream || prefix_length < 0)
155 throw_corrupt("it ends where a record marker was expected");
156 std::vector<char> payload(static_cast<std::size_t>(prefix_length));
157 stream.read(payload.data(), prefix_length);
158 int32_t suffix_length = 0;
159 stream.read(reinterpret_cast<char*>(&suffix_length), sizeof(suffix_length));
160 if (!stream || suffix_length != prefix_length)
161 throw_corrupt("a record's trailing length marker does not match its leading one");
162 return payload;
163 }
164};
165
166} // namespace q3
bool has_reciprocal_lattice_vectors() const
True only for a Quantum ESPRESSO file, which carries the cell; see the header.
void read(const std::string &path)
Read path, replacing whatever this reader held; throws on a malformed file.
Definition qe_reader.hpp:50
const std::vector< IntegerVector3D > & miller_indices() const
const Matrix3x3 & reciprocal_lattice_vectors() const
Rows b1, b2, b3 in 1/bohr; meaningful only when the flag above is set.
const std::vector< std::vector< Complex > > & charge_density_g() const
rho(G) per spin channel, each parallel to miller_indices().
std::complex< double > Complex
3×3 matrix stored row-major: row i = mat[i]