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 '" +
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 '" +
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 '" +
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);
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 '" +
203 std::ofstream output_file(file_path);
204 if (!output_file.is_open()) {
205 log::warn(
"BerkeleyGWExchangeCorrelationFile: cannot open {} for writing", file_path);
208 for (
const auto& block : blocks) {
209 write_kpoint_block(output_file, block);
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)
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)",
237 static constexpr Real imaginary_part_tolerance_ry = 1.0e-6;
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;
246 Vector3D kpoint_crystal_coordinates;
247 std::vector<Complex> diagonal_elements_ev;
272 class MillerDifferenceIndex {
274 explicit MillerDifferenceIndex(
const std::vector<IntegerVector3D>& g_vectors) {
275 if (g_vectors.empty()) {
276 lookup_.assign(1, -1);
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);
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);
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);
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;
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) {
316 return lookup_[flatten(dx, dy, dz)];
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_) +
329 IntegerVector3D minimum_difference_{0, 0, 0};
330 IntegerVector3D maximum_difference_{0, 0, 0};
333 std::vector<int> lookup_;
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,
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);
361 statistics.out_of_set_g_difference_pairs++;
365 bra_coefficient_conjugate * vxc_g[vxc_index] * coefficients[ket_index];
368 return matrix_element;
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 {
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 =
386 blocks[kpoint_index].diagonal_elements_ev.resize(number_of_bands);
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];
395 for (
int band_index = 0; band_index < number_of_bands; band_index++) {
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,
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] =
423 static std::string format_fixed_width_real(
Real value,
int width,
int decimal_places) {
425 std::snprintf(buffer,
sizeof(buffer),
"%*.*f", width, decimal_places, value);
426 return std::string(buffer);
430 static std::string format_fixed_width_int(
int value,
int width) {
432 std::snprintf(buffer,
sizeof(buffer),
"%*d", width, value);
433 return std::string(buffer);
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());
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)
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";