Loading...
Searching...
No Matches
serial.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * SerialPlanewaveHamiltonianApplicator — CPU serial implementation of H|ψ⟩.
5 *
6 * Extracted from PlanewaveDavidsonDiagonalizer. Contains all Hamiltonian
7 * application logic: smooth potential computation, FFT scatter/gather,
8 * kinetic energy, and KB projector application.
9 *
10 * Constructor takes run-constant planewave context by reference.
11 * The caller must ensure all references outlive this object.
12 */
13
14#include <algorithm>
15#include <chrono>
16#include <cmath>
17#include <complex>
18#include <cstring>
19#include <vector>
20
21#include "io/logger.hpp"
36
37namespace q3 {
38
40 public:
41 /**
42 * Construct with run-constant planewave context.
43 * All references must outlive this object.
44 */
46 const Crystal& crystal_structure,
47 const PlanewaveBasis& planewave_basis,
48 const KPointsGrid& kpoint_grid,
49 const NormConservingPlanewaveKohnShamQuantumSystem& pseudopotential_quantum_system,
50 const execution::ExecutionPolicy& execution_policy)
51 : crystal_structure_{crystal_structure}
52 , planewave_basis_{planewave_basis}
53 , kpoint_grid_{kpoint_grid}
54 , pseudopotential_quantum_system_{pseudopotential_quantum_system}
55 , execution_policy_{execution_policy} {}
56
57 /**
58 * Update the effective potential for a new SCF iteration.
59 *
60 * Restricts the potential from the dense grid to the smooth grid via G-space
61 * truncation, then pre-scales by 1/N_smooth for the local potential application.
62 */
63 void update_potential(const std::vector<Real>& real_space_potential) override {
64 cached_smooth_potential_ =
65 compute_smooth_grid_potential(real_space_potential, planewave_basis_);
66 int local_smooth_grid_points = planewave_basis_.fft_wave().local_grid_points();
67 cached_scaled_potential_.resize(local_smooth_grid_points);
68 Real inverse_total_points = 1.0 / planewave_basis_.fft_wave().total_grid_points();
69 for (int grid_index = 0; grid_index < local_smooth_grid_points; grid_index++) {
70 cached_scaled_potential_[grid_index] =
71 cached_smooth_potential_[grid_index] * inverse_total_points;
72 }
73 /**
74 * For task-group band distribution: Allgather the slab-local potential
75 * into a full-grid potential so each rank can do serial FFTs.
76 */
77 if (execution_policy_.group_size() > 1) {
78 int total_smooth_points = planewave_basis_.fft_wave().total_grid_points();
79 cached_full_grid_potential_.resize(total_smooth_points);
80 execution_policy_.allgather_within_group(cached_scaled_potential_.data(),
81 local_smooth_grid_points,
82 cached_full_grid_potential_.data(),
83 total_smooth_points);
84 }
85
86 smooth_potential_cache_valid_ = true;
87 cached_block_d_matrix_.clear();
88 cached_block_d_offsets_.clear();
89 }
90
91 /**
92 * Configure for a specific k-point.
93 * Pre-computes G-vector → FFT grid index mappings and caches kinetic energy.
94 */
95 void prepare_kpoint(int kpoint_index,
96 int number_of_plane_waves,
97 const std::vector<int>& g_vector_indices,
98 const std::vector<Real>& kinetic_energy_squared) override {
99 current_kpoint_index_ = kpoint_index;
100 current_number_of_plane_waves_ = number_of_plane_waves;
101 current_g_vector_indices_ = &g_vector_indices;
102 current_kinetic_energy_squared_ = &kinetic_energy_squared;
103 is_gamma_only_current_ = planewave_basis_.is_gamma_only();
104 gamma_origin_is_local_current_ = planewave_basis_.gamma_origin_is_local();
105
106 precompute_g_to_fft_mapping(number_of_plane_waves, g_vector_indices, planewave_basis_);
107 }
108
109 /**
110 * Apply H|ψ⟩ to a batch of vectors using the cached potential and k-point data.
111 */
112 void apply_batch(const Complex* input_wavefunctions,
113 Complex* output_hamiltonian_wavefunctions,
114 int number_of_vectors) override {
115 /**
116 * With bands distributed, each rank applies H to its own contiguous
117 * slice of the batch and one allgather completes the output. Every rank
118 * holds whole coefficient vectors here, so each transform is local and
119 * serial -- unlike the reciprocal-space split, where the transform is
120 * collective and no rank gets to skip one.
121 *
122 * Falls back to applying the whole batch when there are fewer vectors
123 * than ranks: below that the split leaves ranks idle while still paying
124 * for the allgather.
125 */
126 int band_group_size = execution_policy_.band_distribution_size();
127 if (band_group_size > 1 && number_of_vectors >= band_group_size) {
128 auto partition =
129 execution::partition_bands(number_of_vectors,
130 band_group_size,
131 execution_policy_.band_distribution_rank_index(),
132 current_number_of_plane_waves_,
133 is_gamma_only_current_ ? 2 : 1);
134 if (partition.local_number_of_bands > 0) {
135 std::size_t offset = static_cast<std::size_t>(partition.local_first_band) *
136 current_number_of_plane_waves_;
137 apply_hamiltonian_batch(current_number_of_plane_waves_,
138 partition.local_number_of_bands,
139 *current_g_vector_indices_,
140 *current_kinetic_energy_squared_,
141 cached_scaled_potential_,
142 input_wavefunctions + offset,
143 output_hamiltonian_wavefunctions + offset);
144 }
145 execution_policy_.allgather_across_bands(output_hamiltonian_wavefunctions,
146 partition.counts.data(),
147 partition.displacements.data());
148 return;
149 }
150 apply_hamiltonian_batch(current_number_of_plane_waves_,
151 number_of_vectors,
152 *current_g_vector_indices_,
153 *current_kinetic_energy_squared_,
154 cached_scaled_potential_,
155 input_wavefunctions,
156 output_hamiltonian_wavefunctions);
157 }
158
160 smooth_potential_cache_valid_ = false;
161 cached_block_d_matrix_.clear();
162 cached_block_d_offsets_.clear();
163 }
164
165 // ─── Accessors for Davidson to use during transition ──────────────
166
167 /**
168 * Whether the smooth potential cache is currently valid.
169 */
170 [[nodiscard]] bool is_smooth_potential_cache_valid() const override {
171 return smooth_potential_cache_valid_;
172 }
173
174 /**
175 * Access the cached scaled potential (pre-divided by N_smooth).
176 */
177 [[nodiscard]] const std::vector<Real>& cached_scaled_potential() const {
178 return cached_scaled_potential_;
179 }
180
181 /**
182 * Access the G-to-FFT mapping for the current k-point.
183 */
184 [[nodiscard]] const std::vector<int>& g_to_fft_mapping() const {
185 return g_to_fft_mapping_;
186 }
187
188 /**
189 * Access the minus-G-to-FFT mapping for gamma-only calculations.
190 */
191 [[nodiscard]] const std::vector<int>& minus_g_to_fft_mapping() const {
192 return minus_g_to_fft_mapping_;
193 }
194
195 /**
196 * Whether the current k-point uses gamma-only half-sphere G-vectors.
197 */
198 [[nodiscard]] bool is_gamma_only_current() const override {
199 return is_gamma_only_current_;
200 }
201
202 [[nodiscard]] bool gamma_origin_is_local_current() const override {
203 return gamma_origin_is_local_current_;
204 }
205
206 /**
207 * Set pre-computed KB projectors for a specific k-point.
208 * If set, apply_batch() will use these instead of recomputing them.
209 */
210 void set_cached_projectors(int kpoint_index, const q3::Matrix<Complex>* projectors) override {
211 if (kpoint_index >= static_cast<int>(cached_projector_map_.size())) {
212 cached_projector_map_.resize(kpoint_index + 1, nullptr);
213 }
214 cached_projector_map_[kpoint_index] = projectors;
215 }
216
217 /**
218 * Get pre-computed KB projectors for the current k-point, or nullptr if none.
219 */
220 [[nodiscard]] const q3::Matrix<Complex>*
221 get_cached_projectors(int kpoint_index) const override {
222 if (kpoint_index < static_cast<int>(cached_projector_map_.size())) {
223 return cached_projector_map_[kpoint_index];
224 }
225 return nullptr;
226 }
227
228 /**
229 * Set pre-computed DFT+U (Hubbard) atomic-orbital projectors for a specific k-point.
230 * Geometry-only, set once per k-point at SCF setup (mirrors set_cached_projectors).
231 */
232 void set_cached_hubbard_projectors(int kpoint_index,
233 const q3::Matrix<Complex>* projectors) override {
234 if (kpoint_index >= static_cast<int>(cached_hubbard_projector_map_.size())) {
235 cached_hubbard_projector_map_.resize(kpoint_index + 1, nullptr);
236 }
237 cached_hubbard_projector_map_[kpoint_index] = projectors;
238 }
239
240 /**
241 * Set the dense block-diagonal Hubbard D-matrix. Refreshed once per SCF iteration by
242 * the caller -- unlike the KB D-matrix, this is never lazily rebuilt here (it depends
243 * on the occupation matrix, which this class has no way to compute).
244 */
245 void set_hubbard_d_matrix(const std::vector<Complex>* dense_d_matrix_column_major) override {
246 cached_hubbard_d_matrix_ptr_ = dense_d_matrix_column_major;
247 }
248
249 private:
250 /**
251 * How many bands to transform at once.
252 *
253 * Capped by the FFT grid's own limit: a grid that hands out one set of
254 * scratch arrays can only have one transform in flight, so its band loop
255 * stays serial no matter how many threads the machine has. Distributed
256 * and GPU-backed grids are in that position today, which is why threading
257 * here is a property of the grid rather than of the build flag.
258 *
259 * Returns 1 -- do not fork a team -- for a single thread, a single band,
260 * or a call already inside a parallel region.
261 */
262 [[nodiscard]] static int band_thread_count(const FFTGrid& fft_grid, int number_of_iterations) {
263 int transform_slots = fft_grid.concurrent_transform_slots();
264 if (transform_slots < 2) {
265 return 1;
266 }
267 return std::min(transform_slots,
268 execution::threading::thread_count_for(number_of_iterations, 1));
269 }
270
271 // ─── Core Hamiltonian application ────────────────────────────────
272
273 /**
274 * Apply the full Kohn-Sham Hamiltonian H|ψ⟩ to a batch of vectors in parallel.
275 *
276 * 1. Kinetic energy: diagonal in G-space.
277 * 2. Local potential: FFT to real space, multiply by V(r), FFT back in parallel.
278 * 3. Nonlocal KB projectors: Level-3 BLAS zgemm matrix multiplication.
279 */
280 void apply_hamiltonian_batch(int number_of_plane_waves,
281 int number_of_vectors,
282 const std::vector<int>& g_vector_indices,
283 const std::vector<Real>& kinetic_energy_squared,
284 const std::vector<Real>& real_space_potential,
285 const Complex* psi_wavefunctions,
286 Complex* hamiltonian_psi_wavefunctions) const {
287
288 int leading_dimension = std::max(number_of_plane_waves, 1);
290 int local_smooth_grid_points = planewave_basis_.fft_wave().local_grid_points();
291 /**
292 * The scatter fills reciprocal space, and under the stick layout that
293 * count is not the real-space count; zeroing by the wrong one left a
294 * stale tail from the previous forward transform in the next scatter.
295 */
296 int local_smooth_g_points = planewave_basis_.fft_wave().local_g_space_points();
297 auto hpsi_timer_start = std::chrono::high_resolution_clock::now();
298
299 if (is_gamma_only_current_) {
300 /**
301 * Gamma-only: two-bands-at-a-time — pack two real-valued wavefunctions
302 * into Re/Im of one complex FFT, halving the FFT count.
303 * Uses zero-copy buffer access: scatter/gather directly to/from FFT buffers.
304 */
305 const FFTGrid& fft_wave_gamma = planewave_basis_.fft_wave();
306 /**
307 * G = 0 is its own mirror. On the rank that holds it, it is index 0
308 * and is written once; on every other rank index 0 is an ordinary
309 * G with a mirror of its own, so the mirrored loops start there.
310 */
311 int first_mirrored_index = gamma_origin_is_local_current_ ? 1 : 0;
312 [[maybe_unused]] int gamma_band_threads =
313 band_thread_count(fft_wave_gamma, (number_of_vectors + 1) / 2);
314 /**
315 * One iteration is one packed pair of bands, and pairs are as
316 * independent of each other as single bands are on the other
317 * branch: disjoint reads, disjoint writes, nothing summed across
318 * iterations. Bit-identical at any thread count for the same
319 * reason.
320 */
321#pragma omp parallel for schedule(static) \
322 num_threads(gamma_band_threads) if (gamma_band_threads > 1)
323 for (int pair_index = 0; pair_index < number_of_vectors; pair_index += 2) {
324 int transform_slot = execution::threading::thread_index();
325 Complex* fft_input = fft_wave_gamma.slot_input_buffer(transform_slot);
326 int band_1 = pair_index;
327 int band_2 = pair_index + 1;
328 bool has_second_band = (band_2 < number_of_vectors);
329
330 const Complex* psi_1 = &psi_wavefunctions[band_1 * number_of_plane_waves];
331 Complex* hpsi_1 = &hamiltonian_psi_wavefunctions[band_1 * number_of_plane_waves];
332 const Complex* psi_2 =
333 has_second_band ? &psi_wavefunctions[band_2 * number_of_plane_waves] : nullptr;
334 Complex* hpsi_2 =
335 has_second_band ? &hamiltonian_psi_wavefunctions[band_2 * number_of_plane_waves]
336 : nullptr;
337
338 // 1. Kinetic energy
339 for (int g_index = 0; g_index < number_of_plane_waves; ++g_index) {
340 hpsi_1[g_index] = kinetic_energy_squared[g_index] * psi_1[g_index];
341 }
342 if (has_second_band) {
343 for (int g_index = 0; g_index < number_of_plane_waves; ++g_index) {
344 hpsi_2[g_index] = kinetic_energy_squared[g_index] * psi_2[g_index];
345 }
346 }
347
348 // 2. Pack and scatter two bands directly to FFT input buffer
349 std::memset(fft_input, 0, local_smooth_g_points * sizeof(Complex));
350
351 if (has_second_band) {
352 if (gamma_origin_is_local_current_) {
353 // G=0: (Re psi_1, Re psi_2), once
354 int fft_index_0 = g_to_fft_mapping_[0];
355 if (fft_index_0 >= 0) {
356 fft_input[fft_index_0] = Complex(psi_1[0].real(), psi_2[0].real());
357 }
358 }
359 // Every other G: +G and -G
360 for (int g_index = first_mirrored_index; g_index < number_of_plane_waves;
361 ++g_index) {
362 int fft_plus = g_to_fft_mapping_[g_index];
363 int fft_minus = minus_g_to_fft_mapping_[g_index];
364 if (fft_plus >= 0) {
365 Complex p1 = psi_1[g_index];
366 Complex p2 = psi_2[g_index];
367 // +G: ψ₁(G) + i·ψ₂(G)
368 fft_input[fft_plus] =
369 Complex(p1.real() - p2.imag(), p1.imag() + p2.real());
370 // -G: conj(ψ₁(G)) + i·conj(ψ₂(G))
371 fft_input[fft_minus] =
372 Complex(p1.real() + p2.imag(), -p1.imag() + p2.real());
373 }
374 }
375 } else {
376 // Single band: standard gamma scatter
377 for (int g_index = 0; g_index < number_of_plane_waves; ++g_index) {
378 int fft_index = g_to_fft_mapping_[g_index];
379 if (fft_index >= 0) {
380 fft_input[fft_index] = psi_1[g_index];
381 }
382 }
383 for (int g_index = first_mirrored_index; g_index < number_of_plane_waves;
384 ++g_index) {
385 int fft_minus = minus_g_to_fft_mapping_[g_index];
386 fft_input[fft_minus] = std::conj(psi_1[g_index]);
387 }
388 }
389
390 fft_wave_gamma.execute_inverse_raw_on_slot(transform_slot);
391 Complex* fft_output = fft_wave_gamma.slot_output_buffer(transform_slot);
392
393 for (int grid_index = 0; grid_index < local_smooth_grid_points; ++grid_index) {
394 fft_output[grid_index] *= real_space_potential[grid_index];
395 }
396
397 fft_wave_gamma.prepare_forward_from_output_on_slot(transform_slot);
398 fft_wave_gamma.execute_forward_on_slot(transform_slot);
399 fft_output = fft_wave_gamma.slot_output_buffer(transform_slot);
400
401 // Separate the two bands using ±G symmetry
402 if (has_second_band) {
403 if (gamma_origin_is_local_current_) {
404 // G=0
405 int fft_index_0 = g_to_fft_mapping_[0];
406 if (fft_index_0 >= 0) {
407 Complex result_0 = fft_output[fft_index_0];
408 hpsi_1[0] += Complex(result_0.real(), 0.0);
409 hpsi_2[0] += Complex(result_0.imag(), 0.0);
410 }
411 }
412 // Every other G
413 for (int g_index = first_mirrored_index; g_index < number_of_plane_waves;
414 ++g_index) {
415 int fft_plus = g_to_fft_mapping_[g_index];
416 int fft_minus = minus_g_to_fft_mapping_[g_index];
417 if (fft_plus >= 0) {
418 Complex result_plus = fft_output[fft_plus];
419 Complex result_minus_conj = std::conj(fft_output[fft_minus]);
420 hpsi_1[g_index] += (result_plus + result_minus_conj) * 0.5;
421 Complex diff = (result_plus - result_minus_conj) * 0.5;
422 hpsi_2[g_index] += Complex(diff.imag(), -diff.real());
423 }
424 }
425 } else {
426 // Single band: standard gather
427 for (int g_index = 0; g_index < number_of_plane_waves; ++g_index) {
428 int fft_index = g_to_fft_mapping_[g_index];
429 if (fft_index >= 0) {
430 hpsi_1[g_index] += fft_output[fft_index];
431 }
432 }
433 }
434 }
435 } else {
436 /**
437 * Non-gamma path: per-band FFT loop using zero-copy buffer access.
438 *
439 * Scatter/gather directly to/from the FFT grid's internal buffers,
440 * eliminating 4 memcpy per band. For in-place plans (MPI FFT),
441 * input_buffer == output_buffer, so prepare_forward is a no-op.
442 */
443 const FFTGrid& fft_wave = planewave_basis_.fft_wave();
444 [[maybe_unused]] int band_threads = band_thread_count(fft_wave, number_of_vectors);
445 /**
446 * Bands are independent here: each one reads its own slice of
447 * psi, writes its own slice of H|psi>, and touches nothing the
448 * others do. Nothing is reduced, so no summation order changes
449 * and the results are the same to the last bit however the
450 * iterations are handed out.
451 *
452 * The scatter and gather below -- the sphere-to-grid mapping and
453 * back -- ride along inside this region rather than forking teams
454 * of their own; at a few thousand G-vectors a nested team would
455 * cost more than it saves.
456 */
457#pragma omp parallel for schedule(static) num_threads(band_threads) if (band_threads > 1)
458 for (int vector_index = 0; vector_index < number_of_vectors; ++vector_index) {
459 int transform_slot = execution::threading::thread_index();
460 Complex* fft_input = fft_wave.slot_input_buffer(transform_slot);
461 const Complex* psi_vector =
462 &psi_wavefunctions[vector_index * number_of_plane_waves];
463 Complex* hamiltonian_psi_vector =
464 &hamiltonian_psi_wavefunctions[vector_index * number_of_plane_waves];
465
466 // 1. Kinetic energy
467 for (int g_index = 0; g_index < number_of_plane_waves; ++g_index) {
468 hamiltonian_psi_vector[g_index] =
469 kinetic_energy_squared[g_index] * psi_vector[g_index];
470 }
471
472 // 2. Local potential via zero-copy FFT
473 std::memset(fft_input, 0, local_smooth_g_points * sizeof(Complex));
474 for (int g_index = 0; g_index < number_of_plane_waves; ++g_index) {
475 int fft_index = g_to_fft_mapping_[g_index];
476 if (fft_index >= 0) {
477 fft_input[fft_index] = psi_vector[g_index];
478 }
479 }
480 fft_wave.execute_inverse_raw_on_slot(transform_slot);
481 Complex* fft_output = fft_wave.slot_output_buffer(transform_slot);
482 for (int grid_index = 0; grid_index < local_smooth_grid_points; ++grid_index) {
483 fft_output[grid_index] *= real_space_potential[grid_index];
484 }
485 fft_wave.prepare_forward_from_output_on_slot(transform_slot);
486 fft_wave.execute_forward_on_slot(transform_slot);
487 fft_output = fft_wave.slot_output_buffer(transform_slot);
488 for (int g_index = 0; g_index < number_of_plane_waves; ++g_index) {
489 int fft_index = g_to_fft_mapping_[g_index];
490 if (fft_index >= 0) {
491 hamiltonian_psi_vector[g_index] += fft_output[fft_index];
492 }
493 }
494 }
495 }
496 auto hpsi_timer_after_fft = std::chrono::high_resolution_clock::now();
497
498 /**
499 * 3. Nonlocal PP (KB projectors) via batched Level-3 BLAS zgemm
500 *
501 * Get the KB projectors for this k-point.
502 */
503 Matrix<Complex> computed_projectors;
504 const Matrix<Complex>* kleinman_bylander_potential_ptr = nullptr;
505 if (current_kpoint_index_ < static_cast<int>(cached_projector_map_.size()) &&
506 cached_projector_map_[current_kpoint_index_] != nullptr) {
507 kleinman_bylander_potential_ptr = cached_projector_map_[current_kpoint_index_];
508 } else {
509 Vector3D kpoint_coordinates = kpoint_grid_[current_kpoint_index_].coordinates;
511 kpoint_coordinates,
512 crystal_structure_,
513 planewave_basis_,
514 pseudopotential_quantum_system_.pseudopotential_basis().pseudopotential_set(),
515 pseudopotential_quantum_system_.pseudopotential_basis().beta_function_table(),
516 g_vector_indices,
517 number_of_plane_waves,
518 pseudopotential_quantum_system_.pseudopotential_basis()
520 computed_projectors);
521 kleinman_bylander_potential_ptr = &computed_projectors;
522 }
523 const auto& kleinman_bylander_potential = *kleinman_bylander_potential_ptr;
524
525 if (kleinman_bylander_potential.empty()) {
526 return;
527 }
528
529 int total_number_of_projectors = static_cast<int>(kleinman_bylander_potential.rows());
530
531 auto* cast_wavefunctions = reinterpret_cast<const std::complex<double>*>(psi_wavefunctions);
532 auto* cast_hamiltonian_wavefunctions =
533 reinterpret_cast<std::complex<double>*>(hamiltonian_psi_wavefunctions);
534 auto* cast_projector_matrix =
535 reinterpret_cast<const std::complex<double>*>(kleinman_bylander_potential.data());
536
537 Complex alpha_one(1.0, 0.0);
538 Complex beta_zero(0.0, 0.0);
539 Complex beta_one(1.0, 0.0);
540 auto* cast_alpha_one = reinterpret_cast<const std::complex<double>*>(&alpha_one);
541 auto* cast_beta_zero = reinterpret_cast<const std::complex<double>*>(&beta_zero);
542 auto* cast_beta_one = reinterpret_cast<const std::complex<double>*>(&beta_one);
543
544 // Step 1: Compute all overlaps ⟨β_all|ψ⟩
545 std::vector<Complex> all_overlap_coefficients(
546 total_number_of_projectors * number_of_vectors, Complex(0.0, 0.0));
547
548 if (is_gamma_only_current_) {
549 // Gamma-only: use DGEMM on interleaved real/imag parts.
550 int double_number_of_plane_waves = 2 * number_of_plane_waves;
551 int double_leading_dimension = 2 * leading_dimension;
552 double alpha_two = 2.0;
553 double d_beta_zero = 0.0;
554
555 auto* real_projectors =
556 reinterpret_cast<const double*>(kleinman_bylander_potential.data());
557 auto* real_wavefunctions = reinterpret_cast<const double*>(psi_wavefunctions);
558
559 std::vector<double> overlap_real(
560 static_cast<size_t>(total_number_of_projectors) * number_of_vectors, 0.0);
562 "N",
563 &total_number_of_projectors,
564 &number_of_vectors,
565 &double_number_of_plane_waves,
566 &alpha_two,
567 const_cast<double*>(real_projectors),
568 &double_leading_dimension,
569 const_cast<double*>(real_wavefunctions),
570 &double_leading_dimension,
571 &d_beta_zero,
572 overlap_real.data(),
573 &total_number_of_projectors);
574
575 /**
576 * Remove the origin from the double count, on the rank that holds
577 * it, and copy to Complex. The reduction below completes the sum.
578 */
579 for (int vector_index = 0; vector_index < number_of_vectors; ++vector_index) {
580 const Complex* psi_g0 = &psi_wavefunctions[vector_index * number_of_plane_waves];
581 for (int projector_index = 0; projector_index < total_number_of_projectors;
582 ++projector_index) {
583 int index = projector_index + vector_index * total_number_of_projectors;
584 if (gamma_origin_is_local_current_) {
585 Complex beta_g0 = kleinman_bylander_potential(projector_index, 0);
586 overlap_real[index] -= (std::conj(beta_g0) * psi_g0[0]).real();
587 }
588 all_overlap_coefficients[index] = Complex(overlap_real[index], 0.0);
589 }
590 }
591
592 // Reduce KB projector overlaps across band group.
593 execution_policy_.reduce_within_group(all_overlap_coefficients.data(),
594 total_number_of_projectors * number_of_vectors);
595 } else {
596 // Non-gamma: use ZGEMM
597 auto* cast_all_overlap_coefficients =
598 reinterpret_cast<std::complex<double>*>(all_overlap_coefficients.data());
599
601 "N",
602 &total_number_of_projectors,
603 &number_of_vectors,
604 &number_of_plane_waves,
605 cast_alpha_one,
606 cast_projector_matrix,
607 &leading_dimension,
608 cast_wavefunctions,
609 &leading_dimension,
610 cast_beta_zero,
611 cast_all_overlap_coefficients,
612 &total_number_of_projectors);
613
614 // Reduce KB projector overlaps across band group.
615 execution_policy_.reduce_within_group(all_overlap_coefficients.data(),
616 total_number_of_projectors * number_of_vectors);
617 }
618 auto hpsi_timer_after_overlap = std::chrono::high_resolution_clock::now();
619
620 /**
621 * Step 2: Apply the D matrix, which is block-diagonal by atom.
622 *
623 * It used to be packed into a dense P x P matrix and applied with one
624 * ZGEMM. For 432 silicon atoms P is 7776, so that matrix held 60M
625 * complex numbers of which 140k were nonzero -- 967 MB to carry, and
626 * 432 times the arithmetic, to multiply by a great many zeros. The
627 * blocks are stored contiguously instead and applied one atom at a
628 * time.
629 */
630 if (cached_block_d_matrix_.empty() || cached_block_d_offsets_.empty()) {
631 cached_block_d_offsets_.clear();
632 cached_block_d_sizes_.clear();
633 cached_block_d_projector_starts_.clear();
634 std::size_t packed_size = 0;
635 int starting_projector_index = 0;
636 for (int atom_index = 0; atom_index < crystal_structure_.number_of_atoms();
637 atom_index++) {
638 int species_index = crystal_structure_.species_type_index(atom_index);
639 int number_of_projectors_for_atom =
640 pseudopotential_quantum_system_.pseudopotential_basis()
641 .number_of_projector_channels(species_index);
642 cached_block_d_offsets_.push_back(packed_size);
643 cached_block_d_sizes_.push_back(number_of_projectors_for_atom);
644 cached_block_d_projector_starts_.push_back(starting_projector_index);
645 packed_size += static_cast<std::size_t>(number_of_projectors_for_atom) *
646 static_cast<std::size_t>(number_of_projectors_for_atom);
647 starting_projector_index += number_of_projectors_for_atom;
648 }
649 cached_block_d_matrix_.assign(packed_size, Complex(0.0, 0.0));
650
651 for (std::size_t atom = 0; atom < cached_block_d_sizes_.size(); ++atom) {
652 int atom_index = static_cast<int>(atom);
653 int species_index = crystal_structure_.species_type_index(atom_index);
654 int block_size = cached_block_d_sizes_[atom];
655 Complex* block = cached_block_d_matrix_.data() + cached_block_d_offsets_[atom];
656 for (int projector_index_a = 0; projector_index_a < block_size;
657 projector_index_a++) {
658 for (int projector_index_b = 0; projector_index_b < block_size;
659 projector_index_b++) {
660 Real d_matrix_value =
661 pseudopotential_quantum_system_.d_matrix_element(atom_index,
662 species_index,
663 projector_index_a,
664 projector_index_b,
666 if (std::abs(d_matrix_value) >
668 block[static_cast<std::size_t>(projector_index_a) +
669 static_cast<std::size_t>(projector_index_b) * block_size] =
670 Complex(d_matrix_value, 0.0);
671 }
672 }
673 }
674 }
675 }
676
677 std::vector<Complex> all_scaled_overlap_coefficients(
678 static_cast<std::size_t>(total_number_of_projectors) * number_of_vectors,
679 Complex(0.0, 0.0));
680 auto* cast_all_scaled_overlap_coefficients =
681 reinterpret_cast<std::complex<double>*>(all_scaled_overlap_coefficients.data());
682 auto* cast_overlap_input =
683 reinterpret_cast<const std::complex<double>*>(all_overlap_coefficients.data());
684
685 for (std::size_t atom = 0; atom < cached_block_d_sizes_.size(); ++atom) {
686 int block_size = cached_block_d_sizes_[atom];
687 if (block_size == 0) {
688 continue;
689 }
690 int projector_start = cached_block_d_projector_starts_[atom];
691 auto* cast_block = reinterpret_cast<const std::complex<double>*>(
692 cached_block_d_matrix_.data() + cached_block_d_offsets_[atom]);
694 "N",
695 "N",
696 &block_size,
697 &number_of_vectors,
698 &block_size,
699 cast_alpha_one,
700 const_cast<std::complex<double>*>(cast_block),
701 &block_size,
702 const_cast<std::complex<double>*>(cast_overlap_input + projector_start),
703 &total_number_of_projectors,
704 cast_beta_zero,
705 cast_all_scaled_overlap_coefficients + projector_start,
706 &total_number_of_projectors);
707 }
708
709 /**
710 * Step 3: Accumulate into H|ψ⟩ in a single ZGEMM:
711 * H|ψ_i⟩ += β_all × w_all
712 */
713 auto* cast_all_scaled_overlap_coefficients_const =
714 reinterpret_cast<const std::complex<double>*>(all_scaled_overlap_coefficients.data());
715
717 "N",
718 &number_of_plane_waves,
719 &number_of_vectors,
720 &total_number_of_projectors,
721 cast_alpha_one,
722 cast_projector_matrix,
723 &leading_dimension,
724 cast_all_scaled_overlap_coefficients_const,
725 &total_number_of_projectors,
726 cast_beta_one,
727 cast_hamiltonian_wavefunctions,
728 &leading_dimension);
729
730 /**
731 * 4. DFT+U (Hubbard) nonlocal-like projector term -- structurally identical to the
732 * KB nonlocal block above (steps 1-3), but with atomic-orbital projectors and a
733 * D-matrix that depends on the occupation matrix (refreshed once per SCF iteration
734 * by the caller, not lazily rebuilt here).
735 */
736 bool has_hubbard =
737 current_kpoint_index_ < static_cast<int>(cached_hubbard_projector_map_.size()) &&
738 cached_hubbard_projector_map_[current_kpoint_index_] != nullptr &&
739 cached_hubbard_d_matrix_ptr_ != nullptr;
740 if (has_hubbard) {
741 const auto& hubbard_projectors = *cached_hubbard_projector_map_[current_kpoint_index_];
742 int total_hubbard_orbitals = static_cast<int>(hubbard_projectors.rows());
743 if (total_hubbard_orbitals > 0) {
744 auto* cast_hubbard_projector_matrix =
745 reinterpret_cast<const std::complex<double>*>(hubbard_projectors.data());
746
747 // Step 1: overlaps <phi_m|psi_v>.
748 std::vector<Complex> hubbard_overlaps(static_cast<size_t>(total_hubbard_orbitals) *
749 number_of_vectors,
750 Complex(0.0, 0.0));
751 auto* cast_hubbard_overlaps =
752 reinterpret_cast<std::complex<double>*>(hubbard_overlaps.data());
754 "N",
755 &total_hubbard_orbitals,
756 &number_of_vectors,
757 &number_of_plane_waves,
758 cast_alpha_one,
759 cast_hubbard_projector_matrix,
760 &leading_dimension,
761 cast_wavefunctions,
762 &leading_dimension,
763 cast_beta_zero,
764 cast_hubbard_overlaps,
765 &total_hubbard_orbitals);
766
767 /**
768 * Gamma-only: the ZGEMM summed half the G sphere, so complete
769 * <phi_m|psi> the same way the KB block above completes
770 * <beta|psi> -- 2*Re[...] with the G = 0 term, which this half
771 * sphere contains, subtracted once.
772 *
773 * The KB block reaches for a real DGEMM because its projector
774 * count and its share of H|psi> are large; here the orbital
775 * count is 2l+1 per Hubbard atom and the correction is a scalar
776 * pass over a matrix the ZGEMM already wrote, so the complex
777 * GEMM is kept and only its result is repaired. G = 0 is the
778 * first plane wave (the list is sorted by |k+G|^2) on the rank
779 * that holds it, as the KB block also assumes; a rank that
780 * does not hold it subtracts nothing.
781 *
782 * Only step 1 needs this. Step 3 writes H|psi> back onto the
783 * same half sphere, and the coefficients it projects with are
784 * real -- D is real because n is real symmetric at Gamma -- so
785 * the result still satisfies the half sphere's reality
786 * condition.
787 */
788 if (is_gamma_only_current_) {
789 for (int vector_index = 0; vector_index < number_of_vectors; ++vector_index) {
790 const Complex* wavefunction_g_zero =
791 &psi_wavefunctions[static_cast<size_t>(vector_index) *
792 number_of_plane_waves];
793 for (int orbital_index = 0; orbital_index < total_hubbard_orbitals;
794 ++orbital_index) {
795 Real g_zero_product =
796 gamma_origin_is_local_current_
797 ? (std::conj(hubbard_projectors(
798 static_cast<size_t>(orbital_index), 0)) *
799 wavefunction_g_zero[0])
800 .real()
801 : 0.0;
802 size_t flat_index =
803 static_cast<size_t>(vector_index) * total_hubbard_orbitals +
804 orbital_index;
805 hubbard_overlaps[flat_index] = gamma_only_full_sphere_overlap(
806 hubbard_overlaps[flat_index], g_zero_product);
807 }
808 }
809 }
810 execution_policy_.reduce_within_group(hubbard_overlaps.data(),
811 total_hubbard_orbitals * number_of_vectors);
812
813 // Step 2: apply the dense block-diagonal D-matrix.
814 std::vector<Complex> hubbard_scaled_overlaps(
815 static_cast<size_t>(total_hubbard_orbitals) * number_of_vectors,
816 Complex(0.0, 0.0));
817 auto* cast_hubbard_d_matrix = reinterpret_cast<const std::complex<double>*>(
818 cached_hubbard_d_matrix_ptr_->data());
819 auto* cast_hubbard_overlaps_const =
820 reinterpret_cast<const std::complex<double>*>(hubbard_overlaps.data());
821 auto* cast_hubbard_scaled_overlaps =
822 reinterpret_cast<std::complex<double>*>(hubbard_scaled_overlaps.data());
824 "N",
825 &total_hubbard_orbitals,
826 &number_of_vectors,
827 &total_hubbard_orbitals,
828 cast_alpha_one,
829 cast_hubbard_d_matrix,
830 &total_hubbard_orbitals,
831 cast_hubbard_overlaps_const,
832 &total_hubbard_orbitals,
833 cast_beta_zero,
834 cast_hubbard_scaled_overlaps,
835 &total_hubbard_orbitals);
836
837 // Step 3: project back and accumulate into H|psi>.
838 auto* cast_hubbard_scaled_overlaps_const =
839 reinterpret_cast<const std::complex<double>*>(hubbard_scaled_overlaps.data());
841 "N",
842 &number_of_plane_waves,
843 &number_of_vectors,
844 &total_hubbard_orbitals,
845 cast_alpha_one,
846 cast_hubbard_projector_matrix,
847 &leading_dimension,
848 cast_hubbard_scaled_overlaps_const,
849 &total_hubbard_orbitals,
850 cast_beta_one,
851 cast_hamiltonian_wavefunctions,
852 &leading_dimension);
853 }
854 }
855
856 // Accumulate H|ψ⟩ sub-component timing
857 auto hpsi_timer_end = std::chrono::high_resolution_clock::now();
860 std::chrono::duration<double, std::milli>(hpsi_timer_after_fft - hpsi_timer_start)
861 .count();
862 timing.hamiltonian_nonlocal_time_ms +=
863 std::chrono::duration<double, std::milli>(hpsi_timer_end - hpsi_timer_after_fft)
864 .count();
865 timing.hamiltonian_projector_overlap_time_ms +=
866 std::chrono::duration<double, std::milli>(hpsi_timer_after_overlap -
867 hpsi_timer_after_fft)
868 .count();
869 }
870
871 // ─── Smooth potential computation ───────────────────────────────
872
873 /**
874 * Restrict the effective potential from the dense (charge density) FFT grid
875 * to the smooth (wavefunction) FFT grid via G-space truncation.
876 *
877 * Uses shared utility for the core FFT + G-space truncation, then adds
878 * MPI reduce for band-distributed grids.
879 */
880 std::vector<Real>
881 compute_smooth_grid_potential(const std::vector<Real>& dense_real_space_potential,
882 const PlanewaveBasis& planewave_basis) const {
883
884 int total_smooth_points = planewave_basis.fft_wave().total_grid_points();
885
886 // Core: dense FFT -> G-space truncation -> smooth V(G)
887 auto full_smooth_potential_g =
888 compute_smooth_grid_potential_from_dense(dense_real_space_potential, planewave_basis);
889
890 // MPI: Allreduce to ensure all ranks have the full V_smooth(G)
891 execution_policy_.reduce_group_distributed_result(full_smooth_potential_g.data(),
892 total_smooth_points);
893
894 // Inverse FFT to real space
895 return inverse_fft_smooth_potential(full_smooth_potential_g, planewave_basis);
896 }
897
898 // ─── G-vector to FFT mapping ────────────────────────────────────
899
900 /**
901 * Pre-compute G-vector to smooth FFT grid index mapping.
902 *
903 * Uses the shared core mapping, then adds gamma-only (-G) and
904 * serial (task-group) mappings specific to the serial applicator.
905 */
906 void precompute_g_to_fft_mapping(int number_of_plane_waves,
907 const std::vector<int>& g_vector_indices,
908 const PlanewaveBasis& planewave_basis) {
909 // Core G -> smooth FFT index mapping (shared with GPU applicator)
911 number_of_plane_waves, g_vector_indices, planewave_basis, g_to_fft_mapping_);
912
913 // For gamma-only: pre-compute the -G FFT grid index for each G-vector.
914 if (planewave_basis.is_gamma_only()) {
915 minus_g_to_fft_mapping_.resize(number_of_plane_waves);
916 for (int g_vector_index = 0; g_vector_index < number_of_plane_waves; g_vector_index++) {
917 const auto& g_vector =
918 planewave_basis.g_vectors()[g_vector_indices[g_vector_index]];
919 int miller_index_1 = static_cast<int>(std::round(g_vector.miller.x));
920 int miller_index_2 = static_cast<int>(std::round(g_vector.miller.y));
921 int miller_index_3 = static_cast<int>(std::round(g_vector.miller.z));
922 minus_g_to_fft_mapping_[g_vector_index] =
923 planewave_basis.fft_wave().g_vector_to_fft_grid_index(
924 -miller_index_1, -miller_index_2, -miller_index_3);
925 }
926 } else {
927 minus_g_to_fft_mapping_.clear();
928 }
929
930 // Serial (full-grid) mapping for task-group band distribution.
931 {
932 int fft_grid_dimension_1 = planewave_basis.fft_wave().grid_dimension_1();
933 int fft_grid_dimension_2 = planewave_basis.fft_wave().grid_dimension_2();
934 int fft_grid_dimension_3 = planewave_basis.fft_wave().grid_dimension_3();
935 g_to_fft_serial_mapping_.resize(number_of_plane_waves);
936 const auto& serial_fft = planewave_basis.fft_wave_serial();
937 for (int g_vector_index = 0; g_vector_index < number_of_plane_waves; g_vector_index++) {
938 const auto& g_vector =
939 planewave_basis.g_vectors()[g_vector_indices[g_vector_index]];
940 int miller_index_1 = static_cast<int>(std::round(g_vector.miller.x));
941 int miller_index_2 = static_cast<int>(std::round(g_vector.miller.y));
942 int miller_index_3 = static_cast<int>(std::round(g_vector.miller.z));
943 if (std::abs(miller_index_1) <= fft_grid_dimension_1 / 2 &&
944 std::abs(miller_index_2) <= fft_grid_dimension_2 / 2 &&
945 std::abs(miller_index_3) <= fft_grid_dimension_3 / 2) {
946 g_to_fft_serial_mapping_[g_vector_index] =
947 serial_fft.g_vector_to_fft_grid_index(
948 miller_index_1, miller_index_2, miller_index_3);
949 } else {
950 g_to_fft_serial_mapping_[g_vector_index] = -1;
951 }
952 }
953 }
954 }
955
956 // ─── Run-constant context (references) ──────────────────────────
957
958 const Crystal& crystal_structure_;
959 const PlanewaveBasis& planewave_basis_;
960 const KPointsGrid& kpoint_grid_;
961 const NormConservingPlanewaveKohnShamQuantumSystem& pseudopotential_quantum_system_;
962 const execution::ExecutionPolicy& execution_policy_;
963
964 // ─── Per-k-point state ──────────────────────────────────────────
965
966 int current_kpoint_index_ = 0;
967 int current_number_of_plane_waves_ = 0;
968 const std::vector<int>* current_g_vector_indices_ = nullptr;
969 const std::vector<Real>* current_kinetic_energy_squared_ = nullptr;
970 bool is_gamma_only_current_ = false;
971 bool gamma_origin_is_local_current_ = true;
972
973 // ─── FFT mappings ───────────────────────────────────────────────
974
975 std::vector<int> g_to_fft_mapping_;
976 std::vector<int> minus_g_to_fft_mapping_;
977 std::vector<int> g_to_fft_serial_mapping_;
978
979 // ─── Cached data ────────────────────────────────────────────────
980
981 // Pre-computed KB projectors per k-point (set externally, nullptr means compute internally).
982 std::vector<const q3::Matrix<Complex>*> cached_projector_map_;
983 /**
984 * Pre-computed DFT+U (Hubbard) atomic-orbital projectors per k-point (set externally;
985 * nullptr means no Hubbard correction is active).
986 */
987 std::vector<const q3::Matrix<Complex>*> cached_hubbard_projector_map_;
988 /**
989 * Dense block-diagonal Hubbard D-matrix (column-major), refreshed once per SCF iteration
990 * by the caller. Not owned; not lazily rebuilt (see set_hubbard_d_matrix()).
991 */
992 const std::vector<Complex>* cached_hubbard_d_matrix_ptr_ = nullptr;
993 // Cached smooth grid potential to avoid redundant dense-to-smooth FFTs.
994 mutable std::vector<Real> cached_smooth_potential_;
995 mutable std::vector<Real> cached_scaled_potential_;
996 bool smooth_potential_cache_valid_ = false;
997 // Cached dense block-diagonal D-matrix for the nonlocal PP application.
998 /**
999 * The D matrix as contiguous per-atom blocks, with an offset, a size and a
1000 * projector start for each atom. Replaces a dense P x P matrix that was
1001 * almost entirely zero.
1002 */
1003 mutable std::vector<Complex> cached_block_d_matrix_;
1004 mutable std::vector<std::size_t> cached_block_d_offsets_;
1005 mutable std::vector<int> cached_block_d_sizes_;
1006 mutable std::vector<int> cached_block_d_projector_starts_;
1007 // Full-grid potential for task-group band distribution (Allgathered from slabs).
1008 mutable std::vector<Real> cached_full_grid_potential_;
1009};
1010
1011} // namespace q3
int species_type_index(int atom_index) const
Definition crystal.hpp:273
int number_of_atoms() const
Definition crystal.hpp:209
virtual int concurrent_transform_slots() const
Definition fft_grid.hpp:235
virtual int local_g_space_points() const
Definition fft_grid.hpp:88
virtual int total_grid_points() const =0
virtual int local_grid_points() const =0
const BetaFunctionTable & beta_function_table() const
Definition init.hpp:284
int total_number_of_projectors() const
Definition init.hpp:181
const PseudopotentialSet & pseudopotential_set() const
Definition init.hpp:156
int number_of_projector_channels(int species_type) const
Definition init.hpp:203
const NormConservingPlanewaveBasis & pseudopotential_basis() const
Definition init.hpp:65
virtual Real d_matrix_element(int atom_index, int species_type, int projector_index_i, int projector_index_j, int spin=0) const
Definition init.hpp:84
FFTGrid & fft_wave()
Definition init.hpp:292
bool gamma_origin_is_local() const
Definition init.hpp:172
bool is_gamma_only() const
Definition init.hpp:157
int spin_channel_
Spin channel the current potential and D belong to; 0 when unpolarized.
Definition init.hpp:160
void prepare_kpoint(int kpoint_index, int number_of_plane_waves, const std::vector< int > &g_vector_indices, const std::vector< Real > &kinetic_energy_squared) override
Definition serial.hpp:95
bool is_smooth_potential_cache_valid() const override
Definition serial.hpp:170
SerialPlanewaveHamiltonianApplicator(const Crystal &crystal_structure, const PlanewaveBasis &planewave_basis, const KPointsGrid &kpoint_grid, const NormConservingPlanewaveKohnShamQuantumSystem &pseudopotential_quantum_system, const execution::ExecutionPolicy &execution_policy)
Definition serial.hpp:45
const std::vector< Real > & cached_scaled_potential() const
Definition serial.hpp:177
void set_hubbard_d_matrix(const std::vector< Complex > *dense_d_matrix_column_major) override
Definition serial.hpp:245
void update_potential(const std::vector< Real > &real_space_potential) override
Definition serial.hpp:63
void set_cached_projectors(int kpoint_index, const q3::Matrix< Complex > *projectors) override
Definition serial.hpp:210
const q3::Matrix< Complex > * get_cached_projectors(int kpoint_index) const override
Definition serial.hpp:221
const std::vector< int > & g_to_fft_mapping() const
Definition serial.hpp:184
void invalidate_smooth_potential_cache() override
Definition serial.hpp:159
void apply_batch(const Complex *input_wavefunctions, Complex *output_hamiltonian_wavefunctions, int number_of_vectors) override
Definition serial.hpp:112
bool is_gamma_only_current() const override
Definition serial.hpp:198
const std::vector< int > & minus_g_to_fft_mapping() const
Definition serial.hpp:191
bool gamma_origin_is_local_current() const override
Definition serial.hpp:202
void set_cached_hubbard_projectors(int kpoint_index, const q3::Matrix< Complex > *projectors) override
Definition serial.hpp:232
virtual int band_distribution_rank_index() const
Definition init.hpp:247
virtual int group_size() const =0
virtual void allgather_within_group(const Real *local_data, int local_count, Real *full_data, int total_count) const =0
virtual void allgather_across_bands(ComplexValue *, const int *, const int *) const
Definition init.hpp:265
virtual void reduce_group_distributed_result(ComplexValue *, int) const
Definition init.hpp:215
virtual int band_distribution_size() const
Definition init.hpp:240
virtual void reduce_within_group(ComplexValue *data, int number_of_elements) const =0
void dgemm_(const char *transa, const char *transb, const int *m, const int *n, const int *k, const double *alpha, const double *a, const int *lda, const double *b, const int *ldb, const double *beta, double *c, const int *ldc)
void zgemm_(const char *transa, const char *transb, const int *m, const int *n, const int *k, const std::complex< double > *alpha, const std::complex< double > *a, const int *lda, const std::complex< double > *b, const int *ldb, const std::complex< double > *beta, std::complex< double > *c, const int *ldc)
int thread_count_for(long long work_items, long long minimum_items_per_thread)
BandPartition partition_bands(int number_of_bands, int number_of_ranks, int rank_index, int elements_per_band, int band_granularity=1)
Definition init.hpp:52
constexpr double d_matrix_element_threshold
double Real
std::vector< Complex > compute_smooth_grid_potential_from_dense(const std::vector< Real > &dense_real_space_potential, const PlanewaveBasis &planewave_basis)
std::vector< Real > inverse_fft_smooth_potential(const std::vector< Complex > &full_smooth_potential_g, const PlanewaveBasis &planewave_basis)
void compute_kleinman_bylander_projectors(const Vector3D &k_crystal, const Crystal &crystal_structure, const PlanewaveBasis &planewave_basis, const PseudopotentialSet &pseudopotential_set, const BetaFunctionTable &beta_function_table, const std::vector< int > &g_vector_indices, int number_of_plane_waves, int total_number_of_projectors, Matrix< Complex > &kleinman_bylander_potential)
Definition projector.hpp:36
void compute_g_to_fft_index_mapping(int number_of_plane_waves, const std::vector< int > &g_vector_indices, const PlanewaveBasis &planewave_basis, std::vector< int > &g_to_fft_mapping)
Complex gamma_only_full_sphere_overlap(Complex half_sphere_sum, Real g_zero_product)
Definition hubbard.hpp:352
std::complex< double > Complex
static ProfilingCounters & instance()
Definition counters.hpp:220
std::atomic< long long > hamiltonian_application_count
Definition counters.hpp:24
DavidsonTimingAccumulator davidson_timing
Definition counters.hpp:124