Loading...
Searching...
No Matches
gpu.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * GPUPlanewaveHamiltonianApplicator — GPU implementation of H|ψ⟩ application.
5 *
6 * Implements PlanewaveHamiltonianApplicator using GPU math wrappers (GPUBLAS,
7 * GPUFFTGrid) and device memory (GPUDeviceBuffer). All data transferred to
8 * the GPU is stored in GPUDeviceBuffer members; all BLAS calls go through
9 * the GPUBLAS interface.
10 *
11 * Supports both double precision (FP64) and single precision (FP32) paths.
12 * The FP32 path is used by the mixed-precision Davidson loop when the
13 * precision policy indicates that single precision is appropriate.
14 *
15 * Constructor takes the same planewave context as SerialPlanewaveHamiltonianApplicator,
16 * plus GPUBLAS&, GPUFFTGrid&, and shared_ptr<GPUDeviceBackend>.
17 *
18 * No CUDA headers. No Metal headers. Uses only the vendor-neutral GPU abstractions.
19 */
20
21#include <algorithm>
22#include <cmath>
23#include <complex>
24#include <cstring>
25#include <memory>
26#include <stdexcept>
27#include <string>
28#include <vector>
29
30#include "io/logger.hpp"
48
49namespace q3 {
50
52 public:
53 /**
54 * Construct with run-constant planewave context and GPU math wrappers.
55 *
56 * All references and the shared_ptr must outlive this object.
57 *
58 * @param crystal_structure Crystal geometry.
59 * @param planewave_basis Planewave basis (G-vectors, FFT grids).
60 * @param kpoint_grid K-point grid for Brillouin zone sampling.
61 * @param pseudopotential_quantum_system Pseudopotential data (KB projectors, D-matrix).
62 * @param gpu_blas GPU BLAS wrapper for matrix/vector operations.
63 * @param gpu_fft_grid GPU FFT grid for potential application.
64 * @param device_backend GPU device backend for memory allocation.
65 */
67 const Crystal& crystal_structure,
68 const PlanewaveBasis& planewave_basis,
69 const KPointsGrid& kpoint_grid,
70 const NormConservingPlanewaveKohnShamQuantumSystem& pseudopotential_quantum_system,
72 gpu::GPUFFTGrid* gpu_fft_grid,
73 std::shared_ptr<execution::gpu::device::GPUDeviceBackend> device_backend)
74 : crystal_structure_{crystal_structure}
75 , planewave_basis_{planewave_basis}
76 , kpoint_grid_{kpoint_grid}
77 , pseudopotential_quantum_system_{pseudopotential_quantum_system}
78 , gpu_blas_{gpu_blas}
79 , gpu_fft_grid_{gpu_fft_grid}
80 , device_backend_{std::move(device_backend)}
81 // Initialize empty GPU buffers — they will be resized as needed.
82 , device_smooth_potential_(0, device_backend_)
83 , device_smooth_potential_single_(0, device_backend_)
84 , device_g_to_fft_mapping_(0, device_backend_)
85 , device_kinetic_energy_squared_(0, device_backend_)
86 , device_kinetic_energy_squared_single_(0, device_backend_)
87 , device_projector_matrix_(0, device_backend_)
88 , device_projector_matrix_single_(0, device_backend_)
89 , device_dense_d_matrix_(0, device_backend_)
90 , device_fft_workspace_(0, device_backend_)
91 , device_fft_workspace_single_(0, device_backend_)
92 , device_fft_batch_workspace_(0, device_backend_)
93 , device_fft_batch_workspace_single_(0, device_backend_)
94 , device_minus_g_to_fft_mapping_(0, device_backend_) {
95 /**
96 * Initialize the GPU FFT grid with the wave (smooth) grid dimensions
97 * so that cuFFT plans are created before any FFT calls.
98 */
99 if (gpu_fft_grid_ != nullptr) {
100 const auto& wave_fft = planewave_basis_.fft_wave();
101 gpu_fft_grid_->init(wave_fft.grid_dimension_1(),
102 wave_fft.grid_dimension_2(),
103 wave_fft.grid_dimension_3());
104 }
105 }
106
107 /**
108 * Enable or disable mixed-precision mode.
109 *
110 * When enabled, apply_batch() runs the local potential FFT section in FP32
111 * (scatter FP64→FP32, FFT, V*ψ, FFT, gather FP32→FP64). Kinetic energy
112 * and nonlocal projectors remain in FP64. This gives most of the GPU FP32
113 * speedup without restructuring the Davidson loop.
114 */
115 void set_use_mixed_precision(bool use_mixed_precision) {
116 use_mixed_precision_ = use_mixed_precision;
117 if (use_mixed_precision) {
118 log::info(" GPU applicator: mixed-precision FP32 FFT enabled");
119 }
120 }
121
122 /**
123 * Update the effective potential for a new SCF iteration.
124 *
125 * Computes the smooth-grid potential on CPU (same as serial), then
126 * copies the result to a GPUDeviceBuffer for GPU-side V(r)·ψ(r)
127 * multiplication.
128 */
129 void update_potential(const std::vector<Real>& real_space_potential) override {
130 // Compute smooth grid potential on CPU (reuses serial logic).
131 cached_smooth_potential_ =
132 compute_smooth_grid_potential(real_space_potential, planewave_basis_);
133
134 int local_smooth_grid_points = planewave_basis_.fft_wave().local_grid_points();
135 cached_scaled_potential_.resize(local_smooth_grid_points);
136 Real inverse_total_points = 1.0 / planewave_basis_.fft_wave().total_grid_points();
137 for (int index = 0; index < local_smooth_grid_points; ++index) {
138 cached_scaled_potential_[index] =
139 cached_smooth_potential_[index] * inverse_total_points;
140 }
141
142 // Copy scaled potential to GPU buffer.
143 device_smooth_potential_ = execution::gpu::device::GPUDeviceBuffer<Real>(
144 local_smooth_grid_points, device_backend_);
145 std::memcpy(device_smooth_potential_.data(),
146 cached_scaled_potential_.data(),
147 local_smooth_grid_points * sizeof(Real));
148
149 // Also prepare single-precision copy for mixed-precision path.
150 device_smooth_potential_single_ = execution::gpu::device::GPUDeviceBuffer<RealSingle>(
151 local_smooth_grid_points, device_backend_);
152 precision::cast_real_double_to_single(local_smooth_grid_points,
153 cached_scaled_potential_.data(),
154 device_smooth_potential_single_.data());
155
156 smooth_potential_cache_valid_ = true;
157 cached_dense_d_matrix_.clear();
158 }
159
160 /**
161 * Configure for a specific k-point.
162 *
163 * Copies G-to-FFT mapping, kinetic energy factors, and KB projectors
164 * to GPUDeviceBuffers. The KB projectors are computed on CPU and then
165 * transferred to the GPU.
166 */
167 void prepare_kpoint(int kpoint_index,
168 int number_of_plane_waves,
169 const std::vector<int>& g_vector_indices,
170 const std::vector<Real>& kinetic_energy_squared) override {
171 current_kpoint_index_ = kpoint_index;
172 current_number_of_plane_waves_ = number_of_plane_waves;
173
174 // Compute G-to-FFT mapping on CPU (same as serial).
175 precompute_g_to_fft_mapping(number_of_plane_waves, g_vector_indices, planewave_basis_);
176
177 // Copy mapping to GPU.
178 device_g_to_fft_mapping_ =
179 execution::gpu::device::GPUDeviceBuffer<int>(number_of_plane_waves, device_backend_);
180 std::memcpy(device_g_to_fft_mapping_.data(),
181 g_to_fft_mapping_.data(),
182 number_of_plane_waves * sizeof(int));
183
184 if (planewave_basis_.is_gamma_only()) {
185 device_minus_g_to_fft_mapping_ = execution::gpu::device::GPUDeviceBuffer<int>(
186 number_of_plane_waves, device_backend_);
187 std::memcpy(device_minus_g_to_fft_mapping_.data(),
188 minus_g_to_fft_mapping_.data(),
189 number_of_plane_waves * sizeof(int));
190 } else {
191 device_minus_g_to_fft_mapping_ =
193 }
194
195 // Copy kinetic energy to GPU (FP64).
196 device_kinetic_energy_squared_ =
197 execution::gpu::device::GPUDeviceBuffer<Real>(number_of_plane_waves, device_backend_);
198 std::memcpy(device_kinetic_energy_squared_.data(),
199 kinetic_energy_squared.data(),
200 number_of_plane_waves * sizeof(Real));
201
202 // Copy kinetic energy to GPU (FP32) for mixed-precision.
203 device_kinetic_energy_squared_single_ = execution::gpu::device::GPUDeviceBuffer<RealSingle>(
204 number_of_plane_waves, device_backend_);
205 precision::cast_real_double_to_single(number_of_plane_waves,
206 kinetic_energy_squared.data(),
207 device_kinetic_energy_squared_single_.data());
208
209 // Compute KB projectors on CPU.
210 Vector3D kpoint_coordinates = kpoint_grid_[kpoint_index].coordinates;
212 kpoint_coordinates,
213 crystal_structure_,
214 planewave_basis_,
215 pseudopotential_quantum_system_.pseudopotential_basis().pseudopotential_set(),
216 pseudopotential_quantum_system_.pseudopotential_basis().beta_function_table(),
217 g_vector_indices,
218 number_of_plane_waves,
219 pseudopotential_quantum_system_.pseudopotential_basis().total_number_of_projectors(),
220 cached_projector_matrix_);
221
222 if (!cached_projector_matrix_.empty()) {
223 int total_projectors = static_cast<int>(cached_projector_matrix_.rows());
224 current_total_projectors_ = total_projectors;
225 int projector_data_size = total_projectors * number_of_plane_waves;
226
227 // Copy projectors to GPU (FP64).
228 device_projector_matrix_ = execution::gpu::device::GPUDeviceBuffer<Complex>(
229 projector_data_size, device_backend_);
230 std::memcpy(device_projector_matrix_.data(),
231 cached_projector_matrix_.data(),
232 projector_data_size * sizeof(Complex));
233
234 // Copy projectors to GPU (FP32) for mixed-precision.
235 device_projector_matrix_single_ =
237 device_backend_);
238 precision::cast_double_to_single(projector_data_size,
239 cached_projector_matrix_.data(),
240 device_projector_matrix_single_.data());
241
242 // Build and copy dense D-matrix to GPU.
243 build_dense_d_matrix(total_projectors);
244 } else {
245 current_total_projectors_ = 0;
246 }
247 }
248
249 /**
250 * Apply H|ψ⟩ in double precision (FP64) to a batch of vectors.
251 *
252 * Uses GPU BLAS for KB projector application (3× ZGEMM) and CPU-side
253 * FFT for the local potential application. The kinetic energy is applied
254 * element-wise on the host (unified memory).
255 *
256 * All input/output pointers must come from GPUDeviceBuffer (unified memory).
257 */
258 void apply_batch(const Complex* input_wavefunctions,
259 Complex* output_hamiltonian_wavefunctions,
260 int number_of_vectors) override {
261 int number_of_plane_waves = current_number_of_plane_waves_;
262 int leading_dimension = std::max(number_of_plane_waves, 1);
263 int local_smooth_grid_points = planewave_basis_.fft_wave().local_grid_points();
264
265 // Ensure FFT workspace is large enough.
266 if (static_cast<int>(device_fft_workspace_.size()) < local_smooth_grid_points) {
268 local_smooth_grid_points, device_backend_);
269 }
270
271 // 1. Kinetic energy (element-wise on GPU).
273 number_of_vectors,
274 number_of_plane_waves,
275 device_kinetic_energy_squared_.data(),
276 input_wavefunctions,
277 output_hamiltonian_wavefunctions);
278
279 // 2. Local potential via FFT.
280 if (use_mixed_precision_) {
281 // Mixed-precision path: scatter FP64→FP32, FFT in FP32, gather FP32→FP64.
282 apply_local_potential_mixed_precision(input_wavefunctions,
283 output_hamiltonian_wavefunctions,
284 number_of_vectors,
285 number_of_plane_waves,
286 local_smooth_grid_points);
287 } else {
288 // Full FP64 path: FFT via planewave_basis_.fft_wave().
289 apply_local_potential_double_precision(input_wavefunctions,
290 output_hamiltonian_wavefunctions,
291 number_of_vectors,
292 number_of_plane_waves,
293 local_smooth_grid_points);
294 }
295
296 // 3. Nonlocal KB projectors via GPU BLAS (3× GEMM, always FP64).
297 if (current_total_projectors_ > 0) {
298 apply_nonlocal_projectors_double(input_wavefunctions,
299 output_hamiltonian_wavefunctions,
300 number_of_plane_waves,
301 number_of_vectors);
302 }
303 device_backend_->synchronize();
304 }
305
306 /**
307 * Apply H|ψ⟩ in single precision (FP32) to a batch of vectors.
308 *
309 * Identical algorithm to apply_batch() but operates on ComplexSingle
310 * arrays and uses FP32 BLAS/FFT calls. Used by the mixed-precision
311 * Davidson loop for bulk iterations before switchover.
312 */
313 void apply_batch_single_precision(const ComplexSingle* input_wavefunctions,
314 ComplexSingle* output_hamiltonian_wavefunctions,
315 int number_of_vectors) {
316 int number_of_plane_waves = current_number_of_plane_waves_;
317 int local_smooth_grid_points = planewave_basis_.fft_wave().local_grid_points();
318 bool is_gamma = planewave_basis_.is_gamma_only();
319
320 // Ensure single-precision FFT workspace.
321 if (static_cast<int>(device_fft_workspace_single_.size()) < local_smooth_grid_points) {
322 device_fft_workspace_single_ = execution::gpu::device::GPUDeviceBuffer<ComplexSingle>(
323 local_smooth_grid_points, device_backend_);
324 }
325
326 // 1. Kinetic energy in FP32 on GPU.
328 number_of_vectors,
329 number_of_plane_waves,
330 device_kinetic_energy_squared_single_.data(),
331 input_wavefunctions,
332 output_hamiltonian_wavefunctions);
333
334 /**
335 * 2. Local potential via batched FFT in FP32.
336 * Scatter all bands → batch inverse FFT → V·ψ for all → batch forward FFT → gather all.
337 * This replaces 2×N single FFT calls with 2 batched kernel launches.
338 */
339 int batch_workspace_size = local_smooth_grid_points * number_of_vectors;
340 if (static_cast<int>(device_fft_batch_workspace_single_.size()) < batch_workspace_size) {
341 device_fft_batch_workspace_single_ =
343 device_backend_);
344 }
345
346 // Scatter all bands to contiguous FFT workspaces on GPU.
348 number_of_vectors,
349 number_of_plane_waves,
350 local_smooth_grid_points,
351 device_g_to_fft_mapping_.data(),
352 is_gamma ? device_minus_g_to_fft_mapping_.data() : nullptr,
353 is_gamma,
354 input_wavefunctions,
355 device_fft_batch_workspace_single_.data());
356
357 // Batched inverse FFT in FP32 (all bands in one kernel launch).
358 gpu_fft_grid_->inverse_raw_single_batch(device_fft_batch_workspace_single_.data(),
359 number_of_vectors);
360
361 // Multiply each band by potential in FP32 on GPU.
363 number_of_vectors,
364 local_smooth_grid_points,
365 device_smooth_potential_single_.data(),
366 device_fft_batch_workspace_single_.data());
367
368 // Batched forward FFT in FP32 (all bands in one kernel launch).
369 gpu_fft_grid_->forward_single_batch(device_fft_batch_workspace_single_.data(),
370 number_of_vectors);
371
372 // Gather all bands from FFT workspaces on GPU.
374 number_of_vectors,
375 number_of_plane_waves,
376 local_smooth_grid_points,
377 device_g_to_fft_mapping_.data(),
378 device_fft_batch_workspace_single_.data(),
379 output_hamiltonian_wavefunctions);
380
381 // 3. Nonlocal KB projectors via GPU BLAS in FP32 (3× CGEMM).
382 if (current_total_projectors_ > 0) {
383 apply_nonlocal_projectors_single(input_wavefunctions,
384 output_hamiltonian_wavefunctions,
385 number_of_plane_waves,
386 number_of_vectors);
387 }
388 device_backend_->synchronize();
389 }
390
392 smooth_potential_cache_valid_ = false;
393 cached_dense_d_matrix_.clear();
394 }
395
396 /**
397 * Whether the smooth potential cache is currently valid.
398 */
399 [[nodiscard]] bool is_smooth_potential_cache_valid() const override {
400 return smooth_potential_cache_valid_;
401 }
402
403 /**
404 * Access the cached scaled potential (for serial fallback or debugging).
405 */
406 [[nodiscard]] const std::vector<Real>& cached_scaled_potential() const {
407 return cached_scaled_potential_;
408 }
409
410 /**
411 * Access the G-to-FFT mapping for the current k-point.
412 */
413 [[nodiscard]] const std::vector<int>& g_to_fft_mapping() const {
414 return g_to_fft_mapping_;
415 }
416
417 /**
418 * Access the minus-G-to-FFT mapping (for gamma-only scatter/gather).
419 */
420 [[nodiscard]] const std::vector<int>& minus_g_to_fft_mapping() const {
421 return minus_g_to_fft_mapping_;
422 }
423
424 /**
425 * Report whether the current k-point is gamma-only.
426 *
427 * This affects dot product computations in the subspace manager:
428 * gamma-only uses 2×Re(z·z*) instead of complex zdotc.
429 */
430 [[nodiscard]] bool is_gamma_only_current() const override {
431 return planewave_basis_.is_gamma_only();
432 }
433
434 /**
435 * No-op: GPU applicator computes projectors internally in prepare_kpoint().
436 */
437 void set_cached_projectors(int /*kpoint_index*/,
438 const q3::Matrix<Complex>* /*projectors*/) override {}
439
440 /**
441 * Returns nullptr — GPU applicator manages projectors internally.
442 */
443 [[nodiscard]] const q3::Matrix<Complex>*
444 get_cached_projectors(int /*kpoint_index*/) const override {
445 return nullptr;
446 }
447
448 private:
449 // ─── Local potential FFT application ──────────────────────────────
450
451 /**
452 * Apply local potential via FFT in full FP64 precision.
453 *
454 * When a GPU FFT grid is available, uses gpu_fft_grid_ for FP64 FFTs
455 * (cuFFT Z2Z on CUDA). Falls back to planewave_basis's serial FFT grid
456 * (CPU FFTW) when gpu_fft_grid_ is null.
457 *
458 * Pipeline: scatter ψ(G) → IFFT → V(r)·ψ(r) → FFT → gather H|ψ⟩(G)
459 */
460 void apply_local_potential_double_precision(const Complex* input_wavefunctions,
461 Complex* output_hamiltonian_wavefunctions,
462 int number_of_vectors,
463 int number_of_plane_waves,
464 int local_smooth_grid_points) {
465 bool is_gamma = planewave_basis_.is_gamma_only();
466
467 if (gpu_fft_grid_ != nullptr) {
468 /**
469 * GPU path: batched FP64 FFTs (cuFFT Z2Z on CUDA).
470 * scatter ALL bands → batch inverse FFT → V·ψ ALL → batch forward FFT → gather ALL.
471 */
472 int batch_workspace_size = local_smooth_grid_points * number_of_vectors;
473 if (static_cast<int>(device_fft_batch_workspace_.size()) < batch_workspace_size) {
474 device_fft_batch_workspace_ = execution::gpu::device::GPUDeviceBuffer<Complex>(
475 batch_workspace_size, device_backend_);
476 }
477
478 // Scatter all bands to contiguous FFT workspaces on GPU.
480 number_of_vectors,
481 number_of_plane_waves,
482 local_smooth_grid_points,
483 device_g_to_fft_mapping_.data(),
484 is_gamma ? device_minus_g_to_fft_mapping_.data() : nullptr,
485 is_gamma,
486 input_wavefunctions,
487 device_fft_batch_workspace_.data());
488
489 // Batched inverse FFT in FP64 (all bands in one kernel launch).
490 gpu_fft_grid_->inverse_transform_batch(device_fft_batch_workspace_.data(),
491 number_of_vectors);
492
493 // Multiply each band by potential on GPU.
495 number_of_vectors,
496 local_smooth_grid_points,
497 device_smooth_potential_.data(),
498 device_fft_batch_workspace_.data());
499
500 // Batched forward FFT in FP64 (all bands in one kernel launch).
501 gpu_fft_grid_->forward_transform_batch(device_fft_batch_workspace_.data(),
502 number_of_vectors);
503
504 // Gather all bands from FFT workspaces on GPU.
506 number_of_vectors,
507 number_of_plane_waves,
508 local_smooth_grid_points,
509 device_g_to_fft_mapping_.data(),
510 device_fft_batch_workspace_.data(),
511 output_hamiltonian_wavefunctions);
512 } else {
513 // CPU fallback: use serial FFT grid (FFTW).
514 const FFTGrid& fft_wave = planewave_basis_.fft_wave();
515 Complex* fft_input = fft_wave.input_buffer();
516 for (int vector_index = 0; vector_index < number_of_vectors; ++vector_index) {
517 const Complex* psi = &input_wavefunctions[vector_index * number_of_plane_waves];
518 Complex* hpsi =
519 &output_hamiltonian_wavefunctions[vector_index * number_of_plane_waves];
520
521 // Scatter to FFT grid.
522 std::memset(fft_input, 0, local_smooth_grid_points * sizeof(Complex));
523 for (int g_index = 0; g_index < number_of_plane_waves; ++g_index) {
524 int fft_index = g_to_fft_mapping_[g_index];
525 if (fft_index >= 0) {
526 fft_input[fft_index] = psi[g_index];
527 }
528 }
529 // Gamma-only: fill -G with conj(ψ(G)).
530 if (planewave_basis_.is_gamma_only()) {
531 for (int g_index = 1; g_index < number_of_plane_waves; ++g_index) {
532 int fft_minus = minus_g_to_fft_mapping_[g_index];
533 fft_input[fft_minus] = std::conj(psi[g_index]);
534 }
535 }
536
537 // Inverse FFT.
538 fft_wave.execute_inverse_raw_on_buffers();
539 Complex* fft_output = fft_wave.output_buffer();
540
541 // Multiply by potential.
542 for (int grid_index = 0; grid_index < local_smooth_grid_points; ++grid_index) {
543 fft_output[grid_index] *= cached_scaled_potential_[grid_index];
544 }
545
546 // Forward FFT.
547 fft_wave.prepare_forward_from_output();
548 fft_wave.execute_forward_on_buffers();
549 fft_output = fft_wave.output_buffer();
550
551 // Gather from FFT grid.
552 for (int g_index = 0; g_index < number_of_plane_waves; ++g_index) {
553 int fft_index = g_to_fft_mapping_[g_index];
554 if (fft_index >= 0) {
555 hpsi[g_index] += fft_output[fft_index];
556 }
557 }
558 }
559 }
560 }
561
562 /**
563 * Apply local potential with mixed-precision FFT.
564 *
565 * The scatter/IFFT/V·ψ/FFT/gather pipeline runs in FP32. On Apple Silicon
566 * (Accelerate backend), the FP32 FFT is done via upcast to FP64 through FFTW.
567 * On CUDA, this would use native cuFFT FP32 (cufftExecC2C) for real speedup.
568 *
569 * Input/output wavefunctions remain FP64 — only the FFT workspace is FP32.
570 * The accumulated hpsi contribution is cast back to FP64 before adding.
571 */
572 void apply_local_potential_mixed_precision(const Complex* input_wavefunctions,
573 Complex* output_hamiltonian_wavefunctions,
574 int number_of_vectors,
575 int number_of_plane_waves,
576 int local_smooth_grid_points) {
577
578 // Ensure FP32 FFT workspace and potential are ready.
579 if (static_cast<int>(device_fft_workspace_single_.size()) < local_smooth_grid_points) {
580 device_fft_workspace_single_ = execution::gpu::device::GPUDeviceBuffer<ComplexSingle>(
581 local_smooth_grid_points, device_backend_);
582 }
583 if (static_cast<int>(device_smooth_potential_single_.size()) < local_smooth_grid_points) {
584 // Downcast the FP64 potential to FP32 (done once per SCF iteration).
585 device_smooth_potential_single_ = execution::gpu::device::GPUDeviceBuffer<RealSingle>(
586 local_smooth_grid_points, device_backend_);
587 for (int grid_index = 0; grid_index < local_smooth_grid_points; ++grid_index) {
588 device_smooth_potential_single_.data()[grid_index] =
589 static_cast<float>(cached_scaled_potential_[grid_index]);
590 }
591 }
592
593 for (int vector_index = 0; vector_index < number_of_vectors; ++vector_index) {
594 const Complex* psi = &input_wavefunctions[vector_index * number_of_plane_waves];
595 Complex* hpsi = &output_hamiltonian_wavefunctions[vector_index * number_of_plane_waves];
596
597 if (gpu_fft_grid_ != nullptr) {
598 /**
599 * Native GPU FP32 FFT path.
600 * Scatter FP64 ψ → FP32 workspace on GPU (interleaved cast).
601 */
603 1, // Note: apply_local_potential_mixed_precision operates in a loop over
604 // vector_index, so number_of_vectors = 1
605 number_of_plane_waves,
606 local_smooth_grid_points,
607 device_g_to_fft_mapping_.data(),
608 planewave_basis_.is_gamma_only() ? device_minus_g_to_fft_mapping_.data()
609 : nullptr,
610 planewave_basis_.is_gamma_only(),
611 psi,
612 device_fft_workspace_single_.data());
613
614 // Inverse FFT in FP32.
615 gpu_fft_grid_->inverse_raw_single(device_fft_workspace_single_.data());
616
617 // Multiply by FP32 potential on GPU.
619 1,
620 local_smooth_grid_points,
621 device_smooth_potential_single_.data(),
622 device_fft_workspace_single_.data());
623
624 // Forward FFT in FP32.
625 gpu_fft_grid_->forward_single(device_fft_workspace_single_.data());
626
627 // Gather FP32 → FP64 on GPU.
629 1,
630 number_of_plane_waves,
631 local_smooth_grid_points,
632 device_g_to_fft_mapping_.data(),
633 device_fft_workspace_single_.data(),
634 hpsi);
635 } else {
636 /**
637 * Fallback: no GPU FFT grid. Use planewave_basis FFTW (FP64)
638 * with FP32 rounding at scatter and potential multiply.
639 */
640 const FFTGrid& fft_wave = planewave_basis_.fft_wave();
641 Complex* fft_buffer = fft_wave.input_buffer();
642
643 // Scatter with FP32 rounding.
644 std::memset(fft_buffer, 0, local_smooth_grid_points * sizeof(Complex));
645 for (int g_index = 0; g_index < number_of_plane_waves; ++g_index) {
646 int fft_index = g_to_fft_mapping_[g_index];
647 if (fft_index >= 0) {
648 float real_part = static_cast<float>(psi[g_index].real());
649 float imag_part = static_cast<float>(psi[g_index].imag());
650 fft_buffer[fft_index] = Complex(real_part, imag_part);
651 }
652 }
653 // Gamma-only: fill -G with conj(ψ(G)) with FP32 rounding.
654 if (planewave_basis_.is_gamma_only()) {
655 for (int g_index = 1; g_index < number_of_plane_waves; ++g_index) {
656 int fft_minus = minus_g_to_fft_mapping_[g_index];
657 float real_part = static_cast<float>(psi[g_index].real());
658 float imag_part = static_cast<float>(psi[g_index].imag());
659 fft_buffer[fft_minus] = Complex(real_part, -imag_part);
660 }
661 }
662
663 // Inverse FFT (FFTW FP64).
664 fft_wave.execute_inverse_raw_on_buffers();
665 Complex* fft_output = fft_wave.output_buffer();
666
667 // Multiply by potential in FP32 precision.
668 for (int grid_index = 0; grid_index < local_smooth_grid_points; ++grid_index) {
669 float potential_value = device_smooth_potential_single_.data()[grid_index];
670 float real_result =
671 static_cast<float>(fft_output[grid_index].real()) * potential_value;
672 float imag_result =
673 static_cast<float>(fft_output[grid_index].imag()) * potential_value;
674 fft_output[grid_index] = Complex(real_result, imag_result);
675 }
676
677 // Forward FFT (FFTW FP64).
678 fft_wave.prepare_forward_from_output();
679 fft_wave.execute_forward_on_buffers();
680 fft_output = fft_wave.output_buffer();
681
682 // Gather back to FP64.
683 for (int g_index = 0; g_index < number_of_plane_waves; ++g_index) {
684 int fft_index = g_to_fft_mapping_[g_index];
685 if (fft_index >= 0) {
686 hpsi[g_index] += fft_output[fft_index];
687 }
688 }
689 }
690 }
691 }
692
693 /**
694 * Apply nonlocal KB projectors via 3× ZGEMM on GPU BLAS.
695 *
696 * Step 1: overlap = β† · ψ (projector overlap)
697 * Step 2: scaled = D · overlap (D-matrix application)
698 * Step 3: hψ += β · scaled (accumulate into H|ψ⟩)
699 */
700 void apply_nonlocal_projectors_double(const Complex* input_wavefunctions,
701 Complex* output_hamiltonian_wavefunctions,
702 int number_of_plane_waves,
703 int number_of_vectors) {
704
705 int leading_dimension = std::max(number_of_plane_waves, 1);
706 int total_projectors = current_total_projectors_;
707
708 // Allocate GPU buffers for intermediate results.
709 execution::gpu::device::GPUDeviceBuffer<Complex> overlap_buffer(
710 total_projectors * number_of_vectors, device_backend_);
711 execution::gpu::device::GPUDeviceBuffer<Complex> scaled_overlap_buffer(
712 total_projectors * number_of_vectors, device_backend_);
713
714 Complex alpha_one(1.0, 0.0);
715 Complex beta_zero(0.0, 0.0);
716 Complex beta_one(1.0, 0.0);
717
718 // Step 1: overlap = β† · ψ
719 if (planewave_basis_.is_gamma_only()) {
720 /**
721 * Gamma-only: use DGEMM with α=2 on interleaved real/imag parts.
722 * The wavefunction only stores +G half. The full overlap is:
723 * ⟨β|ψ⟩ = 2·Re(Σ_{G>0} β*(G)·ψ(G)) + Re(β*(0)·ψ(0))
724 * DGEMM on interleaved data gives 2·Re(β†·ψ), then subtract
725 * the double-counted G=0 contribution.
726 */
727 int double_number_of_plane_waves = 2 * number_of_plane_waves;
728 int double_leading_dimension = 2 * leading_dimension;
729
730 // Use a real-valued temporary for the DGEMM output in unified memory.
731 execution::gpu::device::GPUDeviceBuffer<double> device_overlap_real(
732 static_cast<size_t>(total_projectors) * number_of_vectors, device_backend_);
733
734 const double* real_projectors =
735 reinterpret_cast<const double*>(device_projector_matrix_.data());
736 const double* real_wavefunctions = reinterpret_cast<const double*>(input_wavefunctions);
737
738 gpu_blas_.dgemm('T',
739 'N',
740 total_projectors,
741 number_of_vectors,
742 double_number_of_plane_waves,
743 2.0,
744 real_projectors,
745 double_leading_dimension,
746 real_wavefunctions,
747 double_leading_dimension,
748 0.0,
749 device_overlap_real.data(),
750 total_projectors);
751
752 device_backend_->synchronize();
753
754 /**
755 * G=0 correction: subtract the double-counted G=0 term and copy to Complex.
756 * Matrix is row-major (nproj × npw), so element (proj, g=0) is at
757 * offset proj * npw in the flat array.
758 */
759 for (int vector_index = 0; vector_index < number_of_vectors; ++vector_index) {
760 const Complex* psi_g0 = &input_wavefunctions[vector_index * number_of_plane_waves];
761 for (int projector_index = 0; projector_index < total_projectors;
762 ++projector_index) {
763 Complex beta_g0 =
764 device_projector_matrix_.data()[projector_index * number_of_plane_waves];
765 int index = projector_index + vector_index * total_projectors;
766 double value =
767 device_overlap_real.data()[index] - (std::conj(beta_g0) * psi_g0[0]).real();
768 overlap_buffer.data()[index] = Complex(value, 0.0);
769 }
770 }
771 } else {
772 // Non-gamma: standard ZGEMM.
773 gpu_blas_.gemm('C',
774 'N',
775 total_projectors,
776 number_of_vectors,
777 number_of_plane_waves,
778 alpha_one,
779 device_projector_matrix_.data(),
780 leading_dimension,
781 input_wavefunctions,
782 leading_dimension,
783 beta_zero,
784 overlap_buffer.data(),
785 total_projectors);
786 }
787
788 // Step 2: scaled = D · overlap
789 gpu_blas_.gemm('N',
790 'N',
791 total_projectors,
792 number_of_vectors,
793 total_projectors,
794 alpha_one,
795 device_dense_d_matrix_.data(),
796 total_projectors,
797 overlap_buffer.data(),
798 total_projectors,
799 beta_zero,
800 scaled_overlap_buffer.data(),
801 total_projectors);
802
803 // Step 3: hψ += β · scaled
804 gpu_blas_.gemm('N',
805 'N',
806 number_of_plane_waves,
807 number_of_vectors,
808 total_projectors,
809 alpha_one,
810 device_projector_matrix_.data(),
811 leading_dimension,
812 scaled_overlap_buffer.data(),
813 total_projectors,
814 beta_one,
815 output_hamiltonian_wavefunctions,
816 leading_dimension);
817 }
818
819 // ─── KB projector application in FP32 ─────────────────────────────
820
821 /**
822 * Apply nonlocal KB projectors via 3× CGEMM on GPU BLAS (single precision).
823 */
824 void apply_nonlocal_projectors_single(const ComplexSingle* input_wavefunctions,
825 ComplexSingle* output_hamiltonian_wavefunctions,
826 int number_of_plane_waves,
827 int number_of_vectors) {
828
829 int leading_dimension = std::max(number_of_plane_waves, 1);
830 int total_projectors = current_total_projectors_;
831
832 // Allocate GPU buffers for intermediate results in FP32.
833 execution::gpu::device::GPUDeviceBuffer<ComplexSingle> overlap_buffer(
834 total_projectors * number_of_vectors, device_backend_);
835 execution::gpu::device::GPUDeviceBuffer<ComplexSingle> scaled_overlap_buffer(
836 total_projectors * number_of_vectors, device_backend_);
837
838 // Build single-precision D-matrix in a temporary buffer.
839 execution::gpu::device::GPUDeviceBuffer<ComplexSingle> device_d_matrix_single(
840 total_projectors * total_projectors, device_backend_);
841 precision::cast_double_to_single(total_projectors * total_projectors,
842 device_dense_d_matrix_.data(),
843 device_d_matrix_single.data());
844
845 ComplexSingle alpha_one(1.0f, 0.0f);
846 ComplexSingle beta_zero(0.0f, 0.0f);
847 ComplexSingle beta_one(1.0f, 0.0f);
848
849 // Step 1: overlap = β† · ψ (FP32)
850 gpu_blas_.gemm_single('C',
851 'N',
852 total_projectors,
853 number_of_vectors,
854 number_of_plane_waves,
855 alpha_one,
856 device_projector_matrix_single_.data(),
857 leading_dimension,
858 input_wavefunctions,
859 leading_dimension,
860 beta_zero,
861 overlap_buffer.data(),
862 total_projectors);
863
864 // Step 2: scaled = D · overlap (FP32)
865 gpu_blas_.gemm_single('N',
866 'N',
867 total_projectors,
868 number_of_vectors,
869 total_projectors,
870 alpha_one,
871 device_d_matrix_single.data(),
872 total_projectors,
873 overlap_buffer.data(),
874 total_projectors,
875 beta_zero,
876 scaled_overlap_buffer.data(),
877 total_projectors);
878
879 // Step 3: hψ += β · scaled (FP32)
880 gpu_blas_.gemm_single('N',
881 'N',
882 number_of_plane_waves,
883 number_of_vectors,
884 total_projectors,
885 alpha_one,
886 device_projector_matrix_single_.data(),
887 leading_dimension,
888 scaled_overlap_buffer.data(),
889 total_projectors,
890 beta_one,
891 output_hamiltonian_wavefunctions,
892 leading_dimension);
893 }
894
895 // ─── D-matrix construction ────────────────────────────────────────
896
897 /**
898 * Build and cache the dense block-diagonal D-matrix on GPU.
899 */
900 void build_dense_d_matrix(int total_projectors) {
901 cached_dense_d_matrix_.assign(static_cast<size_t>(total_projectors) * total_projectors,
902 Complex(0.0, 0.0));
903
904 int starting_projector_index = 0;
905 for (int atom_index = 0; atom_index < crystal_structure_.number_of_atoms(); ++atom_index) {
906 int species_index = crystal_structure_.species_type_index(atom_index);
907 int projectors_for_atom = pseudopotential_quantum_system_.pseudopotential_basis()
908 .number_of_projector_channels(species_index);
909
910 for (int projector_a = 0; projector_a < projectors_for_atom; ++projector_a) {
911 for (int projector_b = 0; projector_b < projectors_for_atom; ++projector_b) {
912 Real d_value = pseudopotential_quantum_system_.d_matrix_element(
913 atom_index, species_index, projector_a, projector_b, spin_channel_);
914 if (std::abs(d_value) > math::tolerances::d_matrix_element_threshold) {
915 int row = starting_projector_index + projector_a;
916 int col = starting_projector_index + projector_b;
917 cached_dense_d_matrix_[static_cast<size_t>(row) +
918 static_cast<size_t>(col) * total_projectors] =
919 Complex(d_value, 0.0);
920 }
921 }
922 }
923 starting_projector_index += projectors_for_atom;
924 }
925
926 // Copy to GPU.
927 device_dense_d_matrix_ = execution::gpu::device::GPUDeviceBuffer<Complex>(
928 total_projectors * total_projectors, device_backend_);
929 std::memcpy(device_dense_d_matrix_.data(),
930 cached_dense_d_matrix_.data(),
931 total_projectors * total_projectors * sizeof(Complex));
932 }
933
934 // ─── Smooth potential computation (CPU-side) ──────────────────────
935
936 /**
937 * Restrict the effective potential from the dense grid to the smooth grid.
938 *
939 * Uses the shared utility for core FFT + G-space truncation.
940 * No MPI reduce — GPU applicator is single-process only (asserted).
941 */
942 std::vector<Real>
943 compute_smooth_grid_potential(const std::vector<Real>& dense_real_space_potential,
944 const PlanewaveBasis& planewave_basis) const {
945 /**
946 * GPU path currently supports single-process only. Multi-rank GPU requires
947 * NCCL or MPI-aware unified memory, which is not yet implemented.
948 */
949 if (planewave_basis.fft_rho().local_grid_points() !=
950 planewave_basis.fft_rho().total_grid_points()) {
951 throw std::runtime_error(
952 "The GPU Hamiltonian applicator does not support band-distributed MPI: this rank "
953 "holds " +
954 std::to_string(planewave_basis.fft_rho().local_grid_points()) + " of " +
955 std::to_string(planewave_basis.fft_rho().total_grid_points()) +
956 " density grid points, and the device path assumes the whole grid. Run one rank "
957 "per k-point pool, or use the CPU path. This was an assert, which a release "
958 "build compiles out.");
959 }
960
961 // Core: dense FFT -> G-space truncation -> smooth V(G)
962 auto full_smooth_potential_g =
963 compute_smooth_grid_potential_from_dense(dense_real_space_potential, planewave_basis);
964
965 // Inverse FFT to real space (no MPI reduce needed — single process)
966 return inverse_fft_smooth_potential(full_smooth_potential_g, planewave_basis);
967 }
968
969 // ─── G-vector to FFT mapping ──────────────────────────────────────
970
971 /**
972 * Pre-compute G-vector to smooth FFT grid index mapping.
973 *
974 * Uses the shared core mapping. GPU path does not support gamma-only.
975 */
976 void precompute_g_to_fft_mapping(int number_of_plane_waves,
977 const std::vector<int>& g_vector_indices,
978 const PlanewaveBasis& planewave_basis) {
979 // Core G -> smooth FFT index mapping (shared with serial applicator)
981 number_of_plane_waves, g_vector_indices, planewave_basis, g_to_fft_mapping_);
982
983 // For gamma-only: pre-compute the -G FFT grid index for each G-vector.
984 if (planewave_basis.is_gamma_only()) {
985 minus_g_to_fft_mapping_.resize(number_of_plane_waves);
986 for (int g_vector_index = 0; g_vector_index < number_of_plane_waves; g_vector_index++) {
987 const auto& g_vector =
988 planewave_basis.g_vectors()[g_vector_indices[g_vector_index]];
989 int miller_index_1 = static_cast<int>(std::round(g_vector.miller.x));
990 int miller_index_2 = static_cast<int>(std::round(g_vector.miller.y));
991 int miller_index_3 = static_cast<int>(std::round(g_vector.miller.z));
992 minus_g_to_fft_mapping_[g_vector_index] =
993 planewave_basis.fft_wave().g_vector_to_fft_grid_index(
994 -miller_index_1, -miller_index_2, -miller_index_3);
995 }
996 } else {
997 minus_g_to_fft_mapping_.clear();
998 }
999 }
1000
1001 // ─── Run-constant context ─────────────────────────────────────────
1002
1003 const Crystal& crystal_structure_;
1004 const PlanewaveBasis& planewave_basis_;
1005 const KPointsGrid& kpoint_grid_;
1006 const NormConservingPlanewaveKohnShamQuantumSystem& pseudopotential_quantum_system_;
1007 execution::gpu::blas::GPUBLAS& gpu_blas_;
1008 gpu::GPUFFTGrid* gpu_fft_grid_;
1009 std::shared_ptr<execution::gpu::device::GPUDeviceBackend> device_backend_;
1010 bool use_mixed_precision_ = false;
1011
1012 // ─── Per-k-point state ────────────────────────────────────────────
1013
1014 int current_kpoint_index_ = 0;
1015 int current_number_of_plane_waves_ = 0;
1016 int current_total_projectors_ = 0;
1017
1018 // ─── CPU-side cached data ─────────────────────────────────────────
1019
1020 std::vector<Real> cached_smooth_potential_;
1021 std::vector<Real> cached_scaled_potential_;
1022 bool smooth_potential_cache_valid_ = false;
1023 std::vector<Complex> cached_dense_d_matrix_;
1024 Matrix<Complex> cached_projector_matrix_;
1025
1026 // ─── FFT mappings ─────────────────────────────────────────────────
1027
1028 std::vector<int> g_to_fft_mapping_;
1029 std::vector<int> minus_g_to_fft_mapping_;
1030
1031 // ─── GPU device buffers ───────────────────────────────────────────
1032
1033 // Smooth potential (FP64 and FP32).
1034 execution::gpu::device::GPUDeviceBuffer<Real> device_smooth_potential_;
1035 execution::gpu::device::GPUDeviceBuffer<RealSingle> device_smooth_potential_single_;
1036
1037 // G-to-FFT index mapping.
1038 execution::gpu::device::GPUDeviceBuffer<int> device_g_to_fft_mapping_;
1039 execution::gpu::device::GPUDeviceBuffer<int> device_minus_g_to_fft_mapping_;
1040
1041 // Kinetic energy factors (FP64 and FP32).
1042 execution::gpu::device::GPUDeviceBuffer<Real> device_kinetic_energy_squared_;
1043 execution::gpu::device::GPUDeviceBuffer<RealSingle> device_kinetic_energy_squared_single_;
1044
1045 // KB projector matrix (FP64 and FP32).
1046 execution::gpu::device::GPUDeviceBuffer<Complex> device_projector_matrix_;
1047 execution::gpu::device::GPUDeviceBuffer<ComplexSingle> device_projector_matrix_single_;
1048
1049 // Dense block-diagonal D-matrix.
1050 execution::gpu::device::GPUDeviceBuffer<Complex> device_dense_d_matrix_;
1051
1052 // FFT workspace (FP64 and FP32).
1053 execution::gpu::device::GPUDeviceBuffer<Complex> device_fft_workspace_;
1054 execution::gpu::device::GPUDeviceBuffer<ComplexSingle> device_fft_workspace_single_;
1055
1056 // Batched FFT workspace: number_of_vectors × local_smooth_grid_points contiguous grids.
1057 execution::gpu::device::GPUDeviceBuffer<Complex> device_fft_batch_workspace_;
1058 execution::gpu::device::GPUDeviceBuffer<ComplexSingle> device_fft_batch_workspace_single_;
1059};
1060
1061} // namespace q3
int species_type_index(int atom_index) const
Definition crystal.hpp:273
int number_of_atoms() const
Definition crystal.hpp:209
virtual Complex * input_buffer() const =0
virtual int total_grid_points() const =0
virtual void init(int dimension_1, int dimension_2, int dimension_3, bool use_measured_planning=false)=0
virtual int local_grid_points() const =0
void set_cached_projectors(int, const q3::Matrix< Complex > *) override
Definition gpu.hpp:437
void apply_batch(const Complex *input_wavefunctions, Complex *output_hamiltonian_wavefunctions, int number_of_vectors) override
Definition gpu.hpp:258
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 gpu.hpp:167
GPUPlanewaveHamiltonianApplicator(const Crystal &crystal_structure, const PlanewaveBasis &planewave_basis, const KPointsGrid &kpoint_grid, const NormConservingPlanewaveKohnShamQuantumSystem &pseudopotential_quantum_system, execution::gpu::blas::GPUBLAS &gpu_blas, gpu::GPUFFTGrid *gpu_fft_grid, std::shared_ptr< execution::gpu::device::GPUDeviceBackend > device_backend)
Definition gpu.hpp:66
bool is_gamma_only_current() const override
Definition gpu.hpp:430
void update_potential(const std::vector< Real > &real_space_potential) override
Definition gpu.hpp:129
const std::vector< int > & g_to_fft_mapping() const
Definition gpu.hpp:413
void invalidate_smooth_potential_cache() override
Definition gpu.hpp:391
void apply_batch_single_precision(const ComplexSingle *input_wavefunctions, ComplexSingle *output_hamiltonian_wavefunctions, int number_of_vectors)
Definition gpu.hpp:313
void set_use_mixed_precision(bool use_mixed_precision)
Definition gpu.hpp:115
const std::vector< Real > & cached_scaled_potential() const
Definition gpu.hpp:406
bool is_smooth_potential_cache_valid() const override
Definition gpu.hpp:399
const std::vector< int > & minus_g_to_fft_mapping() const
Definition gpu.hpp:420
const q3::Matrix< Complex > * get_cached_projectors(int) const override
Definition gpu.hpp:444
size_t rows() const
Definition matrix.hpp:62
bool empty() const
Definition matrix.hpp:90
T * data()
Returns the underlying flat data pointer.
Definition matrix.hpp:75
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 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
virtual void gemm(char transpose_a, char transpose_b, int m, int n, int k, Complex alpha, const Complex *matrix_a, int leading_dimension_a, const Complex *matrix_b, int leading_dimension_b, Complex beta, Complex *matrix_c, int leading_dimension_c) const =0
virtual void gemm_single(char transpose_a, char transpose_b, int m, int n, int k, ComplexSingle alpha, const ComplexSingle *matrix_a, int leading_dimension_a, const ComplexSingle *matrix_b, int leading_dimension_b, ComplexSingle beta, ComplexSingle *matrix_c, int leading_dimension_c) const =0
virtual void dgemm(char transpose_a, char transpose_b, int m, int n, int k, Real alpha, const Real *matrix_a, int leading_dimension_a, const Real *matrix_b, int leading_dimension_b, Real beta, Real *matrix_c, int leading_dimension_c) const =0
virtual void inverse_raw_single_batch(ComplexSingle *data, int batch_size) const
Definition init.hpp:94
virtual void forward_single(ComplexSingle *data) const =0
virtual void inverse_raw_single(ComplexSingle *data) const =0
virtual void forward_transform_batch(Complex *data, int batch_size) const
Definition init.hpp:136
virtual void forward_single_batch(ComplexSingle *data, int batch_size) const
Definition init.hpp:78
virtual void inverse_transform_batch(Complex *data, int batch_size) const
Definition init.hpp:152
void launch_apply_kinetic_energy_single(int number_of_vectors, int number_of_plane_waves, const float *kinetic_energy, const std::complex< float > *psi, std::complex< float > *hpsi)
void launch_gather_fft_grid_to_g_single(int number_of_vectors, int number_of_plane_waves, int local_smooth_grid_points, const int *g_to_fft_mapping, const std::complex< float > *fft_workspace, std::complex< float > *hpsi)
void launch_apply_kinetic_energy(int number_of_vectors, int number_of_plane_waves, const double *kinetic_energy, const std::complex< double > *psi, std::complex< double > *hpsi)
void launch_multiply_potential(int number_of_vectors, int local_smooth_grid_points, const double *potential, std::complex< double > *fft_workspace)
void launch_multiply_potential_single(int number_of_vectors, int local_smooth_grid_points, const float *potential, std::complex< float > *fft_workspace)
void launch_gather_fft_grid_to_g_mixed(int number_of_vectors, int number_of_plane_waves, int local_smooth_grid_points, const int *g_to_fft_mapping, const std::complex< float > *fft_workspace, std::complex< double > *hpsi)
void launch_scatter_g_to_fft_grid(int number_of_vectors, int number_of_plane_waves, int local_smooth_grid_points, const int *g_to_fft_mapping, const int *minus_g_to_fft_mapping, bool is_gamma_only, const std::complex< double > *psi, std::complex< double > *fft_workspace)
void launch_gather_fft_grid_to_g(int number_of_vectors, int number_of_plane_waves, int local_smooth_grid_points, const int *g_to_fft_mapping, const std::complex< double > *fft_workspace, std::complex< double > *hpsi)
void launch_scatter_g_to_fft_grid_mixed(int number_of_vectors, int number_of_plane_waves, int local_smooth_grid_points, const int *g_to_fft_mapping, const int *minus_g_to_fft_mapping, bool is_gamma_only, const std::complex< double > *psi, std::complex< float > *fft_workspace)
void launch_scatter_g_to_fft_grid_single(int number_of_vectors, int number_of_plane_waves, int local_smooth_grid_points, const int *g_to_fft_mapping, const int *minus_g_to_fft_mapping, bool is_gamma_only, const std::complex< float > *psi, std::complex< float > *fft_workspace)
void info(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:183
constexpr double d_matrix_element_threshold
void cast_double_to_single(int count, const Complex *source, ComplexSingle *destination)
void cast_real_double_to_single(int count, const Real *source, RealSingle *destination)
double Real
std::vector< Complex > compute_smooth_grid_potential_from_dense(const std::vector< Real > &dense_real_space_potential, const PlanewaveBasis &planewave_basis)
std::complex< float > ComplexSingle
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)
std::complex< double > Complex