Loading...
Searching...
No Matches
checkpoint.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * Persistence for MeanFieldRecord — the file half of the converter seam.
5 *
6 * `write_mean_field_record()` drops a complete mean-field description into
7 * `<prefix>.save/mean-field.q3mf` at the end of a run; `read_mean_field_record()`
8 * loads it back, so the standalone converter can produce any supported format
9 * without re-running the SCF. Getting a file wrong then costs a re-convert
10 * rather than a re-calculation.
11 *
12 * This is a NEW file rather than an extension of `effective-potential.q3cp`
13 * because the two answer different questions. That checkpoint exists to restart
14 * an NSCF run and carries what the engine needs to resume; this one exists to
15 * describe a converged mean field to an outside code and carries what any
16 * external format needs (symmetry, k-point metadata, occupations, V_xc). They
17 * overlap but neither contains the other, and conflating them would make each
18 * carry fields the other's consumer must learn to ignore.
19 *
20 * Format: "Q3MF" magic, a version, then length-prefixed arrays, and finally the
21 * wavefunction coefficients as one contiguous block. Fields are written
22 * component by component rather than by memcpy-ing structs, so the file does
23 * not silently depend on host padding. Reads validate the magic and version and
24 * refuse anything else instead of misparsing it.
25 *
26 * The coefficient block is laid out so `FileMeanFieldBandSource` can seek
27 * straight to a band: all bands at one k-point share that k-point's plane-wave
28 * count, so a k-point's offset is a running sum of
29 * `number_of_bands * plane_waves_per_kpoint[k]`, computed on load. Nothing is
30 * held in memory beyond the k-point currently being read.
31 */
32
33#include <cstdint>
34#include <fstream>
35#include <memory>
36#include <stdexcept>
37#include <string>
38#include <vector>
39
41
42namespace q3 {
43
44namespace internal {
45
46constexpr uint32_t mean_field_record_magic = 0x464D3351; // "Q3MF"
47constexpr uint32_t mean_field_record_version = 1;
48
49template <typename ValueType> void write_scalar(std::ostream& stream, const ValueType& value) {
50 stream.write(reinterpret_cast<const char*>(&value), sizeof(ValueType));
51}
52
53template <typename ValueType> ValueType read_scalar(std::istream& stream) {
54 ValueType value{};
55 stream.read(reinterpret_cast<char*>(&value), sizeof(ValueType));
56 return value;
57}
58
59inline void write_vector3d(std::ostream& stream, const Vector3D& vector) {
60 write_scalar<double>(stream, vector.x);
61 write_scalar<double>(stream, vector.y);
62 write_scalar<double>(stream, vector.z);
63}
64
65inline Vector3D read_vector3d(std::istream& stream) {
66 Vector3D vector;
67 vector.x = read_scalar<double>(stream);
68 vector.y = read_scalar<double>(stream);
69 vector.z = read_scalar<double>(stream);
70 return vector;
71}
72
73/// Length-prefixed array of trivially copyable values.
74template <typename ValueType>
75void write_array(std::ostream& stream, const std::vector<ValueType>& values) {
76 write_scalar<int64_t>(stream, static_cast<int64_t>(values.size()));
77 if (!values.empty()) {
78 stream.write(reinterpret_cast<const char*>(values.data()),
79 static_cast<std::streamsize>(values.size() * sizeof(ValueType)));
80 }
81}
82
83template <typename ValueType> std::vector<ValueType> read_array(std::istream& stream) {
84 int64_t count = read_scalar<int64_t>(stream);
85 if (count < 0)
86 throw std::runtime_error("mean-field record: negative array length; the file is corrupt.");
87 std::vector<ValueType> values(static_cast<size_t>(count));
88 if (count > 0) {
89 stream.read(reinterpret_cast<char*>(values.data()),
90 static_cast<std::streamsize>(values.size() * sizeof(ValueType)));
91 }
92 return values;
93}
94
95} // namespace internal
96
97/**
98 * Band source that reads coefficients back from a mean-field record file.
99 *
100 * Caches exactly one k-point: exporters walk (k, band) in order, so this turns
101 * into one sequential read per k-point while keeping peak memory to a single
102 * k-point's worth of coefficients rather than the whole wavefunction set.
103 */
105 public:
106 FileMeanFieldBandSource(std::string file_path,
107 std::streamoff coefficient_block_offset,
108 int number_of_bands,
109 std::vector<int> plane_waves_per_kpoint)
110 : file_path_(std::move(file_path))
111 , coefficient_block_offset_(coefficient_block_offset)
112 , number_of_bands_(number_of_bands)
113 , plane_waves_per_kpoint_(std::move(plane_waves_per_kpoint)) {
114 kpoint_start_offsets_.resize(plane_waves_per_kpoint_.size() + 1, 0);
115 for (size_t kpoint_index = 0; kpoint_index < plane_waves_per_kpoint_.size();
116 kpoint_index++) {
117 kpoint_start_offsets_[kpoint_index + 1] =
118 kpoint_start_offsets_[kpoint_index] +
119 static_cast<std::streamoff>(number_of_bands_) *
120 plane_waves_per_kpoint_[kpoint_index] *
121 static_cast<std::streamoff>(sizeof(Complex));
122 }
123 }
124
125 [[nodiscard]] const Complex* coefficients(int kpoint_state_index,
126 int band_index) const override {
127 if (kpoint_state_index != cached_kpoint_state_index_) {
128 load_kpoint(kpoint_state_index);
129 }
130 int plane_wave_count = plane_waves_per_kpoint_[kpoint_state_index];
131 return cached_coefficients_.data() +
132 static_cast<size_t>(band_index) * static_cast<size_t>(plane_wave_count);
133 }
134
135 private:
136 void load_kpoint(int kpoint_state_index) const {
137 if (kpoint_state_index < 0 ||
138 kpoint_state_index >= static_cast<int>(plane_waves_per_kpoint_.size())) {
139 throw std::runtime_error("mean-field record: k-point state index " +
140 std::to_string(kpoint_state_index) + " is out of range.");
141 }
142 std::ifstream stream(file_path_, std::ios::binary);
143 if (!stream.is_open()) {
144 throw std::runtime_error("mean-field record: cannot reopen '" + file_path_ +
145 "' to read wavefunction coefficients.");
146 }
147 int plane_wave_count = plane_waves_per_kpoint_[kpoint_state_index];
148 size_t value_count = static_cast<size_t>(number_of_bands_) * plane_wave_count;
149 cached_coefficients_.resize(value_count);
150 stream.seekg(coefficient_block_offset_ + kpoint_start_offsets_[kpoint_state_index]);
151 stream.read(reinterpret_cast<char*>(cached_coefficients_.data()),
152 static_cast<std::streamsize>(value_count * sizeof(Complex)));
153 if (!stream) {
154 throw std::runtime_error("mean-field record: '" + file_path_ +
155 "' ended while reading coefficients for k-point state " +
156 std::to_string(kpoint_state_index) +
157 "; the file is truncated or corrupt.");
158 }
159 cached_kpoint_state_index_ = kpoint_state_index;
160 }
161
162 std::string file_path_;
163 std::streamoff coefficient_block_offset_;
164 int number_of_bands_;
165 std::vector<int> plane_waves_per_kpoint_;
166 std::vector<std::streamoff> kpoint_start_offsets_;
167 mutable std::vector<Complex> cached_coefficients_;
168 mutable int cached_kpoint_state_index_ = -1;
169};
170
171/// Serializes `record` to `file_path`. Throws on any write failure.
172inline void write_mean_field_record(const MeanFieldRecord& record, const std::string& file_path) {
173 using namespace internal;
174 std::ofstream stream(file_path, std::ios::binary);
175 if (!stream.is_open())
176 throw std::runtime_error("mean-field record: cannot open '" + file_path + "' for writing.");
177
178 write_scalar<uint32_t>(stream, mean_field_record_magic);
179 write_scalar<uint32_t>(stream, mean_field_record_version);
180
181 write_scalar<int32_t>(stream, record.bravais_lattice_index);
182 write_scalar<double>(stream, record.lattice_parameter);
183 write_scalar<double>(stream, record.two_pi_over_lattice_parameter);
184 write_scalar<double>(stream, record.cell_volume);
185 for (int row = 0; row < 3; row++)
186 write_vector3d(stream, record.lattice_vectors[row]);
187 for (int row = 0; row < 3; row++)
188 write_vector3d(stream, record.reciprocal_lattice_vectors[row]);
189
190 write_scalar<int64_t>(stream, static_cast<int64_t>(record.atomic_numbers.size()));
191 for (const auto& position : record.atom_positions_cartesian)
192 write_vector3d(stream, position);
193 write_array(stream, record.atomic_numbers);
194
195 write_scalar<int64_t>(stream, static_cast<int64_t>(record.symmetry_operations.size()));
196 for (const auto& operation : record.symmetry_operations) {
197 for (int row = 0; row < 3; row++)
198 for (int column = 0; column < 3; column++)
199 write_scalar<int32_t>(stream, operation.rotation[row][column]);
200 for (int row = 0; row < 3; row++)
201 for (int column = 0; column < 3; column++)
202 write_scalar<int32_t>(stream, operation.reciprocal_rotation[row][column]);
203 write_vector3d(stream, operation.fractional_translation);
204 write_array(stream, operation.atom_map);
205 }
206
207 for (int axis = 0; axis < 3; axis++)
208 write_scalar<int32_t>(stream, record.fft_grid_dimensions[axis]);
209 write_array(stream, record.charge_density_miller_indices);
210 write_scalar<uint8_t>(stream, record.is_gamma_only ? 1 : 0);
211 write_scalar<double>(stream, record.charge_density_energy_cutoff);
212 write_scalar<double>(stream, record.wavefunction_energy_cutoff);
213
214 for (int axis = 0; axis < 3; axis++)
215 write_scalar<int32_t>(stream, record.kpoint_grid_dimensions[axis]);
216 for (int axis = 0; axis < 3; axis++)
217 write_scalar<int32_t>(stream, record.kpoint_grid_shifts[axis]);
218 write_scalar<int64_t>(stream, static_cast<int64_t>(record.kpoint_coordinates.size()));
219 for (const auto& coordinates : record.kpoint_coordinates)
220 write_vector3d(stream, coordinates);
221 write_array(stream, record.kpoint_weights);
222 write_array(stream, record.plane_waves_per_kpoint);
223 write_scalar<int64_t>(stream, static_cast<int64_t>(record.kpoint_g_vector_indices.size()));
224 for (const auto& indices : record.kpoint_g_vector_indices)
225 write_array(stream, indices);
226
227 write_scalar<int32_t>(stream, record.number_of_bands);
228 write_scalar<int32_t>(stream, record.number_of_spin_components);
229 write_scalar<int64_t>(stream, static_cast<int64_t>(record.eigenvalues_rydberg.size()));
230 for (const auto& row : record.eigenvalues_rydberg)
231 write_array(stream, row);
232 write_scalar<int64_t>(stream, static_cast<int64_t>(record.occupations.size()));
233 for (const auto& row : record.occupations)
234 write_array(stream, row);
235
236 write_scalar<int64_t>(stream, static_cast<int64_t>(record.charge_density_g.size()));
237 for (const auto& spin_density : record.charge_density_g)
238 write_array(stream, spin_density);
239 write_array(stream, record.exchange_correlation_potential_g);
240
241 /**
242 * Coefficients last, as one contiguous block, so their start offset is
243 * everything written so far and a reader can seek per k-point.
244 */
245 if (!record.band_source) {
246 throw std::runtime_error("mean-field record: the record has no band source, so its "
247 "wavefunction coefficients cannot be written.");
248 }
249 for (int state_index = 0; state_index < record.number_of_kpoint_states(); state_index++) {
250 int plane_wave_count = record.plane_waves_per_kpoint[state_index];
251 for (int band_index = 0; band_index < record.number_of_bands; band_index++) {
252 const Complex* coefficients = record.band_source->coefficients(state_index, band_index);
253 stream.write(reinterpret_cast<const char*>(coefficients),
254 static_cast<std::streamsize>(plane_wave_count * sizeof(Complex)));
255 }
256 }
257
258 if (!stream)
259 throw std::runtime_error("mean-field record: writing '" + file_path + "' failed.");
260}
261
262/// Loads a record written by write_mean_field_record(). Throws on any problem.
263[[nodiscard]] inline MeanFieldRecord read_mean_field_record(const std::string& file_path) {
264 using namespace internal;
265 std::ifstream stream(file_path, std::ios::binary);
266 if (!stream.is_open())
267 throw std::runtime_error("mean-field record: cannot open '" + file_path + "' for reading.");
268
269 uint32_t magic = read_scalar<uint32_t>(stream);
270 if (magic != mean_field_record_magic) {
271 throw std::runtime_error("mean-field record: '" + file_path +
272 "' is not a Q3 mean-field record (bad magic number).");
273 }
274 uint32_t version = read_scalar<uint32_t>(stream);
275 if (version != mean_field_record_version) {
276 throw std::runtime_error("mean-field record: '" + file_path + "' is version " +
277 std::to_string(version) + ", but this build reads version " +
278 std::to_string(mean_field_record_version) +
279 ". Re-run the calculation to regenerate it.");
280 }
281
282 MeanFieldRecord record;
283 record.bravais_lattice_index = read_scalar<int32_t>(stream);
284 record.lattice_parameter = read_scalar<double>(stream);
285 record.two_pi_over_lattice_parameter = read_scalar<double>(stream);
286 record.cell_volume = read_scalar<double>(stream);
287 for (int row = 0; row < 3; row++)
288 record.lattice_vectors[row] = read_vector3d(stream);
289 for (int row = 0; row < 3; row++)
290 record.reciprocal_lattice_vectors[row] = read_vector3d(stream);
291
292 int64_t number_of_atoms = read_scalar<int64_t>(stream);
293 record.atom_positions_cartesian.resize(static_cast<size_t>(number_of_atoms));
294 for (auto& position : record.atom_positions_cartesian)
295 position = read_vector3d(stream);
296 record.atomic_numbers = read_array<int>(stream);
297
298 int64_t number_of_symmetry_operations = read_scalar<int64_t>(stream);
299 record.symmetry_operations.resize(static_cast<size_t>(number_of_symmetry_operations));
300 for (auto& operation : record.symmetry_operations) {
301 for (int row = 0; row < 3; row++)
302 for (int column = 0; column < 3; column++)
303 operation.rotation[row][column] = read_scalar<int32_t>(stream);
304 for (int row = 0; row < 3; row++)
305 for (int column = 0; column < 3; column++)
306 operation.reciprocal_rotation[row][column] = read_scalar<int32_t>(stream);
307 operation.fractional_translation = read_vector3d(stream);
308 operation.atom_map = read_array<int>(stream);
309 }
310
311 for (int axis = 0; axis < 3; axis++)
312 record.fft_grid_dimensions[axis] = read_scalar<int32_t>(stream);
313 record.charge_density_miller_indices = read_array<IntegerVector3D>(stream);
314 record.is_gamma_only = read_scalar<uint8_t>(stream) != 0;
315 record.charge_density_energy_cutoff = read_scalar<double>(stream);
316 record.wavefunction_energy_cutoff = read_scalar<double>(stream);
317
318 for (int axis = 0; axis < 3; axis++)
319 record.kpoint_grid_dimensions[axis] = read_scalar<int32_t>(stream);
320 for (int axis = 0; axis < 3; axis++)
321 record.kpoint_grid_shifts[axis] = read_scalar<int32_t>(stream);
322 int64_t number_of_kpoints = read_scalar<int64_t>(stream);
323 record.kpoint_coordinates.resize(static_cast<size_t>(number_of_kpoints));
324 for (auto& coordinates : record.kpoint_coordinates)
325 coordinates = read_vector3d(stream);
326 record.kpoint_weights = read_array<Real>(stream);
327 record.plane_waves_per_kpoint = read_array<int>(stream);
328 int64_t kpoint_g_set_count = read_scalar<int64_t>(stream);
329 record.kpoint_g_vector_indices.resize(static_cast<size_t>(kpoint_g_set_count));
330 for (auto& indices : record.kpoint_g_vector_indices)
331 indices = read_array<int>(stream);
332
333 record.number_of_bands = read_scalar<int32_t>(stream);
334 record.number_of_spin_components = read_scalar<int32_t>(stream);
335 int64_t eigenvalue_row_count = read_scalar<int64_t>(stream);
336 record.eigenvalues_rydberg.resize(static_cast<size_t>(eigenvalue_row_count));
337 for (auto& row : record.eigenvalues_rydberg)
338 row = read_array<Real>(stream);
339 int64_t occupation_row_count = read_scalar<int64_t>(stream);
340 record.occupations.resize(static_cast<size_t>(occupation_row_count));
341 for (auto& row : record.occupations)
342 row = read_array<Real>(stream);
343
344 int64_t spin_channel_count = read_scalar<int64_t>(stream);
345 record.charge_density_g.resize(static_cast<size_t>(spin_channel_count));
346 for (auto& spin_density : record.charge_density_g)
347 spin_density = read_array<Complex>(stream);
348 record.exchange_correlation_potential_g = read_array<Complex>(stream);
349
350 if (!stream) {
351 throw std::runtime_error("mean-field record: '" + file_path +
352 "' ended before the header was fully read; it is truncated.");
353 }
354
355 record.band_source = std::make_shared<FileMeanFieldBandSource>(
356 file_path, stream.tellg(), record.number_of_bands, record.plane_waves_per_kpoint);
357 return record;
358}
359
360} // namespace q3
const Complex * coefficients(int kpoint_state_index, int band_index) const override
FileMeanFieldBandSource(std::string file_path, std::streamoff coefficient_block_offset, int number_of_bands, std::vector< int > plane_waves_per_kpoint)
std::vector< ValueType > read_array(std::istream &stream)
constexpr uint32_t mean_field_record_magic
ValueType read_scalar(std::istream &stream)
void write_array(std::ostream &stream, const std::vector< ValueType > &values)
Length-prefixed array of trivially copyable values.
void write_vector3d(std::ostream &stream, const Vector3D &vector)
Vector3D read_vector3d(std::istream &stream)
constexpr uint32_t mean_field_record_version
void write_scalar(std::ostream &stream, const ValueType &value)
constexpr const char * version
Definition version.hpp:26
void write_mean_field_record(const MeanFieldRecord &record, const std::string &file_path)
Serializes record to file_path. Throws on any write failure.
MeanFieldRecord read_mean_field_record(const std::string &file_path)
Loads a record written by write_mean_field_record(). Throws on any problem.
std::complex< double > Complex
std::vector< Vector3D > kpoint_coordinates
Crystal (fractional) coordinates, one per physical k-point.
Definition init.hpp:118
std::vector< std::vector< Real > > occupations
Indexed [kpoint_state_index][band_index]; max 1 per (k, spin) state.
Definition init.hpp:131
std::vector< std::vector< int > > kpoint_g_vector_indices
Per physical k-point, indices into charge_density_miller_indices.
Definition init.hpp:123
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
std::vector< std::vector< Complex > > charge_density_g
Definition init.hpp:148
std::vector< std::vector< Real > > eigenvalues_rydberg
Indexed [kpoint_state_index][band_index]; Rydberg.
Definition init.hpp:129
Real lattice_parameter
Definition init.hpp:75
std::vector< Complex > exchange_correlation_potential_g
Definition init.hpp:154
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_kpoint_states() const
(k-point, spin) state count – the row count of eigenvalues/occupations.
Definition init.hpp:197
std::shared_ptr< const MeanFieldBandSource > band_source
Definition init.hpp:132
std::vector< IntegerVector3D > charge_density_miller_indices
Definition init.hpp:104
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