Loading...
Searching...
No Matches
exchange_correlation.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * BerkeleyGWExchangeCorrelationFile — writes BerkeleyGW's `vxc.dat` mean-field file.
5 *
6 * Layout (the BerkeleyGW specification's vxc.dat section, with the column
7 * widths of pw2bgw.x's `write_vxc_g`/`write_vxc_r`, which write byte-identical
8 * text -- see header.hpp for both citations): per k-point, one ASCII header
9 * line `kx ky kz ndiag noffdiag` -- three 13-wide fixed-point fields with 9
10 * decimals, then two 8-wide integers (k in CRYSTAL coordinates; the two
11 * integers are already multiplied by nspin -- nspin*ndiag, and nspin times
12 * the number of off-diagonal band pairs) -- then `ndiag` diagonal lines
13 * `spin band Re(Vxc) Im(Vxc)` -- two 8-wide integers, then two 15-wide
14 * fixed-point fields with 9 decimals. Diagonal-only is in scope here, so
15 * `noffdiag = 0` always and no off-diagonal lines (three 8-wide integers,
16 * then the two values) are ever written. `ndiag` = the full band count: Q3
17 * has no analog of pw2bgw.x's `diag_nmin`/`diag_nmax` partial-range input,
18 * so every band is written, matching WFN's own energies/occupations records
19 * (which are likewise unconditionally all-band). Matrix elements are in eV,
20 * as the specification requires (pw2bgw.x converts each element from
21 * Rydberg where it computes it), i.e. Q3's own `constants::RY_TO_EV`,
22 * applied once at the very end.
23 *
24 * ── The physics: sidestepping the dense-grid transform ──────────────────
25 *
26 * pw2bgw.x computes `<psi_nk|Vxc|psi_nk>` by transforming psi_nk to real
27 * space on the DENSE (charge-density) FFT grid, multiplying pointwise by
28 * Vxc(r), and transforming back (write_vxc_g). Q3's only wavefunction
29 * G<->R transform (`WavefunctionTransformer`, reached via
30 * `PlanewaveBasis::g_to_r`/`r_to_g`) targets the coarser SMOOTH grid
31 * (`fft_wave()`), not the dense one (`fft_rho()`) -- reproducing pw2bgw.x's
32 * route literally would need a new G->R transform Q3 does not expose.
33 *
34 * That transform is avoidable entirely: pointwise multiplication in real
35 * space is convolution in G-space, so the SAME matrix element is exactly a
36 * G-space double sum over this band's own plane-wave coefficients c_nk and
37 * the already-available G-space XC potential:
38 *
39 * <psi_nk|Vxc|psi_nk> = sum_{G,G'} conj(c_nk(G)) * Vxc(G - G') * c_nk(G')
40 *
41 * where G, G' range over psi_nk's plane-wave G-set at this k-point and
42 * Vxc(G) is `NormConservingPlanewaveKohnShamSCFState::exchange_correlation_potential_g`
43 * (already on the charge-density G set -- see its assembly in
44 * effective_potential/planewave/init.hpp's `assemble_effective_potential()`:
45 * `exchange_correlation_potential_average = spin_averaged_potential -
46 * hartree - local_ionic`, then `planewave_basis_.rho_r_to_g(...)`). Two
47 * independent derivations confirm this needs no extra normalization
48 * (no 1/N, no cell volume) beyond the final Ry->eV conversion:
49 *
50 * 1. Standard QE Bloch-function algebra: psi_nk(r) = sum_G c(G)
51 * e^{i(k+G).r} with the usual plane-wave normalization sum_G|c(G)|^2 =
52 * 1 (so integral|psi|^2 dr = 1 over the cell), and Vxc(r) = sum_G
53 * Vxc(G) e^{iG.r}. Expanding <psi|Vxc|psi> = integral psi*(r) Vxc(r)
54 * psi(r) dr and using integral e^{iq.r} dr = Omega * delta_{q,0}
55 * collapses the two cell-volume factors (one from the integral, one
56 * from 1/Omega implicit in orthonormal c(G)) exactly, leaving the sum
57 * above with no leftover factor.
58 *
59 * 2. Q3's OWN Hamiltonian application (SerialPlanewaveHamiltonianApplicator,
60 * `apply_hamiltonian_batch()`'s "2. Local potential" step) computes
61 * H|psi> for the real local potential via UNNORMALIZED raw FFTs
62 * (`execute_inverse_raw_on_buffers()`/`execute_forward_on_buffers()`,
63 * see FFTGrid's `inverse_raw`/`forward` docs) with the potential
64 * PRE-SCALED by `1/total_grid_points` before the pointwise multiply
65 * (`update_potential()`'s `cached_scaled_potential_`). Working through
66 * that pipeline algebraically (raw-FFT round trip contributes exactly
67 * one factor of `total_grid_points`, canceling the pre-scaling)
68 * reproduces `H|psi>(G) = sum_G' V(G-G') psi(G')` with V(G) taken
69 * AS-IS from the stored G-space potential -- the identical formula and
70 * normalization used here, this time derived from what Q3 itself
71 * already does to apply a real potential to a wavefunction, not from a
72 * textbook.
73 *
74 * `MillerDifferenceIndex` below turns the per-(G,G') lookup "is Miller
75 * difference (G-G') a stored charge-density G-vector, and if so at what
76 * index" into an O(1) dense-array lookup, built ONCE per write_to_file()
77 * call rather than once per band. A difference that lands outside the
78 * stored charge-density G-set contributes zero (skipped, not
79 * approximated) -- for the standard norm-conserving dual=4 cutoff ratio
80 * (`PlanewaveBasis::generate()`'s own `4.0 *
81 * energy_cutoff_wavefunction_` default) this never actually happens: by
82 * the triangle inequality |G-G'| <= |G| + |G'| <= 2*sqrt(ecutwfc) =
83 * sqrt(4*ecutwfc) = sqrt(ecutrho) for any two wavefunction G-vectors, so
84 * every difference falls within the charge-density cutoff sphere. A
85 * smaller dual ratio (user-configured `density` cutoff below 4x
86 * `wavefunction`) can genuinely truncate some differences; write_to_file()
87 * counts and reports the fraction (via `log::info`) precisely so that case
88 * is visible rather than silently absorbed.
89 *
90 * Since Vxc(r) is real, Vxc(-G) = conj(Vxc(G)), which forces the DIAGONAL
91 * matrix element above to be real in exact arithmetic; write_to_file()
92 * computes it as complex, checks the imaginary part stays within
93 * floating-point rounding of zero (see `imaginary_part_tolerance_ry`), and
94 * throws rather than writing a suspect file if it does not -- a
95 * non-negligible imaginary part means an index or conjugation bug, not
96 * physics, and should never be silently written.
97 *
98 * ── Scope restrictions (both throw a clear, specific error) ─────────────
99 *
100 * - Spin-polarized (nspin>1): the only Vxc Q3 exposes on the SCF state,
101 * `exchange_correlation_potential_g`, is the SPIN-AVERAGED potential (see
102 * its assembly above) -- using it for both spin channels would silently
103 * give a wrong answer for any magnetized system. Mirrors
104 * `BerkeleyGWWavefunctionFile`'s own nspin>1 rejection exactly. (For an
105 * unpolarized run the average of one channel is itself, so this does not
106 * apply.)
107 *
108 * - Gamma-only (`PlanewaveBasis::is_gamma_only()`): a gamma-only run stores
109 * only HALF the plane-wave G-sphere per k-point and reconstructs the
110 * other half via c(-G) = conj(c(G)) wherever it actually needs psi(r) --
111 * see `SerialPlanewaveHamiltonianApplicator::apply_hamiltonian_batch()`'s
112 * explicit `+G`/`-G` packing before its own real-space multiply. The
113 * direct G-space double sum above, applied to only the STORED half,
114 * would silently omit exactly those reconstructed contributions. Adding
115 * the reconstruction correctly is possible in principle but out of scope
116 * here; throwing is the same honest choice already made for nspin>1
117 * rather than guessing. This is a NEW restriction relative to WFN/RHO
118 * (which pass gamma-only data through as-is, without needing this
119 * reconstruction) discovered while implementing this file, not part of
120 * the original two blockers.
121 */
122
123#include <algorithm>
124#include <cmath>
125#include <complex>
126#include <cstdio>
127#include <fstream>
128#include <stdexcept>
129#include <string>
130#include <vector>
131
132#include "io/logger.hpp"
136
137namespace q3 {
138
140 public:
141 explicit BerkeleyGWExchangeCorrelationFile(const MeanFieldRecord& record) : record_(record) {}
142 /**
143 * These writers keep a reference to the record, so binding one to a
144 * temporary would dangle the moment the full expression ends. Deleting the
145 * rvalue overload turns that into a compile error instead of a silent
146 * read of freed memory -- callers must keep the record alive themselves,
147 * which every real call site already does.
148 */
150
151 void write_to_file(const std::string& file_path) const {
152 if (record_.number_of_spin_components > 1) {
153 throw std::runtime_error(
154 "BerkeleyGWExchangeCorrelationFile: spin-polarized (nspin>1) vxc.dat export is "
155 "not supported -- Q3's exchange_correlation_potential_g is the SPIN-AVERAGED "
156 "potential (see assemble_effective_potential() in "
157 "effective_potential/planewave/init.hpp), so using it for both spin channels "
158 "would silently give a wrong <psi|Vxc|psi> for any magnetized system. Use "
159 "number_of_spin_components: 1, or omit mean_field_export, for a spin-polarized "
160 "run. Cannot write '" +
161 file_path + "'.");
162 }
163 if (record_.is_gamma_only) {
164 throw std::runtime_error(
165 "BerkeleyGWExchangeCorrelationFile: gamma-only vxc.dat export is not supported "
166 "-- a gamma-only run stores only half the plane-wave G-sphere per k-point and "
167 "reconstructs the other half via c(-G) = conj(c(G)) wherever real-space values "
168 "are actually needed (see SerialPlanewaveHamiltonianApplicator's own +G/-G "
169 "packing); the direct G-space convolution used here, applied to only the stored "
170 "half, would silently omit those reconstructed contributions. Cannot write '" +
171 file_path + "'.");
172 }
173
174 const auto& g_vectors = record_.charge_density_miller_indices;
175 const auto& vxc_g = record_.exchange_correlation_potential_g;
176 if (vxc_g.size() != g_vectors.size()) {
177 throw std::runtime_error(
178 "BerkeleyGWExchangeCorrelationFile: exchange_correlation_potential_g has " +
179 std::to_string(vxc_g.size()) + " entries, expected " +
180 std::to_string(g_vectors.size()) +
181 " (one per charge-density G-vector) -- the SCF state does not carry a usable "
182 "G-space Vxc (e.g. an incomplete or synthetic state). Cannot write '" +
183 file_path + "'.");
184 }
185
186 MillerDifferenceIndex miller_lookup(g_vectors);
187 Statistics statistics;
188 std::vector<KPointBlock> blocks =
189 compute_all_kpoint_blocks(g_vectors, vxc_g, miller_lookup, statistics);
190
191 if (statistics.maximum_absolute_imaginary_part_ry > imaginary_part_tolerance_ry) {
192 throw std::runtime_error(
193 "BerkeleyGWExchangeCorrelationFile: computed <psi_nk|Vxc|psi_nk> has a "
194 "non-negligible imaginary part (max |Im| = " +
195 std::to_string(statistics.maximum_absolute_imaginary_part_ry) + " Ry, tolerance " +
196 std::to_string(imaginary_part_tolerance_ry) +
197 " Ry) -- Vxc(r) is real, so this diagonal matrix element must come out real to "
198 "within floating-point rounding. This size of imaginary residual means an index "
199 "or conjugation bug, not noise. Refusing to write a suspect '" +
200 file_path + "'.");
201 }
202
203 std::ofstream output_file(file_path);
204 if (!output_file.is_open()) {
205 log::warn("BerkeleyGWExchangeCorrelationFile: cannot open {} for writing", file_path);
206 return;
207 }
208 for (const auto& block : blocks) {
209 write_kpoint_block(output_file, block);
210 }
211
212 double out_of_set_fraction =
213 statistics.total_g_difference_pairs > 0
214 ? static_cast<double>(statistics.out_of_set_g_difference_pairs) /
215 static_cast<double>(statistics.total_g_difference_pairs)
216 : 0.0;
217 log::info(" Wrote BerkeleyGW exchange-correlation file: {} ({} k-point(s), {} "
218 "band(s); out-of-set G-difference fraction {:.3e}, max |Im(Vxc)| {:.3e} eV)",
219 file_path,
220 blocks.size(),
221 record_.number_of_bands,
222 out_of_set_fraction,
223 statistics.maximum_absolute_imaginary_part_ry * constants::RY_TO_EV);
224 }
225
226 private:
227 const MeanFieldRecord& record_;
228
229 /**
230 * Above this, a computed <psi_nk|Vxc|psi_nk>'s imaginary part is treated
231 * as a bug rather than floating-point noise. A direct double sum of up
232 * to ~1500^2 ~ 2.3e6 complex terms accumulates rounding error of order
233 * sqrt(N)*epsilon*|typical term| -- roughly 1.5e3 * 1e-16 * O(1) ~
234 * 1.5e-13 Ry in practice -- many orders of magnitude below this
235 * threshold, so anything that trips it is a real defect.
236 */
237 static constexpr Real imaginary_part_tolerance_ry = 1.0e-6;
238
239 struct Statistics {
240 long total_g_difference_pairs = 0;
241 long out_of_set_g_difference_pairs = 0;
242 Real maximum_absolute_imaginary_part_ry = 0.0;
243 };
244
245 struct KPointBlock {
246 Vector3D kpoint_crystal_coordinates;
247 std::vector<Complex> diagonal_elements_ev; // band-major, band index 0..nbands-1
248 };
249
250 /**
251 * Dense lookup from a Miller-index DIFFERENCE (G - G', for G, G' both
252 * charge-density G-vectors) to that difference's position in the
253 * charge-density G-vector list `PlanewaveBasis::g_vectors()`, or -1 if
254 * the difference is not itself a stored charge-density G-vector. Built
255 * ONCE per write_to_file() call (O(number_of_g_vectors)), then queried
256 * O(number_of_plane_waves^2) times per band.
257 *
258 * Sized from the ACTUAL observed Miller range of g_vectors() rather
259 * than, say, PlanewaveBasis::fft_rho()'s grid dimensions, so it needs
260 * no assumption about how the charge-density G-sphere relates to its
261 * enclosing FFT box. A difference of two Miller indices, each within
262 * [min, max], ranges within [min-max, max-min] -- twice the extent of
263 * the original range -- so the lookup box is sized to that wider
264 * range. The ORIGINAL range is always a subset of it (needed so
265 * populating the array from the original G-vectors never goes out of
266 * bounds): G=(0,0,0) is always a stored charge-density G-vector (any
267 * positive cutoff includes it, both in the general and gamma-only
268 * cases), so 0 is always within [min, max] for every component, which
269 * forces min <= 0 <= max and hence [min, max] subseteq [min-max,
270 * max-min].
271 */
272 class MillerDifferenceIndex {
273 public:
274 explicit MillerDifferenceIndex(const std::vector<IntegerVector3D>& g_vectors) {
275 if (g_vectors.empty()) {
276 lookup_.assign(1, -1);
277 return;
278 }
279
280 IntegerVector3D minimum_miller = g_vectors[0];
281 IntegerVector3D maximum_miller = g_vectors[0];
282 for (const auto& g_vector : g_vectors) {
283 minimum_miller.x = std::min(minimum_miller.x, g_vector.x);
284 minimum_miller.y = std::min(minimum_miller.y, g_vector.y);
285 minimum_miller.z = std::min(minimum_miller.z, g_vector.z);
286 maximum_miller.x = std::max(maximum_miller.x, g_vector.x);
287 maximum_miller.y = std::max(maximum_miller.y, g_vector.y);
288 maximum_miller.z = std::max(maximum_miller.z, g_vector.z);
289 }
290
291 minimum_difference_ = IntegerVector3D(minimum_miller.x - maximum_miller.x,
292 minimum_miller.y - maximum_miller.y,
293 minimum_miller.z - maximum_miller.z);
294 maximum_difference_ = IntegerVector3D(maximum_miller.x - minimum_miller.x,
295 maximum_miller.y - minimum_miller.y,
296 maximum_miller.z - minimum_miller.z);
297
298 range_y_ = maximum_difference_.y - minimum_difference_.y + 1;
299 range_z_ = maximum_difference_.z - minimum_difference_.z + 1;
300 int range_x = maximum_difference_.x - minimum_difference_.x + 1;
301 lookup_.assign(static_cast<size_t>(range_x) * range_y_ * range_z_, -1);
302
303 for (int g_index = 0; g_index < static_cast<int>(g_vectors.size()); g_index++) {
304 const auto& miller = g_vectors[g_index];
305 lookup_[flatten(miller.x, miller.y, miller.z)] = g_index;
306 }
307 }
308
309 /// Position of Miller difference (dx,dy,dz) in the charge-density G-vector list, or -1.
310 [[nodiscard]] int find(int dx, int dy, int dz) const {
311 if (dx < minimum_difference_.x || dx > maximum_difference_.x ||
312 dy < minimum_difference_.y || dy > maximum_difference_.y ||
313 dz < minimum_difference_.z || dz > maximum_difference_.z) {
314 return -1;
315 }
316 return lookup_[flatten(dx, dy, dz)];
317 }
318
319 private:
320 [[nodiscard]] size_t flatten(int x, int y, int z) const {
321 size_t x_index = static_cast<size_t>(x - minimum_difference_.x);
322 size_t y_index = static_cast<size_t>(y - minimum_difference_.y);
323 size_t z_index = static_cast<size_t>(z - minimum_difference_.z);
324 return (x_index * static_cast<size_t>(range_y_) + y_index) *
325 static_cast<size_t>(range_z_) +
326 z_index;
327 }
328
329 IntegerVector3D minimum_difference_{0, 0, 0};
330 IntegerVector3D maximum_difference_{0, 0, 0};
331 int range_y_ = 1;
332 int range_z_ = 1;
333 std::vector<int> lookup_;
334 };
335
336 /**
337 * <psi_nk|Vxc|psi_nk> = sum_{G,G'} conj(c_nk(G)) Vxc(G-G') c_nk(G'), in
338 * Rydberg (see class comment for the derivation and normalization).
339 * O(number_of_plane_waves^2) per band -- ~2.3e6 complex multiply-adds
340 * for a 1500-plane-wave k-point, negligible next to the SCF cost that
341 * already produced psi_nk and Vxc(G).
342 */
343 Complex compute_diagonal_matrix_element_ry(const std::vector<IntegerVector3D>& g_vectors,
344 const std::vector<int>& g_indices_for_kpoint,
345 int number_of_plane_waves,
346 const Complex* coefficients,
347 const std::vector<Complex>& vxc_g,
348 const MillerDifferenceIndex& miller_lookup,
349 Statistics& statistics) const {
350 Complex matrix_element(0.0, 0.0);
351 for (int bra_index = 0; bra_index < number_of_plane_waves; bra_index++) {
352 const IntegerVector3D& bra_miller = g_vectors[g_indices_for_kpoint[bra_index]];
353 Complex bra_coefficient_conjugate = std::conj(coefficients[bra_index]);
354 for (int ket_index = 0; ket_index < number_of_plane_waves; ket_index++) {
355 const IntegerVector3D& ket_miller = g_vectors[g_indices_for_kpoint[ket_index]];
356 statistics.total_g_difference_pairs++;
357 int vxc_index = miller_lookup.find(bra_miller.x - ket_miller.x,
358 bra_miller.y - ket_miller.y,
359 bra_miller.z - ket_miller.z);
360 if (vxc_index < 0) {
361 statistics.out_of_set_g_difference_pairs++;
362 continue;
363 }
364 matrix_element +=
365 bra_coefficient_conjugate * vxc_g[vxc_index] * coefficients[ket_index];
366 }
367 }
368 return matrix_element;
369 }
370
371 /// Compute every k-point's per-band diagonal matrix elements (eV), accumulating statistics.
372 std::vector<KPointBlock>
373 compute_all_kpoint_blocks(const std::vector<IntegerVector3D>& g_vectors,
374 const std::vector<Complex>& vxc_g,
375 const MillerDifferenceIndex& miller_lookup,
376 Statistics& statistics) const {
377 const auto& kpoint_g_vector_indices = record_.kpoint_g_vector_indices;
378 const auto& plane_waves_per_kpoint = record_.plane_waves_per_kpoint;
379 int number_of_kpoints = record_.number_of_kpoints();
380 int number_of_bands = record_.number_of_bands;
381
382 std::vector<KPointBlock> blocks(number_of_kpoints);
383 for (int kpoint_index = 0; kpoint_index < number_of_kpoints; kpoint_index++) {
384 blocks[kpoint_index].kpoint_crystal_coordinates =
385 record_.kpoint_coordinates[kpoint_index];
386 blocks[kpoint_index].diagonal_elements_ev.resize(number_of_bands);
387
388 int number_of_plane_waves = plane_waves_per_kpoint[kpoint_index];
389 const auto& g_indices_for_kpoint = kpoint_g_vector_indices[kpoint_index];
390
391 /**
392 * write_to_file() has already rejected nspin>1, so kpoint_state_index ==
393 * kpoint_index unconditionally (spin_index is always 0) -- same as WFN's write_body().
394 */
395 for (int band_index = 0; band_index < number_of_bands; band_index++) {
396 const Complex* coefficients =
397 record_.band_source->coefficients(kpoint_index, band_index);
398 Complex element_ry = compute_diagonal_matrix_element_ry(g_vectors,
399 g_indices_for_kpoint,
400 number_of_plane_waves,
401 coefficients,
402 vxc_g,
403 miller_lookup,
404 statistics);
405 statistics.maximum_absolute_imaginary_part_ry = std::max(
406 statistics.maximum_absolute_imaginary_part_ry, std::abs(element_ry.imag()));
407 blocks[kpoint_index].diagonal_elements_ev[band_index] =
408 element_ry * constants::RY_TO_EV;
409 }
410 }
411 return blocks;
412 }
413
414 /**
415 * Fixed-width Fortran-F-edit-descriptor-style formatting: `width` total
416 * characters, `decimal_places` digits after the decimal point,
417 * right-justified with leading spaces -- matches `%*.*f`'s own
418 * semantics exactly, so this is a thin, clearly-named wrapper. The
419 * independent reader (tests/helpers_berkeleygw.py) parses by slicing
420 * these exact fixed widths with NO separating whitespace assumed
421 * between fields, so columns must match byte-for-byte.
422 */
423 static std::string format_fixed_width_real(Real value, int width, int decimal_places) {
424 char buffer[64];
425 std::snprintf(buffer, sizeof(buffer), "%*.*f", width, decimal_places, value);
426 return std::string(buffer);
427 }
428
429 /// Fixed-width Fortran-I-edit-descriptor-style integer formatting (right-justified).
430 static std::string format_fixed_width_int(int value, int width) {
431 char buffer[64];
432 std::snprintf(buffer, sizeof(buffer), "%*d", width, value);
433 return std::string(buffer);
434 }
435
436 /**
437 * One k-point block: header line `FORMAT (3F13.9, 2I8)` = `kx ky kz
438 * ndiag noffdiag` (noffdiag always 0, diagonal-only scope), then one
439 * `FORMAT (2I8, 2F15.9)` = `spin band Re(Vxc) Im(Vxc)` line per band
440 * (spin always 1: write_to_file() has already rejected nspin>1).
441 */
442 void write_kpoint_block(std::ofstream& output_file, const KPointBlock& block) const {
443 int number_of_bands = static_cast<int>(block.diagonal_elements_ev.size());
444
445 output_file << format_fixed_width_real(block.kpoint_crystal_coordinates.x, 13, 9)
446 << format_fixed_width_real(block.kpoint_crystal_coordinates.y, 13, 9)
447 << format_fixed_width_real(block.kpoint_crystal_coordinates.z, 13, 9)
448 << format_fixed_width_int(number_of_bands, 8) << format_fixed_width_int(0, 8)
449 << "\n";
450
451 for (int band_index = 0; band_index < number_of_bands; band_index++) {
452 const Complex& element = block.diagonal_elements_ev[band_index];
453 output_file << format_fixed_width_int(1, 8) << format_fixed_width_int(band_index + 1, 8)
454 << format_fixed_width_real(element.real(), 15, 9)
455 << format_fixed_width_real(element.imag(), 15, 9) << "\n";
456 }
457 }
458};
459
460} // namespace q3
BerkeleyGWExchangeCorrelationFile(const MeanFieldRecord &record)
BerkeleyGWExchangeCorrelationFile(const MeanFieldRecord &&)=delete
void write_to_file(const std::string &file_path) const
constexpr Real RY_TO_EV
void warn(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:187
void info(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:183
double Real
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< int > > kpoint_g_vector_indices
Per physical k-point, indices into charge_density_miller_indices.
Definition init.hpp:123
int number_of_spin_components
Definition init.hpp:127
int number_of_kpoints() const
Definition init.hpp:184
std::vector< Complex > exchange_correlation_potential_g
Definition init.hpp:154
std::vector< int > plane_waves_per_kpoint
Definition init.hpp:121
std::shared_ptr< const MeanFieldBandSource > band_source
Definition init.hpp:132
std::vector< IntegerVector3D > charge_density_miller_indices
Definition init.hpp:104