Line data Source code
1 : !--------------------------------------------------------------------------------------------------!
2 : ! CP2K: A general program to perform molecular dynamics simulations !
3 : ! Copyright 2000-2026 CP2K developers group <https://cp2k.org> !
4 : ! !
5 : ! SPDX-License-Identifier: GPL-2.0-or-later !
6 : !--------------------------------------------------------------------------------------------------!
7 :
8 : ! **************************************************************************************************
9 : !> \brief GW using RI-RS Approximation for molecules
10 : !> \par History
11 : !> 04.2026 created [Ritaj Tyagi]
12 : ! **************************************************************************************************
13 :
14 : MODULE gw_non_periodic_ri_rs
15 : USE atomic_kind_types, ONLY: atomic_kind_type,&
16 : get_atomic_kind_set
17 : USE cell_types, ONLY: cell_type
18 : USE constants_operator, ONLY: operator_coulomb
19 : USE cp_blacs_env, ONLY: cp_blacs_env_create,&
20 : cp_blacs_env_release,&
21 : cp_blacs_env_type
22 : USE cp_dbcsr_api, ONLY: &
23 : dbcsr_add, dbcsr_binary_read, dbcsr_binary_write, dbcsr_copy, dbcsr_create, &
24 : dbcsr_deallocate_matrix, dbcsr_distribution_get, dbcsr_distribution_new, &
25 : dbcsr_distribution_release, dbcsr_distribution_type, dbcsr_filter, dbcsr_finalize, &
26 : dbcsr_get_block_p, dbcsr_get_data_size, dbcsr_get_info, dbcsr_get_occupation, &
27 : dbcsr_iterator_blocks_left, dbcsr_iterator_next_block, dbcsr_iterator_start, &
28 : dbcsr_iterator_stop, dbcsr_iterator_type, dbcsr_multiply, dbcsr_p_type, dbcsr_put_block, &
29 : dbcsr_release, dbcsr_scale, dbcsr_set, dbcsr_type, dbcsr_type_no_symmetry
30 : USE cp_dbcsr_contrib, ONLY: dbcsr_reserve_all_blocks
31 : USE cp_dbcsr_operations, ONLY: copy_dbcsr_to_fm,&
32 : copy_fm_to_dbcsr,&
33 : dbcsr_deallocate_matrix_set,&
34 : max_elements_per_block
35 : USE cp_fm_basic_linalg, ONLY: cp_fm_scale_and_add,&
36 : cp_fm_uplo_to_full
37 : USE cp_fm_cholesky, ONLY: cp_fm_cholesky_decompose,&
38 : cp_fm_cholesky_invert,&
39 : cp_fm_cholesky_solve
40 : USE cp_fm_diag, ONLY: cp_fm_geeig,&
41 : cp_fm_power
42 : USE cp_fm_struct, ONLY: cp_fm_struct_create,&
43 : cp_fm_struct_release,&
44 : cp_fm_struct_type
45 : USE cp_fm_types, ONLY: &
46 : cp_fm_create, cp_fm_get_diag, cp_fm_get_info, cp_fm_get_submatrix, cp_fm_release, &
47 : cp_fm_set_all, cp_fm_set_submatrix, cp_fm_to_fm, cp_fm_type
48 : USE cp_log_handling, ONLY: cp_get_default_logger,&
49 : cp_logger_type
50 : USE cp_output_handling, ONLY: cp_p_file,&
51 : cp_print_key_should_output
52 : USE gw_integrals, ONLY: build_3c_integral_block_ctx,&
53 : gw_3c_ctx_create,&
54 : gw_3c_ctx_release,&
55 : gw_3c_ctx_type,&
56 : gw_3c_ws_create,&
57 : gw_3c_ws_release,&
58 : gw_3c_ws_type
59 : USE gw_large_cell_gamma, ONLY: Fourier_transform_w_to_t,&
60 : G_occ_vir,&
61 : compute_fm_chi_Gamma_freq,&
62 : create_fm_W_MIC_time,&
63 : delete_unnecessary_files,&
64 : fill_fm_Sigma_c_Gamma_time,&
65 : fm_write,&
66 : multiply_fm_W_MIC_time_with_Minv_Gamma
67 : USE gw_optimize_ri_rs_grid, ONLY: optimize_ri_rs_grid
68 : USE gw_utils, ONLY: analyt_conti_and_print,&
69 : de_init_bs_env,&
70 : time_to_freq
71 : USE input_constants, ONLY: G0W0,&
72 : evGW0,&
73 : rtp_method_bse
74 : USE input_section_types, ONLY: section_vals_type
75 : USE kinds, ONLY: default_path_length,&
76 : default_string_length,&
77 : dp,&
78 : int_8,&
79 : max_line_length
80 : USE kpoint_coulomb_2c, ONLY: build_2c_coulomb_matrix_kp
81 : USE machine, ONLY: m_flush,&
82 : m_hostnm,&
83 : m_memory_details,&
84 : m_walltime
85 : USE message_passing, ONLY: mp_para_env_type
86 : USE mp2_ri_2c, ONLY: RI_2c_integral_mat
87 : USE parallel_gemm_api, ONLY: parallel_gemm
88 : USE particle_types, ONLY: particle_type
89 : USE physcon, ONLY: angstrom,&
90 : evolt
91 : USE post_scf_bandstructure_types, ONLY: post_scf_bandstructure_type,&
92 : rirs_grid_type
93 : USE post_scf_bandstructure_utils, ONLY: get_all_VBM_CBM_bandgaps
94 : USE qs_environment_types, ONLY: get_qs_env,&
95 : qs_environment_type
96 : USE qs_kind_types, ONLY: get_qs_kind,&
97 : qs_kind_type
98 : USE rirs_grid_utils, ONLY: evaluate_ao_on_points,&
99 : get_rirs_grid_filepath,&
100 : initialize_rirs_grid,&
101 : read_rirs_grid_file
102 : USE util, ONLY: sort
103 : #include "./base/base_uses.f90"
104 :
105 : IMPLICIT NONE
106 :
107 : PRIVATE
108 :
109 : CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'gw_non_periodic_ri_rs'
110 :
111 : PUBLIC :: gw_calc_non_periodic_ri_rs, ri_rs_grid_assembler, &
112 : get_basis_offsets, precompute_ri_rs_radii, solve_D_lp_distributed, &
113 : atomic_basis_at_grid_point, compute_coeff_Z_lP, &
114 : reserve_blocks_within_radius
115 :
116 : CONTAINS
117 :
118 : ! **************************************************************************************************
119 : !> \brief GW calculation using RI-RS formalism for molecules
120 : !> \param qs_env ...
121 : !> \param bs_env ...
122 : ! **************************************************************************************************
123 36 : SUBROUTINE gw_calc_non_periodic_ri_rs(qs_env, bs_env)
124 :
125 : TYPE(qs_environment_type), POINTER :: qs_env
126 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
127 :
128 : CHARACTER(LEN=*), PARAMETER :: routineN = 'gw_calc_non_periodic_ri_rs'
129 :
130 : INTEGER :: handle
131 36 : TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:) :: fm_Sigma_x_Gamma, fm_W_time
132 :
133 36 : CALL timeset(routineN, handle)
134 :
135 : ! ==============================================================================
136 : ! 0. Precompute AO and RI radii
137 : ! Per-atom cutoff radii from the most diffuse Gaussian primitives of
138 : ! the AO ("ORB") and RI auxiliary ("RI_AUX") basis sets:
139 : ! α_min,ao = min { ζ_ao | ζ_ao > 10⁻³ }, α_min,ri analogous
140 : ! r_ao = sqrt( -ln(ε) / α_min,ao ) (radius_ao_per_atom)
141 : ! r_ri = sqrt( -ln(ε) / α_min,ri ) (radius_ri_per_atom)
142 : ! ==============================================================================
143 36 : CALL precompute_ri_rs_radii(qs_env, bs_env)
144 :
145 : ! ==============================================================================
146 : ! 1. Grid generation for RI-RS
147 : ! Modified Lebedev atomic grids (Duchemin & Blase), one per atom,
148 : ! concatenated into a flat global list: r_l = R_A + r_l^(A)
149 : ! ==============================================================================
150 36 : CALL ri_rs_grid_assembler(qs_env, bs_env, bs_env%ri_rs%grid_points)
151 :
152 : ! ==============================================================================
153 : ! 2a. Atomic basis evaluation on the grid (grid x AO matrix)
154 : ! Φ_μl = Φ_μ(r_l) (mat_phi_mu_l)
155 : ! ==============================================================================
156 : CALL atomic_basis_at_grid_point(qs_env, bs_env, bs_env%ri_rs%grid_points, &
157 36 : bs_env%ri_rs%mat_phi_mu_l)
158 :
159 : ! ==============================================================================
160 : ! 2b. Print the memory estimate for the RI-RS calculation
161 : ! ==============================================================================
162 36 : CALL print_ri_rs_memory_estimate(qs_env, bs_env)
163 :
164 : ! ==============================================================================
165 : ! 3. RI-RS fitting coefficients Z_lP (grid x RI matrix)
166 : ! Per-atom regularized solve, restricted to grid points r_l within a
167 : ! cutoff distance of atom P:
168 : ! a. D_ll' = [ Σ_μ Φ_μ(r_l) Φ_μ(r_l') ]²
169 : ! b. D_lP = Σ_μν Φ_μ(r_l) Φ_ν(r_l) (μν|P)
170 : ! c. Jacobi conditioning with d_l = 1/sqrt(D_ll):
171 : ! D'_ll' = d_l D_ll' d_l' + λδ_ll' , D'_lP = d_l D_lP
172 : ! d. Solve Σ_l' D'_ll' Z'_l'P = D'_lP
173 : ! e. Rescale Z_lP = d_l Z'_lP (mat_Z_lP)
174 : ! ==============================================================================
175 : CALL compute_coeff_Z_lP(qs_env, bs_env, bs_env%ri_rs%grid_points, &
176 36 : bs_env%ri_rs%mat_phi_mu_l, bs_env%ri_rs%mat_Z_lP)
177 :
178 : ! flag the RI-RS grid as built so a subsequent RT-BSE run reuses Z_lP
179 : ! instead of rebuilding it
180 36 : bs_env%ri_rs%grid_built = .TRUE.
181 :
182 : ! ==============================================================================
183 : ! 4. Polarizability matrix χ on the imaginary-time grid
184 : ! G^occ_µλ(i|τ|) = Σ_n^occ C_µn e^(-|(ϵ_n-ϵ_F)τ|) C_λn
185 : ! G^vir_µλ(i|τ|) = Σ_n^vir C_µn e^(-|(ϵ_n-ϵ_F)τ|) C_λn
186 : ! G^occ_ll'(i|τ|) = Σ_µν Φ_µ(r_l) G^occ_µν Φ_ν(r_l') (G^vir analogous)
187 : ! χ_ll'(iτ) = G^occ_ll'(i|τ|) ∘ G^vir_ll'(i|τ|) (element-wise)
188 : ! χ_PQ(iτ) = Σ_ll' Z_lP χ_ll'(iτ) Z_l'Q
189 : ! ==============================================================================
190 : CALL get_mat_chi_Gamma_tau(bs_env, bs_env%mat_chi_Gamma_tau, &
191 36 : bs_env%ri_rs%mat_phi_mu_l, bs_env%ri_rs%mat_Z_lP)
192 :
193 : ! ==============================================================================
194 : ! 5. Screened Coulomb interaction W (RI basis)
195 : ! χ_PQ(iτ) -> χ_PQ(iω) -> ε_PQ(iω) -> W_PQ(iω) -> W_PQ(iτ)
196 : ! ==============================================================================
197 36 : CALL compute_W(bs_env, qs_env, bs_env%mat_chi_Gamma_tau, fm_W_time)
198 :
199 : ! ==============================================================================
200 : ! 6. Exact-exchange self-energy Σ^x
201 : ! D_µν = Σ_n^occ C_µn C_νn (density matrix)
202 : ! D_ll' = Σ_µν Φ_µ(r_l) D_µν Φ_ν(r_l')
203 : ! V^tr_ll' = Σ_PQ Z_lP V^tr_PQ Z_l'Q (truncated Coulomb)
204 : ! Σ^x_ll' = D_ll' ∘ V^tr_ll'
205 : ! Σ^x_λσ(k=0) = -Σ_ll' Φ_λ(r_l) Σ^x_ll' Φ_σ(r_l')
206 : ! ==============================================================================
207 : CALL compute_Sigma_x(bs_env, qs_env, bs_env%ri_rs%mat_phi_mu_l, &
208 36 : bs_env%ri_rs%mat_Z_lP, fm_Sigma_x_Gamma)
209 :
210 : ! ==============================================================================
211 : ! 7. Correlation self-energy Σ^c and quasiparticle energies, iterated
212 : ! until eigenvalue self-consistency if the &EVGW0 section is given and
213 : ! done in a single pass for G0W0.
214 : !
215 : ! (a) W_ll'(iτ) = Σ_PQ Z_lP W^MIC_PQ(iτ) Z_l'Q
216 : ! Σ^c_ll'(iτ) = -G^occ_ll'(i|τ|) ∘ W_ll'(iτ), τ < 0
217 : ! Σ^c_ll'(iτ) = G^vir_ll'(i|τ|) ∘ W_ll'(iτ), τ > 0
218 : ! Σ^c_λσ(iτ) = Σ_ll' Φ_λ(r_l) Σ^c_ll'(iτ) Φ_σ(r_l')
219 : ! (b) Σ^c_λσ(iτ) -> Σ^c_nn(ϵ)
220 : ! ϵ_n^GW,(i) = ϵ_n^DFT + Σ^c_nn[G^(i-1),W](ϵ_n^GW,(i)) + Σ^x_nn - v^xc_nn
221 : ! ==============================================================================
222 36 : CALL compute_Sigma_c_and_QP_energies(bs_env, fm_W_time, fm_Sigma_x_Gamma)
223 :
224 36 : CALL de_init_bs_env(qs_env, bs_env)
225 :
226 36 : CALL timestop(handle)
227 :
228 36 : END SUBROUTINE gw_calc_non_periodic_ri_rs
229 :
230 : ! **************************************************************************************************
231 : !> \brief Correlation self-energy and quasiparticle energies, iterated to eigenvalue
232 : !> self-consistency in G (evGW0).
233 : !>
234 : !> For G0W0 this runs once with G^(0) built from the DFT eigenvalues. For evGW0 the
235 : !> Green's function is rebuilt from the quasiparticle energies of all states until the
236 : !> quasiparticle HOMO, LUMO and HOMO-LUMO gap change by less than EPS_ITER between two
237 : !> cycles, or MAX_ITER cycles are spent. W, Σ^x and everything computed before this
238 : !> routine stay frozen: they either do not depend on the eigenvalues at all (Σ^x is G
239 : !> at τ = 0, a pure density matrix) or are held fixed by construction in GW0. The
240 : !> imaginary-time grid is fixed as well, since W(iτ) lives on it.
241 : !>
242 : !> \param bs_env ...
243 : !> \param fm_W_time ...
244 : !> \param fm_Sigma_x_Gamma ...
245 : ! **************************************************************************************************
246 36 : SUBROUTINE compute_Sigma_c_and_QP_energies(bs_env, fm_W_time, fm_Sigma_x_Gamma)
247 :
248 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
249 : TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:) :: fm_W_time, fm_Sigma_x_Gamma
250 :
251 : CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_Sigma_c_and_QP_energies'
252 :
253 : INTEGER :: handle, i_iter, n_iter
254 : LOGICAL :: converged
255 36 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :) :: eigenval_scf_Gamma_dft
256 : REAL(KIND=dp), DIMENSION(2) :: e_fermi_dft
257 : REAL(KIND=dp), DIMENSION(3, 2) :: band_prev
258 36 : TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:, :, :) :: fm_Sigma_c_Gamma_time
259 :
260 36 : CALL timeset(routineN, handle)
261 :
262 : converged = .FALSE.
263 :
264 36 : CALL init_evGW0(bs_env, n_iter, band_prev, eigenval_scf_Gamma_dft, e_fermi_dft)
265 :
266 : ! evGW0 self-consistency loop; for G0W0, loop is terminated after one iteration
267 50 : DO i_iter = 1, n_iter
268 :
269 50 : bs_env%ri_rs%evgw0_i_iter = i_iter
270 :
271 : ! W_ll'(iτ) = Σ_PQ Z_lP W^MIC_PQ(iτ) Z_l'Q
272 : ! Σ^c_ll'(iτ) = -G^occ_ll'(i|τ|) ∘ W_ll'(iτ), τ < 0
273 : ! Σ^c_ll'(iτ) = G^vir_ll'(i|τ|) ∘ W_ll'(iτ), τ > 0
274 : ! Σ^c_λσ(iτ) = Σ_ll' Φ_λ(r_l) Σ^c_ll'(iτ) Φ_σ(r_l')
275 : CALL compute_Sigma_c(bs_env, fm_W_time, bs_env%ri_rs%mat_phi_mu_l, &
276 50 : bs_env%ri_rs%mat_Z_lP, fm_Sigma_c_Gamma_time)
277 :
278 : ! Σ^c_λσ(iτ) -> Σ^c_nn(ϵ)
279 : ! ϵ_n^GW,(i) = ϵ_n^DFT + Σ^c_nn[G^(i-1),W](ϵ_n^GW,(i)) + Σ^x_nn - v^xc_nn
280 50 : CALL compute_QP_energies(bs_env, fm_Sigma_x_Gamma, fm_Sigma_c_Gamma_time)
281 :
282 50 : IF (bs_env%gw_flavour == G0W0) EXIT
283 :
284 18 : CALL print_evGW0_band_edges(bs_env, band_prev, i_iter, n_iter, converged)
285 :
286 18 : IF (i_iter == n_iter .OR. converged) EXIT
287 :
288 : ! eigenvalues ϵ_n to be updated in G:
289 : ! G^occ_µλ(i|τ|) = Σ_n^occ C_µn e^(-|(ϵ_n-ϵ_F)τ|) C_λn
290 : ! G^vir_µλ(i|τ|) = Σ_n^vir C_µn e^(-|(ϵ_n-ϵ_F)τ|) C_λn
291 68 : CALL update_eigenvalues_G(bs_env)
292 :
293 : END DO
294 :
295 36 : CALL cp_fm_release(fm_W_time)
296 :
297 36 : CALL reset_and_clean_bs_env(bs_env, eigenval_scf_Gamma_dft, e_fermi_dft, fm_Sigma_x_Gamma)
298 :
299 36 : CALL delete_unnecessary_files(bs_env)
300 :
301 36 : CALL timestop(handle)
302 :
303 36 : END SUBROUTINE compute_Sigma_c_and_QP_energies
304 :
305 : ! **************************************************************************************************
306 : !> \brief Sets up the evGW0 eigenvalue self-consistency loop: the cycle count, the eigenvalues
307 : !> the first Green's function is built from, and the DFT reference that the loop
308 : !> overwrites. A G0W0 run reduces to a single cycle and needs none of it.
309 : !> \param bs_env ...
310 : !> \param n_iter ...
311 : !> \param band_prev ...
312 : !> \param eigenval_scf_Gamma_dft ...
313 : !> \param e_fermi_dft ...
314 : ! **************************************************************************************************
315 36 : SUBROUTINE init_evGW0(bs_env, n_iter, band_prev, eigenval_scf_Gamma_dft, e_fermi_dft)
316 :
317 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
318 : INTEGER, INTENT(OUT) :: n_iter
319 : REAL(KIND=dp), DIMENSION(3, 2), INTENT(OUT) :: band_prev
320 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
321 : INTENT(OUT) :: eigenval_scf_Gamma_dft
322 : REAL(KIND=dp), DIMENSION(2), INTENT(OUT) :: e_fermi_dft
323 :
324 : CHARACTER(LEN=*), PARAMETER :: routineN = 'init_evGW0'
325 :
326 : INTEGER :: handle
327 :
328 36 : CALL timeset(routineN, handle)
329 :
330 36 : n_iter = 1
331 36 : band_prev(:, :) = 0.0_dp
332 36 : e_fermi_dft(:) = 0.0_dp
333 :
334 36 : IF (bs_env%gw_flavour == evGW0) THEN
335 4 : n_iter = bs_env%ri_rs%evgw0_iter
336 :
337 : ! eigenvalues currently in G; the first cycle starts is a plain G0W0 step
338 70 : bs_env%eigenval_evGW0(:, :, :) = bs_env%eigenval_scf(:, :, :)
339 : ! the loop overwrites these; post-GW printing expects the DFT values back
340 76 : ALLOCATE (eigenval_scf_Gamma_dft, SOURCE=bs_env%eigenval_scf_Gamma)
341 12 : e_fermi_dft(:) = bs_env%e_fermi(:)
342 : END IF
343 :
344 36 : CALL timestop(handle)
345 :
346 36 : END SUBROUTINE init_evGW0
347 :
348 : ! **************************************************************************************************
349 : !> \brief Feeds the quasiparticle energies of the current evGW0 cycle back into the Green's
350 : !> function used by the next one, and re-centres the Fermi level between the new
351 : !> quasiparticle HOMO and LUMO.
352 : !> \param bs_env ...
353 : ! **************************************************************************************************
354 14 : SUBROUTINE update_eigenvalues_G(bs_env)
355 :
356 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
357 :
358 : CHARACTER(LEN=*), PARAMETER :: routineN = 'update_eigenvalues_G'
359 :
360 : INTEGER :: handle, i_mo, ispin
361 :
362 14 : CALL timeset(routineN, handle)
363 :
364 : ! record this cycle's evGW0 result; it is also what the next Green's function is built from
365 230 : bs_env%eigenval_evGW0(:, :, :) = bs_env%eigenval_GW(:, :, :)
366 :
367 34 : DO ispin = 1, bs_env%n_spin
368 : ! Update all physical states; exclude linear-dependency placeholders.
369 196 : DO i_mo = 1, bs_env%n_mo_retained
370 196 : bs_env%eigenval_scf_Gamma(i_mo, ispin) = bs_env%eigenval_GW(i_mo, 1, ispin)
371 : END DO
372 : bs_env%e_fermi(ispin) = &
373 : 0.5_dp*(bs_env%eigenval_GW(bs_env%n_occ(ispin), 1, ispin) + &
374 34 : bs_env%eigenval_GW(bs_env%n_occ(ispin) + 1, 1, ispin))
375 : END DO
376 :
377 14 : CALL timestop(handle)
378 :
379 14 : END SUBROUTINE update_eigenvalues_G
380 :
381 : ! **************************************************************************************************
382 : !> \brief Restores the DFT reference that the evGW0 loop overwrote and cleanup
383 : !> \param bs_env ...
384 : !> \param eigenval_scf_Gamma_dft ...
385 : !> \param e_fermi_dft ...
386 : !> \param fm_Sigma_x_Gamma ..
387 : ! **************************************************************************************************
388 36 : SUBROUTINE reset_and_clean_bs_env(bs_env, eigenval_scf_Gamma_dft, e_fermi_dft, fm_Sigma_x_Gamma)
389 :
390 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
391 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
392 : INTENT(INOUT) :: eigenval_scf_Gamma_dft
393 : REAL(KIND=dp), DIMENSION(2), INTENT(IN) :: e_fermi_dft
394 : TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:) :: fm_Sigma_x_Gamma
395 :
396 : CHARACTER(LEN=*), PARAMETER :: routineN = 'reset_and_clean_bs_env'
397 :
398 : INTEGER :: handle
399 :
400 36 : CALL timeset(routineN, handle)
401 :
402 36 : IF (bs_env%gw_flavour == evGW0) THEN
403 : ! Update array with final evGW0 result
404 70 : bs_env%eigenval_evGW0(:, :, :) = bs_env%eigenval_GW(:, :, :)
405 : ! put the DFT reference back for the post-GW DOS/band-edge printing
406 64 : bs_env%eigenval_scf_Gamma(:, :) = eigenval_scf_Gamma_dft(:, :)
407 12 : bs_env%e_fermi(:) = e_fermi_dft(:)
408 4 : DEALLOCATE (eigenval_scf_Gamma_dft)
409 4 : CALL cp_fm_release(fm_Sigma_x_Gamma)
410 : END IF
411 :
412 36 : CALL timestop(handle)
413 :
414 36 : END SUBROUTINE reset_and_clean_bs_env
415 :
416 : ! **************************************************************************************************
417 : !> \brief Print the quasiparticle HOMO, LUMO and HOMO-LUMO gap of the current evGW0 cycle
418 : !> \param bs_env ...
419 : !> \param band_prev ...
420 : !> \param i_iter ...
421 : !> \param n_iter ...
422 : !> \param converged ...
423 : ! **************************************************************************************************
424 18 : SUBROUTINE print_evGW0_band_edges(bs_env, band_prev, i_iter, n_iter, converged)
425 :
426 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
427 : REAL(KIND=dp), DIMENSION(3, 2), INTENT(INOUT) :: band_prev
428 : INTEGER, INTENT(IN) :: i_iter, n_iter
429 : LOGICAL, INTENT(OUT) :: converged
430 :
431 : CHARACTER(LEN=*), PARAMETER :: routineN = 'print_evGW0_band_edges'
432 :
433 : INTEGER :: handle, homo, ispin, u
434 : REAL(KIND=dp) :: max_delta
435 : REAL(KIND=dp), DIMENSION(3) :: band
436 :
437 18 : CALL timeset(routineN, handle)
438 :
439 18 : u = bs_env%unit_nr
440 :
441 18 : converged = (i_iter > 1)
442 18 : max_delta = 0.0_dp
443 :
444 18 : IF (u > 0) THEN
445 9 : WRITE (u, '(A)') ' '
446 9 : WRITE (u, '(T2,A)') REPEAT('-', 79)
447 9 : WRITE (u, '(T2,A,I4,A,I4)') 'evGW0 cycle', i_iter, ' /', n_iter
448 9 : WRITE (u, '(T2,A)') REPEAT('-', 79)
449 : END IF
450 :
451 44 : DO ispin = 1, bs_env%n_spin
452 :
453 26 : homo = bs_env%n_occ(ispin)
454 26 : band(1) = bs_env%eigenval_GW(homo, 1, ispin)
455 26 : band(2) = bs_env%eigenval_GW(homo + 1, 1, ispin)
456 26 : band(3) = band(2) - band(1)
457 :
458 26 : IF (i_iter > 1) THEN
459 80 : max_delta = MAX(max_delta, MAXVAL(ABS(band(:) - band_prev(:, ispin))))
460 : END IF
461 :
462 26 : IF (u > 0) THEN
463 13 : IF (bs_env%n_spin == 2) WRITE (u, '(T2,A,I0)') 'Spin ', ispin
464 13 : WRITE (u, '(T2,A,T61,F20.3)') 'evGW0 HOMO (eV)', band(1)*evolt
465 13 : WRITE (u, '(T2,A,T61,F20.3)') 'evGW0 LUMO (eV)', band(2)*evolt
466 13 : WRITE (u, '(T2,A,T61,F20.3)') 'evGW0 HOMO-LUMO gap (eV)', band(3)*evolt
467 : END IF
468 :
469 122 : band_prev(:, ispin) = band(:)
470 :
471 : END DO
472 :
473 18 : IF (i_iter > 1) THEN
474 14 : converged = (max_delta < bs_env%ri_rs%evgw0_eps_iter)
475 21 : IF (u > 0) WRITE (u, '(T2,A,T61,F20.6)') 'Max. change to previous cycle (eV)', &
476 14 : max_delta*evolt
477 : END IF
478 :
479 11 : IF (u > 0) WRITE (u, '(T2,A)') REPEAT('-', 79)
480 :
481 18 : IF (converged) THEN
482 4 : IF (u > 0) THEN
483 2 : WRITE (u, '(A)') ' '
484 : WRITE (u, '(T2,A,I4,A)') &
485 2 : 'evGW0 eigenvalue self-consistency reached in', i_iter, ' cycles.'
486 2 : WRITE (u, '(A)') ' '
487 : END IF
488 14 : ELSE IF (i_iter == n_iter) THEN
489 : CALL cp_warn(__LOCATION__, &
490 : "The evGW0 eigenvalue self-consistency cycle did not converge "// &
491 : "within MAX_ITER cycles. The reported quasiparticle energies are "// &
492 0 : "those of the last cycle.")
493 : END IF
494 :
495 18 : CALL timestop(handle)
496 :
497 18 : END SUBROUTINE print_evGW0_band_edges
498 :
499 : ! **************************************************************************************************
500 : !> \brief Compute per-atom AO and RI basis radii from the most diffuse Gaussian
501 : !> primitive in the AO ("ORB") and RI auxiliary ("RI_AUX") basis sets.
502 : !> Stores results in bs_env%ri_rs%radius_ao_per_atom(:) and
503 : !> bs_env%ri_rs%radius_ri_per_atom(:) and prints a per-atom table.
504 : !> Radius: r_kind = sqrt(-log(eps) / alpha_min_kind)
505 : !> with eps = eps_filter.
506 : !> \param qs_env ...
507 : !> \param bs_env ...
508 : ! **************************************************************************************************
509 44 : SUBROUTINE precompute_ri_rs_radii(qs_env, bs_env)
510 :
511 : TYPE(qs_environment_type), POINTER :: qs_env
512 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
513 :
514 : CHARACTER(LEN=*), PARAMETER :: routineN = 'precompute_ri_rs_radii'
515 :
516 : INTEGER :: handle, i, iatom, ikind, j, natom, nkind
517 44 : INTEGER, ALLOCATABLE, DIMENSION(:) :: kind_of
518 : REAL(KIND=dp) :: eps
519 44 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:) :: alpha_min_ao_kind, alpha_min_ri_kind
520 44 : REAL(KIND=dp), DIMENSION(:, :), POINTER :: zet_ao, zet_ri
521 44 : TYPE(atomic_kind_type), DIMENSION(:), POINTER :: atomic_kind_set
522 44 : TYPE(particle_type), DIMENSION(:), POINTER :: particle_set
523 :
524 44 : CALL timeset(routineN, handle)
525 :
526 : CALL get_qs_env(qs_env, nkind=nkind, atomic_kind_set=atomic_kind_set, &
527 44 : particle_set=particle_set)
528 44 : natom = SIZE(particle_set)
529 :
530 44 : eps = bs_env%eps_filter
531 :
532 176 : ALLOCATE (alpha_min_ao_kind(nkind), alpha_min_ri_kind(nkind))
533 106 : alpha_min_ao_kind = HUGE(1.0_dp)
534 106 : alpha_min_ri_kind = HUGE(1.0_dp)
535 :
536 106 : DO ikind = 1, nkind
537 62 : zet_ao => bs_env%basis_set_AO(ikind)%gto_basis_set%zet
538 62 : zet_ri => bs_env%basis_set_RI(ikind)%gto_basis_set%zet
539 :
540 204 : DO i = 1, SIZE(zet_ao, 1)
541 478 : DO j = 1, SIZE(zet_ao, 2)
542 416 : IF (zet_ao(i, j) > 1.0E-3_dp) THEN
543 274 : alpha_min_ao_kind(ikind) = MIN(alpha_min_ao_kind(ikind), zet_ao(i, j))
544 : END IF
545 : END DO
546 : END DO
547 168 : DO i = 1, SIZE(zet_ri, 1)
548 486 : DO j = 1, SIZE(zet_ri, 2)
549 424 : IF (zet_ri(i, j) > 1.0E-3_dp) THEN
550 362 : alpha_min_ri_kind(ikind) = MIN(alpha_min_ri_kind(ikind), zet_ri(i, j))
551 : END IF
552 : END DO
553 : END DO
554 : END DO
555 :
556 44 : CALL get_atomic_kind_set(atomic_kind_set=atomic_kind_set, kind_of=kind_of)
557 :
558 132 : ALLOCATE (bs_env%ri_rs%radius_ao_per_atom(natom))
559 88 : ALLOCATE (bs_env%ri_rs%radius_ri_per_atom(natom))
560 150 : DO iatom = 1, natom
561 106 : ikind = kind_of(iatom)
562 106 : bs_env%ri_rs%radius_ao_per_atom(iatom) = SQRT(-LOG(eps)/alpha_min_ao_kind(ikind))
563 150 : bs_env%ri_rs%radius_ri_per_atom(iatom) = SQRT(-LOG(eps)/alpha_min_ri_kind(ikind))
564 : END DO
565 :
566 44 : IF (bs_env%unit_nr > 0) THEN
567 22 : WRITE (bs_env%unit_nr, '(T2,A)') 'Per-kind RI-RS basis radii (Å):'
568 22 : WRITE (bs_env%unit_nr, '(T4,A6,2X,A4,2A14)') 'Kind', 'Elem', 'r_AO (Å)', 'r_RI (Å)'
569 53 : DO ikind = 1, nkind
570 : WRITE (bs_env%unit_nr, '(T4,I6,2X,A4,2F14.4)') &
571 31 : ikind, &
572 31 : atomic_kind_set(ikind)%element_symbol, &
573 31 : SQRT(-LOG(eps)/alpha_min_ao_kind(ikind))*angstrom, &
574 84 : SQRT(-LOG(eps)/alpha_min_ri_kind(ikind))*angstrom
575 : END DO
576 22 : WRITE (bs_env%unit_nr, '(A)') ' '
577 : END IF
578 :
579 44 : DEALLOCATE (alpha_min_ao_kind, alpha_min_ri_kind, kind_of)
580 :
581 44 : CALL timestop(handle)
582 :
583 44 : END SUBROUTINE precompute_ri_rs_radii
584 :
585 : ! **************************************************************************************************
586 : !> \brief Spreads the low 21 bits of a into every third bit (bits 0,3,6,...,60): the 1-D helper
587 : !> for a 3-D Morton (Z-order) code. Standard 64-bit magic-mask implementation.
588 : !> \param a value in [0, 2^21)
589 : !> \param x a with two zero bits inserted between consecutive input bits
590 : ! **************************************************************************************************
591 318 : SUBROUTINE morton_split3(a, x)
592 : INTEGER(KIND=int_8), INTENT(IN) :: a
593 : INTEGER(KIND=int_8), INTENT(OUT) :: x
594 :
595 318 : x = IAND(a, INT(z'1FFFFF', int_8))
596 318 : x = IAND(IOR(x, ISHFT(x, 32)), INT(z'1F00000000FFFF', int_8))
597 318 : x = IAND(IOR(x, ISHFT(x, 16)), INT(z'1F0000FF0000FF', int_8))
598 318 : x = IAND(IOR(x, ISHFT(x, 8)), INT(z'100F00F00F00F00F', int_8))
599 318 : x = IAND(IOR(x, ISHFT(x, 4)), INT(z'10C30C30C30C30C3', int_8))
600 318 : x = IAND(IOR(x, ISHFT(x, 2)), INT(z'1249249249249249', int_8))
601 318 : END SUBROUTINE morton_split3
602 :
603 : ! **************************************************************************************************
604 : !> \brief Returns a permutation of atom indices in Morton (Z-order) space-filling order of their
605 : !> Cartesian centers, so consecutive atoms are spatial neighbors. The RI-RS grid rows are
606 : !> laid down in this order, so a contiguous grid panel maps to a compact spatial region and
607 : !> the CUTOFF_RADIUS_RL_W neighborhood of every panel shrinks. The grid row index
608 : !> is a summed contraction index, so ANY permutation is result-preserving; this one is
609 : !> chosen purely to improve locality. Coordinates are normalized to the atom bounding box
610 : !> and quantized to 21 bits per axis (sub-picometre for any real cell).
611 : !> \param particle_set ...
612 : !> \param order order(i) = atom index placed at layout position i
613 : ! **************************************************************************************************
614 44 : SUBROUTINE spatial_atom_order(particle_set, order)
615 : TYPE(particle_type), DIMENSION(:), POINTER :: particle_set
616 : INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT) :: order
617 :
618 : CHARACTER(LEN=*), PARAMETER :: routineN = 'spatial_atom_order'
619 : INTEGER, PARAMETER :: nbits = 21
620 :
621 : INTEGER :: handle, ia, k, natom
622 : INTEGER(KIND=int_8) :: cmax, ic(3), m1, m2, m3
623 44 : INTEGER(KIND=int_8), ALLOCATABLE :: mcode(:)
624 : REAL(KIND=dp) :: hi(3), lo(3), span(3)
625 :
626 44 : CALL timeset(routineN, handle)
627 :
628 44 : natom = SIZE(particle_set)
629 220 : ALLOCATE (order(natom), mcode(natom))
630 : cmax = ISHFT(1_int_8, nbits) - 1_int_8
631 :
632 176 : lo(:) = HUGE(1.0_dp)
633 176 : hi(:) = -HUGE(1.0_dp)
634 150 : DO ia = 1, natom
635 468 : DO k = 1, 3
636 318 : lo(k) = MIN(lo(k), particle_set(ia)%r(k))
637 424 : hi(k) = MAX(hi(k), particle_set(ia)%r(k))
638 : END DO
639 : END DO
640 176 : span(:) = hi(:) - lo(:)
641 176 : DO k = 1, 3
642 176 : IF (span(k) <= 0.0_dp) span(k) = 1.0_dp
643 : END DO
644 :
645 150 : DO ia = 1, natom
646 424 : DO k = 1, 3
647 318 : ic(k) = INT(((particle_set(ia)%r(k) - lo(k))/span(k))*REAL(cmax, dp), int_8)
648 424 : ic(k) = MIN(cmax, MAX(0_int_8, ic(k)))
649 : END DO
650 106 : CALL morton_split3(ic(1), m1)
651 106 : CALL morton_split3(ic(2), m2)
652 106 : CALL morton_split3(ic(3), m3)
653 150 : mcode(ia) = IOR(IOR(m1, ISHFT(m2, 1)), ISHFT(m3, 2))
654 : END DO
655 :
656 : ! sort(mcode, natom, order): order(i) = original atom index with the i-th smallest code
657 44 : CALL sort(mcode, natom, order)
658 :
659 44 : DEALLOCATE (mcode)
660 :
661 44 : CALL timestop(handle)
662 :
663 44 : END SUBROUTINE spatial_atom_order
664 :
665 : ! **************************************************************************************************
666 : !> \brief Compute grid points for RI-RS
667 : !> Right now based on Ivan and Xavier implementation
668 : !> JCP 150, 174120 (2019), JCTC 17, 2383 (2021)
669 : !> \param qs_env ...
670 : !> \param bs_env ...
671 : !> \param ri_rs_grid_points ...
672 : ! **************************************************************************************************
673 44 : SUBROUTINE ri_rs_grid_assembler(qs_env, bs_env, ri_rs_grid_points)
674 :
675 : TYPE(qs_environment_type), POINTER :: qs_env
676 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
677 : REAL(KIND=dp), ALLOCATABLE, INTENT(OUT) :: ri_rs_grid_points(:, :)
678 :
679 : CHARACTER(LEN=*), PARAMETER :: routineN = 'ri_rs_grid_assembler'
680 :
681 : INTEGER :: atom_idx, end_idx, handle, i_layout, &
682 : natom, start_idx, total_grid_npts
683 44 : INTEGER, ALLOCATABLE :: atom_order(:), ri_rs_grid_offsets(:)
684 : REAL(KIND=dp) :: atomic_center(3)
685 44 : TYPE(atomic_kind_type), DIMENSION(:), POINTER :: atomic_kind_set
686 44 : TYPE(particle_type), DIMENSION(:), POINTER :: particle_set
687 :
688 44 : CALL timeset(routineN, handle)
689 :
690 : !! Get the information about the atoms in the system
691 44 : CALL get_qs_env(qs_env, atomic_kind_set=atomic_kind_set, particle_set=particle_set)
692 :
693 44 : natom = SIZE(particle_set)
694 :
695 : !! 1. Build the grid cache
696 44 : CALL build_grid_cache(bs_env, atomic_kind_set)
697 :
698 : !! 2. Optimize RIRS grid points
699 44 : IF (bs_env%ri_rs%grid_opt%enabled) THEN
700 6 : CALL optimize_ri_rs_grid(qs_env, bs_env)
701 : END IF
702 :
703 : !! 3. Calculate grid counts and offsets
704 132 : ALLOCATE (ri_rs_grid_offsets(natom + 1))
705 :
706 : ! grid_atom_boundaries(k) = 1-based start of the k-th atom's grid run in LAYOUT order
707 : ! (the order in which the points are laid down below). Used later to build atom-aligned
708 : ! DBCSR grid row-blocks in atomic_basis_at_grid_point.
709 44 : IF (ALLOCATED(bs_env%ri_rs%grid_atom_boundaries)) DEALLOCATE (bs_env%ri_rs%grid_atom_boundaries)
710 88 : ALLOCATE (bs_env%ri_rs%grid_atom_boundaries(natom + 1))
711 :
712 : ! Lay the grid rows down in Morton (spatial) atom order, so a
713 : ! contiguous grid maps to a compact spatial region.
714 44 : CALL spatial_atom_order(particle_set, atom_order)
715 :
716 44 : total_grid_npts = 0
717 150 : DO i_layout = 1, natom
718 106 : atom_idx = atom_order(i_layout)
719 106 : ri_rs_grid_offsets(atom_idx) = total_grid_npts + 1
720 106 : bs_env%ri_rs%grid_atom_boundaries(i_layout) = total_grid_npts + 1
721 150 : total_grid_npts = total_grid_npts + bs_env%ri_rs%grid_cache(atom_idx)%npts
722 : END DO
723 :
724 44 : ri_rs_grid_offsets(natom + 1) = total_grid_npts + 1
725 44 : bs_env%ri_rs%grid_atom_boundaries(natom + 1) = total_grid_npts + 1
726 :
727 44 : IF (bs_env%unit_nr > 0) THEN
728 : WRITE (bs_env%unit_nr, FMT="(T2,A,T69,I12)") &
729 22 : 'Total grid points used for RI-RS:', total_grid_npts
730 22 : WRITE (bs_env%unit_nr, "(A)") ' '
731 : END IF
732 :
733 : !! 4. Allocate the global ri_rs_grid arrays
734 132 : ALLOCATE (ri_rs_grid_points(3, total_grid_npts))
735 :
736 : !! 5. Parallelize the grid generation loop
737 : !$OMP PARALLEL DO DEFAULT(NONE) &
738 : !$OMP SHARED(ri_rs_grid_points, ri_rs_grid_offsets, &
739 : !$OMP particle_set, bs_env, natom) &
740 : !$OMP PRIVATE(atom_idx, atomic_center, start_idx, end_idx) &
741 44 : !$OMP SCHEDULE(DYNAMIC, 1)
742 : DO atom_idx = 1, natom
743 : atomic_center(:) = particle_set(atom_idx)%r(:)
744 :
745 : start_idx = ri_rs_grid_offsets(atom_idx)
746 : end_idx = start_idx + bs_env%ri_rs%grid_cache(atom_idx)%npts - 1
747 :
748 : !! Shift the cached origin grid by the atom's center
749 : ri_rs_grid_points(1, start_idx:end_idx) = bs_env%ri_rs%grid_cache(atom_idx)%raw_points(1, :) + atomic_center(1)
750 : ri_rs_grid_points(2, start_idx:end_idx) = bs_env%ri_rs%grid_cache(atom_idx)%raw_points(2, :) + atomic_center(2)
751 : ri_rs_grid_points(3, start_idx:end_idx) = bs_env%ri_rs%grid_cache(atom_idx)%raw_points(3, :) + atomic_center(3)
752 :
753 : END DO
754 : !$OMP END PARALLEL DO
755 :
756 : !! 6. Cleanup memory
757 44 : IF (ALLOCATED(bs_env%ri_rs%grid_cache)) THEN
758 150 : DO atom_idx = 1, natom
759 150 : IF (ALLOCATED(bs_env%ri_rs%grid_cache(atom_idx)%raw_points)) THEN
760 106 : DEALLOCATE (bs_env%ri_rs%grid_cache(atom_idx)%raw_points)
761 : END IF
762 : END DO
763 150 : DEALLOCATE (bs_env%ri_rs%grid_cache)
764 : END IF
765 :
766 44 : DEALLOCATE (atom_order, ri_rs_grid_offsets)
767 :
768 44 : CALL timestop(handle)
769 :
770 88 : END SUBROUTINE ri_rs_grid_assembler
771 :
772 : ! **************************************************************************************************
773 : !> \brief Reads grids from .ion files and stores them in memory based on grid_select
774 : !> \param bs_env ...
775 : !> \param atomic_kind_set ...
776 : ! **************************************************************************************************
777 44 : SUBROUTINE build_grid_cache(bs_env, atomic_kind_set)
778 :
779 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
780 : TYPE(atomic_kind_type), DIMENSION(:), POINTER :: atomic_kind_set
781 :
782 : CHARACTER(LEN=*), PARAMETER :: routineN = 'build_grid_cache'
783 :
784 : CHARACTER(LEN=default_path_length) :: full_path, warning_msg
785 : CHARACTER(LEN=default_string_length) :: atom_sym
786 : INTEGER :: handle, iatom, ikind, &
787 : n_voronoi_candidates, natom, nkind, &
788 : requested_npts
789 44 : TYPE(particle_type), DIMENSION(:), POINTER :: particle_set
790 44 : TYPE(rirs_grid_type), ALLOCATABLE :: kind_grid(:)
791 :
792 44 : CALL timeset(routineN, handle)
793 :
794 44 : nkind = SIZE(atomic_kind_set)
795 44 : particle_set => bs_env%ri_rs%particle_set
796 44 : CPASSERT(ASSOCIATED(particle_set))
797 44 : natom = SIZE(particle_set)
798 194 : ALLOCATE (kind_grid(nkind))
799 :
800 106 : DO ikind = 1, nkind
801 62 : atom_sym = TRIM(atomic_kind_set(ikind)%element_symbol)
802 : CALL get_rirs_grid_filepath( &
803 62 : atom_sym, bs_env%ri_rs%grid_select, bs_env%ri_rs%grid_file_suffix, full_path)
804 62 : CALL read_rirs_grid_file(TRIM(full_path), kind_grid(ikind)%raw_points)
805 106 : kind_grid(ikind)%npts = SIZE(kind_grid(ikind)%raw_points, 2)
806 : END DO
807 :
808 : ! Make every atom own an independent copy. The optimizer can therefore adapt symmetry-
809 : ! equivalent atoms to their individual local environments, and GW consumes those copies
810 : ! directly without writing intermediate .ion files.
811 44 : IF (ALLOCATED(bs_env%ri_rs%grid_cache)) DEALLOCATE (bs_env%ri_rs%grid_cache)
812 238 : ALLOCATE (bs_env%ri_rs%grid_cache(natom))
813 150 : DO iatom = 1, natom
814 106 : ikind = particle_set(iatom)%atomic_kind%kind_number
815 106 : bs_env%ri_rs%grid_cache(iatom) = kind_grid(ikind)
816 150 : IF (bs_env%ri_rs%grid_opt%enabled) THEN
817 18 : atom_sym = TRIM(atomic_kind_set(ikind)%element_symbol)
818 18 : requested_npts = bs_env%ri_rs%grid_opt%grid_npoints(ikind)
819 : CALL initialize_rirs_grid( &
820 : bs_env%ri_rs%grid_cache(iatom)%raw_points, requested_npts, iatom, particle_set, &
821 18 : n_voronoi_candidates)
822 18 : IF (n_voronoi_candidates < requested_npts) THEN
823 : WRITE (warning_msg, '(A,I0,A,A,A,I0,A,I0,A)') &
824 0 : "Atom ", iatom, " (", TRIM(atom_sym), "): Grid selection requests ", requested_npts, &
825 0 : " points, but only ", n_voronoi_candidates, &
826 0 : " are inside its Voronoi volume. Selecting from the full RI-RS source grid."
827 0 : CALL cp_warn(__LOCATION__, TRIM(warning_msg))
828 : END IF
829 18 : bs_env%ri_rs%grid_cache(iatom)%npts = requested_npts
830 : END IF
831 : END DO
832 106 : DEALLOCATE (kind_grid)
833 :
834 44 : CALL timestop(handle)
835 :
836 44 : END SUBROUTINE build_grid_cache
837 :
838 : ! **************************************************************************************************
839 : !> \brief Evaluates the AO basis on the RI-RS grid and stores it as the sparse DBCSR matrix
840 : !> Φ_μl = Φ_μ(r_l) (rows = grid points in atom-aligned blocks of at most
841 : !> max_elements_per_block points, columns = one block per atom's full AO set).
842 : !> Grid points outside the reach of an atom's most
843 : !> diffuse Gaussian (or the CUTOFF_RADIUS_RL_AO) are skipped, and only blocks
844 : !> with at least one element > eps_filter are stored. This locality is the source of
845 : !> ALL grid-dimension sparsity used downstream. Also caches the atom centers and the
846 : !> per-chunk centroids needed by the optional CUTOFF_RADIUS_G_W / CUTOFF_RADIUS_RL_W
847 : !> operator truncations.
848 : !> \param qs_env ...
849 : !> \param bs_env ...
850 : !> \param ri_rs_grid_points ...
851 : !> \param mat_phi_mu_l ...
852 : ! **************************************************************************************************
853 44 : SUBROUTINE atomic_basis_at_grid_point(qs_env, bs_env, ri_rs_grid_points, mat_phi_mu_l)
854 :
855 : TYPE(qs_environment_type), POINTER :: qs_env
856 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
857 : REAL(KIND=dp), ALLOCATABLE, INTENT(INOUT) :: ri_rs_grid_points(:, :)
858 : TYPE(dbcsr_type), INTENT(OUT) :: mat_phi_mu_l
859 :
860 : CHARACTER(LEN=*), PARAMETER :: routineN = 'atomic_basis_at_grid_point'
861 :
862 : INTEGER :: bs_eff, c_size, dimen_ORB, handle, i, i_blk, ia, iatom, natom, npcol, nprow, &
863 : num_grid_chunks, r_end, r_start, remaining, run, safe_max, total_grid_npts
864 44 : INTEGER, ALLOCATABLE, DIMENSION(:) :: blk_row_start, first_sgf
865 44 : INTEGER, DIMENSION(:), POINTER :: c_blk_sizes, col_dist, r_blk_sizes, &
866 44 : row_dist
867 : REAL(KIND=dp) :: r2_threshold
868 44 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :) :: atom_col_buffer
869 44 : TYPE(atomic_kind_type), DIMENSION(:), POINTER :: atomic_kind_set
870 : TYPE(cell_type), POINTER :: cell
871 : TYPE(dbcsr_distribution_type) :: dist
872 : TYPE(dbcsr_distribution_type), POINTER :: dbcsr_dist_ks
873 : TYPE(mp_para_env_type), POINTER :: para_env
874 44 : TYPE(particle_type), DIMENSION(:), POINTER :: particle_set
875 44 : TYPE(qs_kind_type), DIMENSION(:), POINTER :: qs_kind_set
876 :
877 44 : CALL timeset(routineN, handle)
878 :
879 : ! Extract environment variables
880 : CALL get_qs_env(qs_env, cell=cell, atomic_kind_set=atomic_kind_set, &
881 : qs_kind_set=qs_kind_set, particle_set=particle_set, &
882 44 : para_env=para_env)
883 :
884 44 : natom = SIZE(particle_set)
885 44 : total_grid_npts = SIZE(ri_rs_grid_points, 2)
886 :
887 : ! Map the starting indices of spherical gaussian functions (SGF) for each atom
888 132 : ALLOCATE (first_sgf(natom + 1))
889 44 : CALL get_basis_offsets(particle_set, qs_kind_set, first_sgf, dimen_ORB)
890 :
891 : ! =========================================================================
892 : ! 1. SETUP DBCSR MATRIX TOPOLOGY
893 : ! =========================================================================
894 :
895 : ! A. Define Column Block Sizes (1 Block = 1 Atom's full basis set)
896 132 : ALLOCATE (c_blk_sizes(natom))
897 150 : DO iatom = 1, natom
898 150 : c_blk_sizes(iatom) = first_sgf(iatom + 1) - first_sgf(iatom)
899 : END DO
900 :
901 : ! B. Define Row Block Sizes: atom-aligned blocks (a block never spans two atoms' grid
902 : ! runs), each atom's run subdivided into blocks of at most bs_eff points.
903 :
904 : ! Fetch CP2K's default process grid configuration
905 44 : CALL get_qs_env(qs_env, dbcsr_dist=dbcsr_dist_ks)
906 44 : CALL dbcsr_distribution_get(dbcsr_dist_ks, nprows=nprow, npcols=npcol)
907 :
908 : ! Overflow-safe upper bound on the block size (see bs_env%dbcsr_msg_elem_limit).
909 : safe_max = INT(0.5_dp*REAL(bs_env%dbcsr_msg_elem_limit, dp)* &
910 : REAL(MAX(MIN(nprow, npcol), 1), dp)/ &
911 44 : REAL(total_grid_npts, dp))
912 44 : safe_max = MAX(1, safe_max)
913 : ! Block size = CP2K's global max_elements_per_block (GLOBAL/DBCSR input; default 32),
914 : ! overflow-capped.
915 44 : bs_eff = MAX(1, MIN(max_elements_per_block, safe_max))
916 :
917 : ! Count the atom-aligned blocks, then fill r_blk_sizes and each block's starting grid row.
918 44 : num_grid_chunks = 0
919 150 : DO ia = 1, natom
920 106 : run = bs_env%ri_rs%grid_atom_boundaries(ia + 1) - bs_env%ri_rs%grid_atom_boundaries(ia)
921 150 : IF (run > 0) num_grid_chunks = num_grid_chunks + (run + bs_eff - 1)/bs_eff
922 : END DO
923 176 : ALLOCATE (r_blk_sizes(num_grid_chunks), blk_row_start(num_grid_chunks))
924 44 : i_blk = 0
925 44 : r_start = 1
926 150 : DO ia = 1, natom
927 106 : remaining = bs_env%ri_rs%grid_atom_boundaries(ia + 1) - bs_env%ri_rs%grid_atom_boundaries(ia)
928 744 : DO WHILE (remaining > 0)
929 594 : i_blk = i_blk + 1
930 594 : r_blk_sizes(i_blk) = MIN(bs_eff, remaining)
931 594 : blk_row_start(i_blk) = r_start
932 594 : r_start = r_start + r_blk_sizes(i_blk)
933 700 : remaining = remaining - r_blk_sizes(i_blk)
934 : END DO
935 : END DO
936 :
937 44 : IF (bs_env%unit_nr > 0) THEN
938 22 : WRITE (bs_env%unit_nr, '(T2,A,T69,I12)') 'RI-RS grid row-blocks of phi(mu,l)', num_grid_chunks
939 22 : WRITE (bs_env%unit_nr, '(T2,A,T69,I12)') 'RI-RS grid points per block (max)', bs_eff
940 : END IF
941 :
942 : ! Cache atomic positions: AO and RI blocks are one-block-per-atom, so these are the
943 : ! block centers used by the optional CUTOFF_RADIUS_G_W atom-pair truncation.
944 44 : IF (ALLOCATED(bs_env%ri_rs%atom_centers)) DEALLOCATE (bs_env%ri_rs%atom_centers)
945 132 : ALLOCATE (bs_env%ri_rs%atom_centers(3, natom))
946 150 : DO iatom = 1, natom
947 892 : bs_env%ri_rs%atom_centers(1:3, iatom) = particle_set(iatom)%r(1:3)
948 : END DO
949 :
950 : ! Cache per-chunk centroids for the optional CUTOFF_RADIUS_RL_W / CUTOFF_RADIUS_W0 block
951 : ! truncations. Left unallocated otherwise, so PRESENT(centroids) stays .FALSE. at the
952 : ! contract_grid_panels* calls.
953 44 : IF (bs_env%ri_rs%cutoff_radius_v_w > 0.0_dp .OR. &
954 : bs_env%ri_rs%cutoff_radius_w0 > 0.0_dp) THEN
955 8 : IF (ALLOCATED(bs_env%ri_rs%chunk_centroids)) DEALLOCATE (bs_env%ri_rs%chunk_centroids)
956 24 : ALLOCATE (bs_env%ri_rs%chunk_centroids(3, num_grid_chunks))
957 110 : DO i_blk = 1, num_grid_chunks
958 102 : r_start = blk_row_start(i_blk)
959 102 : r_end = r_start + r_blk_sizes(i_blk) - 1
960 : bs_env%ri_rs%chunk_centroids(1, i_blk) = &
961 2950 : SUM(ri_rs_grid_points(1, r_start:r_end))/REAL(r_blk_sizes(i_blk), dp)
962 : bs_env%ri_rs%chunk_centroids(2, i_blk) = &
963 2950 : SUM(ri_rs_grid_points(2, r_start:r_end))/REAL(r_blk_sizes(i_blk), dp)
964 : bs_env%ri_rs%chunk_centroids(3, i_blk) = &
965 2994 : SUM(ri_rs_grid_points(3, r_start:r_end))/REAL(r_blk_sizes(i_blk), dp)
966 : END DO
967 : END IF
968 :
969 : ! C. Build Custom Mappings using Round-Robin across the 2D process grid
970 :
971 88 : ALLOCATE (row_dist(num_grid_chunks))
972 638 : DO i = 1, num_grid_chunks
973 638 : row_dist(i) = MOD(i - 1, nprow)
974 : END DO
975 :
976 88 : ALLOCATE (col_dist(natom))
977 150 : DO i = 1, natom
978 150 : col_dist(i) = MOD(i - 1, npcol)
979 : END DO
980 :
981 : ! E. Create the DBCSR Distribution and Initialize the Matrix
982 : CALL dbcsr_distribution_new(dist, template=dbcsr_dist_ks, &
983 44 : row_dist=row_dist, col_dist=col_dist)
984 :
985 : CALL dbcsr_create(mat_phi_mu_l, name="phi_val_sparse", dist=dist, &
986 : matrix_type=dbcsr_type_no_symmetry, &
987 44 : row_blk_size=r_blk_sizes, col_blk_size=c_blk_sizes)
988 :
989 : ! =========================================================================
990 : ! 2. STREAM DATA DIRECTLY INTO SPARSE MATRIX
991 : ! =========================================================================
992 : ! Iterate over the atoms assigned to this specific MPI rank
993 44 : DO iatom = para_env%mepos + 1, natom, para_env%num_pe
994 :
995 53 : c_size = c_blk_sizes(iatom)
996 :
997 : ! Allocate a temporary dense buffer just for this specific atom
998 212 : ALLOCATE (atom_col_buffer(total_grid_npts, c_size))
999 53 : atom_col_buffer = 0.0_dp
1000 :
1001 : ! Evaluate the basis functions on the grid. Skip grid points outside
1002 : ! the spatial extent of the most diffuse AO Gaussian on iatom; beyond
1003 : ! that radius the contribution is guaranteed below eps_filter. A positive
1004 : ! CUTOFF_RADIUS_RL_AO overrides this with a user-defined hard cutoff.
1005 53 : IF (bs_env%ri_rs%cutoff_radius_ri_ao > 0.0_dp) THEN
1006 3 : r2_threshold = bs_env%ri_rs%cutoff_radius_ri_ao**2
1007 : ELSE
1008 50 : r2_threshold = bs_env%ri_rs%radius_ao_per_atom(iatom)**2
1009 : END IF
1010 : CALL fill_phi_for_atom(atom_col_buffer, ri_rs_grid_points, total_grid_npts, &
1011 53 : iatom, particle_set, qs_kind_set, cell, r2_threshold)
1012 :
1013 : ! Slice the dense column into the atom-aligned grid row-blocks and insert into DBCSR
1014 786 : DO i_blk = 1, num_grid_chunks
1015 733 : r_start = blk_row_start(i_blk)
1016 733 : r_end = r_start + r_blk_sizes(i_blk) - 1
1017 :
1018 : ! Apply dynamic sparsity filtering: Only store blocks with physical significance
1019 81503 : IF (MAXVAL(ABS(atom_col_buffer(r_start:r_end, 1:c_size))) > bs_env%eps_filter) THEN
1020 : CALL dbcsr_put_block(mat_phi_mu_l, row=i_blk, col=iatom, &
1021 721 : block=atom_col_buffer(r_start:r_end, 1:c_size))
1022 : END IF
1023 : END DO
1024 :
1025 53 : DEALLOCATE (atom_col_buffer)
1026 :
1027 : END DO
1028 :
1029 44 : CALL dbcsr_finalize(mat_phi_mu_l)
1030 :
1031 44 : CALL print_matrix_occupation(mat_phi_mu_l, 'φ(μ,l)', bs_env)
1032 :
1033 : ! -------------------------------------------------------------------------
1034 : ! CLEANUP
1035 : ! -------------------------------------------------------------------------
1036 44 : DEALLOCATE (first_sgf, r_blk_sizes, c_blk_sizes, row_dist, col_dist, blk_row_start)
1037 44 : CALL dbcsr_distribution_release(dist)
1038 :
1039 44 : CALL timestop(handle)
1040 :
1041 220 : END SUBROUTINE atomic_basis_at_grid_point
1042 :
1043 : ! **************************************************************************************************
1044 : !> \brief Evaluates all spherical AO basis functions of one atom on a set of grid points and
1045 : !> ACCUMULATES them into phi_val (+=). For each point within the cutoff radius,
1046 : !> Φ_μ(r) = Σ_pgf Σ_cart sphi(cart,μ) · (x-X_A)^lx (y-Y_A)^ly (z-Z_A)^lz · e^(-ζ_pgf |r-R_A|²)
1047 : !> i.e. contracted Cartesian Gaussians transformed to the spherical basis via the sphi
1048 : !> coefficients. Distances are minimum-image wrapped (pbc); points with
1049 : !> |r - R_A|² > r2_threshold are skipped since every primitive is below eps there.
1050 : !> OMP-parallel over grid points.
1051 : !> \param phi_val ...
1052 : !> \param ri_rs_grid ...
1053 : !> \param npts ...
1054 : !> \param iatom ...
1055 : !> \param particle_set ...
1056 : !> \param qs_kind_set ...
1057 : !> \param cell ...
1058 : !> \param r2_threshold ...
1059 : ! **************************************************************************************************
1060 177 : SUBROUTINE fill_phi_for_atom(phi_val, ri_rs_grid, npts, iatom, &
1061 : particle_set, qs_kind_set, cell, r2_threshold)
1062 :
1063 : REAL(KIND=dp), INTENT(INOUT) :: phi_val(:, :)
1064 : INTEGER, INTENT(IN) :: npts
1065 : REAL(KIND=dp), INTENT(IN) :: ri_rs_grid(3, npts)
1066 : INTEGER, INTENT(IN) :: iatom
1067 : TYPE(particle_type), DIMENSION(:), POINTER :: particle_set
1068 : TYPE(qs_kind_type), DIMENSION(:), POINTER :: qs_kind_set
1069 : TYPE(cell_type), POINTER :: cell
1070 : REAL(KIND=dp), INTENT(IN) :: r2_threshold
1071 :
1072 : CHARACTER(LEN=*), PARAMETER :: routineN = 'fill_phi_for_atom'
1073 :
1074 : INTEGER :: handle
1075 :
1076 177 : CALL timeset(routineN, handle)
1077 :
1078 : CALL evaluate_ao_on_points(phi_val, ri_rs_grid, iatom, particle_set, qs_kind_set, cell, &
1079 177 : cutoff_squared=r2_threshold)
1080 :
1081 177 : CALL timestop(handle)
1082 :
1083 177 : END SUBROUTINE fill_phi_for_atom
1084 :
1085 : ! **************************************************************************************************
1086 : !> \brief Computes the AO basis offsets: first_sgf(iatom) is the global index of the first
1087 : !> spherical Gaussian function (SGF) of iatom, first_sgf(natom+1) = total_sgf + 1,
1088 : !> and total_sgf is the total number of AO basis functions.
1089 : !> \param particle_set ...
1090 : !> \param qs_kind_set ...
1091 : !> \param first_sgf ...
1092 : !> \param total_sgf ...
1093 : ! **************************************************************************************************
1094 44 : SUBROUTINE get_basis_offsets(particle_set, qs_kind_set, first_sgf, total_sgf)
1095 :
1096 : TYPE(particle_type), DIMENSION(:), POINTER :: particle_set
1097 : TYPE(qs_kind_type), DIMENSION(:), POINTER :: qs_kind_set
1098 : INTEGER, INTENT(OUT) :: first_sgf(:), total_sgf
1099 :
1100 : CHARACTER(LEN=*), PARAMETER :: routineN = 'get_basis_offsets'
1101 :
1102 : INTEGER :: handle, iatom, ikind, nsgf
1103 :
1104 44 : CALL timeset(routineN, handle)
1105 :
1106 44 : total_sgf = 0
1107 150 : DO iatom = 1, SIZE(particle_set)
1108 106 : first_sgf(iatom) = total_sgf + 1
1109 106 : ikind = particle_set(iatom)%atomic_kind%kind_number
1110 106 : CALL get_qs_kind(qs_kind_set(ikind), nsgf=nsgf, basis_type="ORB")
1111 150 : total_sgf = total_sgf + nsgf
1112 : END DO
1113 44 : first_sgf(SIZE(particle_set) + 1) = total_sgf + 1
1114 :
1115 44 : CALL timestop(handle)
1116 :
1117 44 : END SUBROUTINE get_basis_offsets
1118 :
1119 : ! **************************************************************************************************
1120 : !> \brief Computes the RI-RS fitting coefficients Z_lP by solving, independently for every RI
1121 : !> atom P, a Jacobi-conditioned, Tikhonov-regularized linear system restricted to the
1122 : !> grid points r_l inside P's integration sphere |r_l - R_P| <= cutoff_ri(P):
1123 : !> D_ll' = [ Σ_μ Φ_μ(r_l) Φ_μ(r_l') ]² (squared grid Gram matrix, Eq. 13)
1124 : !> D_lP = Σ_μν Φ_μ(r_l) Φ_ν(r_l) (μν|P) (grid-RI right-hand side, Eq. 15)
1125 : !> d_l = 1 / sqrt(D_ll) (Jacobi conditioning vector)
1126 : !> D'_ll' = d_l D_ll' d_l' + λ δ_ll' (λ = TIKHONOV_SIGMA regularization)
1127 : !> Σ_l' D'_ll' Z'_l'P = d_l D_lP (Cholesky solve, Eq. 14)
1128 : !> Z_lP = d_l Z'_l'P (undo the conditioning)
1129 : !> Work is distributed over atoms in two phases (planned by classify_z_lp_atoms and
1130 : !> lpt_assign_atoms): Phase A solves "small" atoms with single-rank LAPACK
1131 : !> (dpotrf/dpotrs); Phase B solves "big" atoms, whose dense Gram matrix would exceed one
1132 : !> rank's memory, with ScaLAPACK (pdpotrf/pdpotrs) over rank subgroups of size G.
1133 : !> The solved Z columns are scattered into the sparse global mat_Z_lP.
1134 : !> If a Z_lP restart file exists, it is read instead and the solve is skipped entirely.
1135 : !> \param qs_env ...
1136 : !> \param bs_env ...
1137 : !> \param ri_rs_grid_points ...
1138 : !> \param mat_phi_mu_l ...
1139 : !> \param mat_Z_lP ...
1140 : ! **************************************************************************************************
1141 88 : SUBROUTINE compute_coeff_Z_lP(qs_env, bs_env, ri_rs_grid_points, mat_phi_mu_l, mat_Z_lP)
1142 :
1143 : TYPE(qs_environment_type), POINTER :: qs_env
1144 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
1145 : REAL(KIND=dp), ALLOCATABLE, INTENT(INOUT) :: ri_rs_grid_points(:, :)
1146 : TYPE(dbcsr_type), INTENT(INOUT) :: mat_phi_mu_l
1147 : TYPE(dbcsr_type), INTENT(OUT) :: mat_Z_lP
1148 :
1149 : CHARACTER(LEN=*), PARAMETER :: key = 'PROPERTIES%BANDSTRUCTURE%GW%PRINT%RESTART', &
1150 : routineN = 'compute_coeff_Z_lP'
1151 :
1152 : INTEGER :: atom_j_mepos, atom_j_stride, atom_P, G, handle, handle_dpotrf, handle_dpotrs, &
1153 : i_blk, idx, info, iphase, j, max_ao_size, my_group, n_ao_total, n_ao_used, n_big, n_done, &
1154 : n_groups, n_loc_ri, n_local_grid, n_my_atoms, n_small, natom, next_pct, npcol_phi, &
1155 : num_grid_chunks, P_loop_atom, phase_hi
1156 88 : INTEGER, ALLOCATABLE, DIMENSION(:) :: ao_col_map, big_list, local_grid_idx, &
1157 44 : my_atoms_A, my_atoms_B, &
1158 44 : n_local_grid_atom, row_offset, &
1159 44 : small_list
1160 88 : INTEGER, DIMENSION(:), POINTER :: col_dist_ri, r_blk_sizes, ri_blk_sizes, &
1161 44 : row_dist_grid
1162 : LOGICAL :: do_scatter, use_dist
1163 : REAL(KIND=dp) :: balance_A, balance_B, cutoff_ri, r_c, t1
1164 44 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:) :: cutoff_ri_per_atom, d_vec_local
1165 44 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :) :: D_local, d_lp_local, phi_local
1166 44 : TYPE(atomic_kind_type), DIMENSION(:), POINTER :: atomic_kind_set
1167 : TYPE(cell_type), POINTER :: cell
1168 : TYPE(cp_blacs_env_type), POINTER :: blacs_env_sub
1169 : TYPE(cp_fm_struct_type), POINTER :: fm_struct_b, fm_struct_D
1170 : TYPE(cp_fm_type) :: fm_b, fm_D
1171 : TYPE(cp_logger_type), POINTER :: logger
1172 : TYPE(dbcsr_distribution_type) :: dist_phi, dist_Z
1173 572 : TYPE(gw_3c_ctx_type) :: ctx_3c
1174 : TYPE(mp_para_env_type), POINTER :: para_env, para_env_sub
1175 44 : TYPE(particle_type), DIMENSION(:), POINTER :: particle_set
1176 44 : TYPE(qs_kind_type), DIMENSION(:), POINTER :: qs_kind_set
1177 : TYPE(section_vals_type), POINTER :: input
1178 :
1179 44 : CALL timeset(routineN, handle)
1180 :
1181 44 : t1 = m_walltime()
1182 :
1183 : CALL get_qs_env(qs_env, para_env=para_env, particle_set=particle_set, input=input, &
1184 44 : qs_kind_set=qs_kind_set, cell=cell, atomic_kind_set=atomic_kind_set)
1185 :
1186 44 : NULLIFY (para_env_sub, blacs_env_sub)
1187 :
1188 44 : natom = SIZE(bs_env%i_RI_start_from_atom)
1189 44 : n_ao_total = bs_env%i_ao_end_from_atom(natom)
1190 :
1191 : ! =========================================================================
1192 : ! 1. SETUP DBCSR TOPOLOGY & EXACT OFFSETS
1193 : ! mat_Z_lP inherits the grid row blocking (and row distribution) of
1194 : ! mat_phi_mu_l; its columns are one block per RI atom.
1195 : ! =========================================================================
1196 44 : CALL dbcsr_get_info(mat_phi_mu_l, row_blk_size=r_blk_sizes, distribution=dist_phi)
1197 44 : CALL dbcsr_distribution_get(dist_phi, row_dist=row_dist_grid, npcols=npcol_phi)
1198 :
1199 44 : num_grid_chunks = SIZE(r_blk_sizes)
1200 :
1201 132 : ALLOCATE (row_offset(num_grid_chunks))
1202 44 : row_offset(1) = 0
1203 594 : DO i_blk = 2, num_grid_chunks
1204 594 : row_offset(i_blk) = row_offset(i_blk - 1) + r_blk_sizes(i_blk - 1)
1205 : END DO
1206 :
1207 176 : ALLOCATE (ri_blk_sizes(natom), col_dist_ri(natom))
1208 150 : DO atom_P = 1, natom
1209 106 : ri_blk_sizes(atom_P) = bs_env%i_RI_end_from_atom(atom_P) - bs_env%i_RI_start_from_atom(atom_P) + 1
1210 150 : col_dist_ri(atom_P) = MOD(atom_P - 1, npcol_phi)
1211 : END DO
1212 :
1213 44 : CALL dbcsr_distribution_new(dist_Z, template=dist_phi, row_dist=row_dist_grid, col_dist=col_dist_ri)
1214 :
1215 44 : IF (bs_env%ri_rs%Z_lP_exists) THEN
1216 : CALL dbcsr_binary_read(filepath=TRIM(bs_env%prefix)//"Z_lP.matrix", &
1217 : distribution=dist_Z, &
1218 2 : matrix_new=mat_Z_lP)
1219 2 : IF (bs_env%unit_nr > 0) THEN
1220 : WRITE (bs_env%unit_nr, '(T2,A,T57,A,F7.1,A)') &
1221 1 : 'Read Z_lP from file ', ' Execution time', m_walltime() - t1, ' s'
1222 : ! The grid rows are laid out in Morton (spatial) order (spatial_atom_order); a Z_lP.matrix
1223 : ! written by an older build with a different grid ordering would be silently misread into
1224 : ! the current row order. Delete stale Z_lP.matrix files and recompute if in doubt.
1225 : WRITE (bs_env%unit_nr, '(T2,A)') &
1226 1 : '*** NOTE: Z_lP restart must match the current (spatial) grid row ordering ***'
1227 1 : WRITE (bs_env%unit_nr, '(A)') ' '
1228 : END IF
1229 : ELSE
1230 :
1231 : CALL dbcsr_create(mat_Z_lP, name="mat_Z_lP", dist=dist_Z, &
1232 : matrix_type=dbcsr_type_no_symmetry, &
1233 42 : row_blk_size=r_blk_sizes, col_blk_size=ri_blk_sizes)
1234 :
1235 : ! Largest per-atom AO block, needed to size the 3c-integral work buffers.
1236 42 : max_ao_size = 0
1237 142 : DO j = 1, SIZE(bs_env%i_ao_start_from_atom)
1238 142 : max_ao_size = MAX(max_ao_size, bs_env%i_ao_end_from_atom(j) - bs_env%i_ao_start_from_atom(j) + 1)
1239 : END DO
1240 :
1241 : ! Per-atom RI-RS integration sphere:
1242 : ! cutoff_ri(P) = r_c + r_RI(P)
1243 : ! where r_c is the truncated-Coulomb cutoff of the RI metric and r_RI the radius of
1244 : ! the most diffuse RI auxiliary Gaussian on P. The CUTOFF_RADIUS_RL_RI keyword
1245 : ! (when > 0) overrides the entire cutoff calculation.
1246 126 : ALLOCATE (cutoff_ri_per_atom(natom))
1247 :
1248 42 : IF (bs_env%ri_rs%cutoff_radius_ri_rs > 0.0_dp) THEN
1249 8 : cutoff_ri_per_atom(:) = bs_env%ri_rs%cutoff_radius_ri_rs
1250 : ELSE
1251 40 : r_c = bs_env%ri_metric%cutoff_radius
1252 134 : DO P_loop_atom = 1, natom
1253 : cutoff_ri_per_atom(P_loop_atom) = &
1254 134 : r_c + bs_env%ri_rs%radius_ri_per_atom(P_loop_atom)
1255 : END DO
1256 : END IF
1257 :
1258 42 : CALL print_sphere_cutoff_table(bs_env, atomic_kind_set, particle_set, cutoff_ri_per_atom)
1259 :
1260 : ! =========================================================================
1261 : ! 2. PER-ATOM SOLVER CLASSIFICATION
1262 : ! Split the atoms into "small" (single-rank LAPACK, Phase A) and "big"
1263 : ! (distributed ScaLAPACK over subgroups of G ranks, Phase B) by comparing
1264 : ! each atom's estimated solve peak memory against the measured budget.
1265 : ! =========================================================================
1266 : CALL classify_z_lp_atoms(bs_env, para_env, ri_rs_grid_points, particle_set, &
1267 : cutoff_ri_per_atom, ri_blk_sizes, &
1268 42 : n_local_grid_atom, small_list, n_small, big_list, n_big, G)
1269 :
1270 : ! LPT scheduling: sort the atoms of each phase by estimated solve cost
1271 : ! (n_local_grid^3, Cholesky-dominated) and greedily assign to the least-loaded
1272 : ! rank (Phase A) / subgroup (Phase B).
1273 : CALL lpt_assign_atoms(small_list, n_small, n_local_grid_atom, para_env%num_pe, &
1274 42 : para_env%mepos, my_atoms_A, balance_A)
1275 42 : IF (n_big > 0) THEN
1276 0 : n_groups = para_env%num_pe/G
1277 0 : my_group = MIN(para_env%mepos/G, n_groups - 1)
1278 : CALL lpt_assign_atoms(big_list, n_big, n_local_grid_atom, n_groups, my_group, &
1279 0 : my_atoms_B, balance_B)
1280 : ELSE
1281 42 : ALLOCATE (my_atoms_B(0))
1282 42 : balance_B = 1.0_dp
1283 : END IF
1284 :
1285 : ! Atoms this rank will process across both phases for rank-0 progress
1286 42 : n_my_atoms = SIZE(my_atoms_A) + SIZE(my_atoms_B)
1287 42 : n_done = 0
1288 42 : next_pct = 25
1289 :
1290 42 : IF (bs_env%unit_nr > 0) THEN
1291 : WRITE (bs_env%unit_nr, '(T2,A,I7,A,I8,A)') &
1292 21 : 'RI-RS Z_lP solver: ', n_small, ' atoms single-rank (BLAS), ', n_big, &
1293 42 : ' atoms distributed'
1294 21 : IF (n_small > 0) WRITE (bs_env%unit_nr, '(T4,A,F18.2)') &
1295 21 : 'estimated single-rank load balance (max/mean cost per rank)', balance_A
1296 21 : IF (n_big > 0) THEN
1297 : WRITE (bs_env%unit_nr, '(T4,A,I44,A)') &
1298 0 : 'distributed subgroup size G', G, ' ranks'
1299 : WRITE (bs_env%unit_nr, '(T4,A,F17.2)') &
1300 0 : 'estimated distributed load balance (max/mean cost per group)', balance_B
1301 : END IF
1302 21 : WRITE (bs_env%unit_nr, '(A)') ' '
1303 : END IF
1304 :
1305 : ! Shared context for the three-center integrals (μν|P) of the RHS build
1306 : CALL gw_3c_ctx_create(ctx_3c, qs_env, bs_env%ri_metric, &
1307 : basis_j=bs_env%basis_set_AO, basis_k=bs_env%basis_set_AO, &
1308 42 : basis_i=bs_env%basis_set_RI)
1309 :
1310 : ! =========================================================================
1311 : ! 3. TWO-PHASE LOOP OVER ATOMS
1312 : ! Phase A processes the "small" atoms with the single-rank BLAS path
1313 : ! Phase B processes the "big" atoms with the distributed ScaLAPACK path
1314 : ! over rank subgroups of size G. phi_local for each atom's cutoff sphere
1315 : ! is built on the fly to avoid replicating a global grid x AO matrix.
1316 : ! =========================================================================
1317 126 : DO iphase = 1, 2
1318 84 : IF (iphase == 1) THEN
1319 42 : use_dist = .FALSE.
1320 42 : atom_j_mepos = 0
1321 42 : atom_j_stride = 1
1322 42 : phase_hi = SIZE(my_atoms_A)
1323 : ELSE
1324 42 : IF (n_big == 0) CYCLE
1325 0 : use_dist = .TRUE.
1326 0 : n_groups = para_env%num_pe/G
1327 0 : my_group = MIN(para_env%mepos/G, n_groups - 1)
1328 0 : ALLOCATE (para_env_sub)
1329 0 : CALL para_env_sub%from_split(para_env, my_group)
1330 0 : CALL cp_blacs_env_create(blacs_env=blacs_env_sub, para_env=para_env_sub)
1331 0 : atom_j_mepos = para_env_sub%mepos
1332 0 : atom_j_stride = para_env_sub%num_pe
1333 : ! All ranks of a subgroup share my_group, hence the identical my_atoms_B list
1334 : ! (the per-atom ScaLAPACK solve is collective over the subgroup).
1335 0 : phase_hi = SIZE(my_atoms_B)
1336 : END IF
1337 :
1338 92 : DO idx = 1, phase_hi
1339 50 : IF (iphase == 1) THEN
1340 50 : atom_P = my_atoms_A(idx)
1341 : ELSE
1342 0 : atom_P = my_atoms_B(idx)
1343 : END IF
1344 :
1345 50 : n_loc_ri = ri_blk_sizes(atom_P)
1346 50 : cutoff_ri = cutoff_ri_per_atom(atom_P)
1347 :
1348 : ! ---------------------------------------------------------------------
1349 : ! A. Sphere-local AO matrix Φ_μ(r_l): select the grid points with
1350 : ! |r_l - R_P| <= cutoff_ri(P), evaluate every AO on them, and drop
1351 : ! points whose largest AO amplitude is below EPS_FILTER.
1352 : ! ---------------------------------------------------------------------
1353 : CALL build_phi_on_sphere(bs_env, particle_set, qs_kind_set, cell, &
1354 : ri_rs_grid_points, atom_P, cutoff_ri, n_ao_total, &
1355 : local_grid_idx, n_local_grid, phi_local, &
1356 50 : ao_col_map, n_ao_used)
1357 :
1358 : ! ---------------------------------------------------------------------
1359 : ! B. Right-hand side D_lP = Σ_μν Φ_μ(r_l) Φ_ν(r_l) (μν|P)
1360 : ! ---------------------------------------------------------------------
1361 200 : ALLOCATE (d_lp_local(n_local_grid, n_loc_ri))
1362 50 : d_lp_local = 0.0_dp
1363 :
1364 : CALL compute_d_lp(bs_env, ctx_3c, phi_local, ao_col_map, d_lp_local, n_local_grid, &
1365 50 : n_loc_ri, atom_P, max_ao_size, atom_j_mepos, atom_j_stride)
1366 :
1367 : ! Reduce per-subgroup-rank partials into the replicated d_lp_local.
1368 : ! Skipped for BLAS path: each rank has the full sum locally.
1369 50 : IF (use_dist) THEN
1370 0 : CALL para_env_sub%sum(d_lp_local)
1371 : END IF
1372 :
1373 : ! ---------------------------------------------------------------------
1374 : ! C. Jacobi conditioning vector d_l = 1/sqrt(D_ll) and, on the BLAS path,
1375 : ! the dense conditioned Gram matrix D'_ll' = d_l D_ll' d_l' + λδ_ll'.
1376 : ! ---------------------------------------------------------------------
1377 150 : ALLOCATE (d_vec_local(n_local_grid))
1378 :
1379 50 : IF (.NOT. use_dist) THEN
1380 : CALL build_gram_jacobi_blas(phi_local, n_local_grid, n_ao_used, &
1381 50 : bs_env%ri_rs%tikhonov, D_local, d_vec_local)
1382 : ELSE
1383 : ! ScaLAPACK path: only d_vec is needed here (= 1/||phi(r_l)||^2);
1384 : ! solve_D_lp_distributed builds its block-cyclic slice of D' internally
1385 : ! with the squared+scaled values, so no dense D_local on this rank.
1386 : CALL build_jacobi_diag_from_phi(phi_local, n_local_grid, n_ao_used, &
1387 0 : d_vec_local)
1388 : END IF
1389 :
1390 : ! ---------------------------------------------------------------------
1391 : ! D. Pre-scale the RHS: D'_lP = d_l * D_lP
1392 : ! ---------------------------------------------------------------------
1393 50 : CALL scale_rows_by_diag(d_lp_local, d_vec_local, n_local_grid, n_loc_ri)
1394 :
1395 : ! ---------------------------------------------------------------------
1396 : ! E. Cholesky solve Σ_l' D'_ll' Z'_l'P = D'_lP
1397 : ! (BLAS dpotrf/dpotrs or ScaLAPACK pdpotrf/pdpotrs)
1398 : ! ---------------------------------------------------------------------
1399 50 : IF (.NOT. use_dist) THEN
1400 50 : CALL timeset(routineN//"_dpotrf", handle_dpotrf)
1401 50 : CALL dpotrf('L', n_local_grid, D_local, n_local_grid, info)
1402 50 : CALL timestop(handle_dpotrf)
1403 50 : CALL timeset(routineN//"_dpotrs", handle_dpotrs)
1404 : CALL dpotrs('L', n_local_grid, n_loc_ri, D_local, n_local_grid, &
1405 50 : d_lp_local, n_local_grid, info)
1406 50 : CALL timestop(handle_dpotrs)
1407 50 : DEALLOCATE (D_local)
1408 : ELSE
1409 : CALL solve_D_lp_distributed(phi_local, d_vec_local, d_lp_local, &
1410 : n_local_grid, n_ao_used, n_loc_ri, &
1411 : bs_env%ri_rs%tikhonov, &
1412 : para_env_sub, blacs_env_sub, &
1413 0 : fm_struct_D, fm_struct_b, fm_D, fm_b, info)
1414 : END IF
1415 :
1416 : ! ---------------------------------------------------------------------
1417 : ! F. Undo the conditioning: Z_lP = d_l * Z'_lP
1418 : ! ---------------------------------------------------------------------
1419 50 : CALL scale_rows_by_diag(d_lp_local, d_vec_local, n_local_grid, n_loc_ri)
1420 :
1421 : ! ---------------------------------------------------------------------
1422 : ! G. Scatter the solved Z columns back into the global sparse mat_Z_lP.
1423 : ! ---------------------------------------------------------------------
1424 50 : do_scatter = .TRUE.
1425 50 : IF (use_dist) do_scatter = (para_env_sub%mepos == 0)
1426 0 : IF (do_scatter) THEN
1427 : CALL scatter_z_columns(mat_Z_lP, d_lp_local, local_grid_idx, n_local_grid, &
1428 : n_loc_ri, atom_P, r_blk_sizes, row_offset, &
1429 50 : bs_env%eps_filter)
1430 : END IF
1431 :
1432 50 : DEALLOCATE (d_vec_local, d_lp_local)
1433 50 : DEALLOCATE (local_grid_idx, phi_local, ao_col_map)
1434 :
1435 : ! Progress based on rank 0 only
1436 50 : n_done = n_done + 1
1437 92 : IF (bs_env%unit_nr > 0 .AND. n_my_atoms > 0) THEN
1438 112 : DO WHILE (next_pct <= 100 .AND. n_done*100 >= next_pct*n_my_atoms)
1439 84 : WRITE (bs_env%unit_nr, '(T2,A,I57,A)') 'Computing Z_lP:', next_pct, ' % done'
1440 91 : next_pct = next_pct + 25
1441 : END DO
1442 : END IF
1443 : END DO ! idx: atoms of this phase owned by this rank / subgroup
1444 :
1445 : ! Tear down the Phase-B subgroup (all ranks created it collectively).
1446 84 : IF (iphase == 2) THEN
1447 0 : CALL cp_blacs_env_release(blacs_env_sub)
1448 0 : CALL para_env_sub%free()
1449 0 : DEALLOCATE (para_env_sub)
1450 : END IF
1451 : END DO ! iphase
1452 :
1453 42 : DEALLOCATE (cutoff_ri_per_atom)
1454 42 : DEALLOCATE (small_list, big_list)
1455 :
1456 42 : CALL gw_3c_ctx_release(ctx_3c)
1457 :
1458 42 : CALL dbcsr_finalize(mat_Z_lP)
1459 :
1460 42 : CALL print_matrix_occupation(mat_Z_lP, 'Z(l,P)', bs_env)
1461 :
1462 42 : IF (bs_env%unit_nr > 0) THEN
1463 21 : WRITE (bs_env%unit_nr, '(A)') ' '
1464 : WRITE (bs_env%unit_nr, '(T2,A,T57,A,F7.1,A)') &
1465 21 : 'Computed Z_lP ', ' Execution time', m_walltime() - t1, ' s'
1466 21 : WRITE (bs_env%unit_nr, '(A)') ' '
1467 : END IF
1468 :
1469 42 : logger => cp_get_default_logger()
1470 :
1471 42 : IF (BTEST(cp_print_key_should_output(logger%iter_info, input, key), cp_p_file)) THEN
1472 6 : CALL dbcsr_binary_write(matrix=mat_Z_lP, filepath=TRIM(bs_env%prefix)//"Z_lP.matrix")
1473 : END IF
1474 :
1475 : END IF
1476 :
1477 44 : DEALLOCATE (row_offset, ri_blk_sizes, col_dist_ri)
1478 44 : CALL dbcsr_distribution_release(dist_Z)
1479 :
1480 44 : DEALLOCATE (ri_rs_grid_points)
1481 :
1482 44 : CALL timestop(handle)
1483 :
1484 220 : END SUBROUTINE compute_coeff_Z_lP
1485 :
1486 : ! **************************************************************************************************
1487 : !> \brief Prints the per-kind maximum RI-RS integration-sphere cutoff table.
1488 : !> \param bs_env ...
1489 : !> \param atomic_kind_set ...
1490 : !> \param particle_set ...
1491 : !> \param cutoff_ri_per_atom ...
1492 : ! **************************************************************************************************
1493 42 : SUBROUTINE print_sphere_cutoff_table(bs_env, atomic_kind_set, particle_set, cutoff_ri_per_atom)
1494 :
1495 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
1496 : TYPE(atomic_kind_type), DIMENSION(:), POINTER :: atomic_kind_set
1497 : TYPE(particle_type), DIMENSION(:), POINTER :: particle_set
1498 : REAL(KIND=dp), DIMENSION(:), INTENT(IN) :: cutoff_ri_per_atom
1499 :
1500 : CHARACTER(LEN=*), PARAMETER :: routineN = 'print_sphere_cutoff_table'
1501 :
1502 : INTEGER :: handle, iatom, ikind, nkind
1503 42 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:) :: cutoff_ri_per_kind
1504 :
1505 42 : CALL timeset(routineN, handle)
1506 :
1507 42 : IF (bs_env%unit_nr <= 0) THEN
1508 21 : CALL timestop(handle)
1509 : RETURN
1510 : END IF
1511 :
1512 21 : nkind = SIZE(atomic_kind_set)
1513 63 : ALLOCATE (cutoff_ri_per_kind(nkind))
1514 21 : cutoff_ri_per_kind(:) = 0.0_dp
1515 :
1516 71 : DO iatom = 1, SIZE(particle_set)
1517 50 : ikind = particle_set(iatom)%atomic_kind%kind_number
1518 71 : cutoff_ri_per_kind(ikind) = MAX(cutoff_ri_per_kind(ikind), cutoff_ri_per_atom(iatom))
1519 : END DO
1520 :
1521 21 : WRITE (bs_env%unit_nr, '(T2,A)') 'Per-kind maximum RI-RS sphere cutoff (Å):'
1522 21 : WRITE (bs_env%unit_nr, '(T4,A4,A14)') 'Kind', 'cutoff (Å)'
1523 50 : DO ikind = 1, nkind
1524 : WRITE (bs_env%unit_nr, '(T4,A4,F14.4)') &
1525 29 : atomic_kind_set(ikind)%element_symbol, &
1526 79 : cutoff_ri_per_kind(ikind)*angstrom
1527 : END DO
1528 21 : WRITE (bs_env%unit_nr, '(A)') ' '
1529 :
1530 21 : DEALLOCATE (cutoff_ri_per_kind)
1531 :
1532 21 : CALL timestop(handle)
1533 :
1534 42 : END SUBROUTINE print_sphere_cutoff_table
1535 :
1536 : ! **************************************************************************************************
1537 : !> \brief Splits the atoms of the Z_lP solve into a single-rank list ("small", Phase A: LAPACK
1538 : !> dpotrf/dpotrs on one rank) and a distributed list ("big", Phase B: ScaLAPACK
1539 : !> pdpotrf/pdpotrs over a rank subgroup of size G), and sizes G.
1540 : !> AUTO mode (N_PROCS_PER_ATOM_Z_LP <= 0, the default): estimate each atom's single-rank
1541 : !> peak memory
1542 : !> peak(P) = 8*n_local_grid(P)^2 (dense Gram matrix D_local)
1543 : !> + 8*n_local_grid(P)*n_ao_used(P) (phi_local)
1544 : !> + 8*n_local_grid(P)*n_RI(P)*(1+n_threads) (d_lp + OMP partials)
1545 : !> and send atoms whose peak exceeds mem_safety * available-memory-per-proc to the
1546 : !> distributed path; G is auto-sized so the biggest atom's distributed D_local (/G)
1547 : !> fits alongside the replicated phi_local + d_lp.
1548 : !> MANUAL mode (> 0): 1 forces the single-rank path for every atom; > 1 keeps the
1549 : !> memory-based classification but forces that fixed subgroup size G.
1550 : !> In every mode G is floored by the ScaLAPACK 32-bit index limit (a local block-cyclic
1551 : !> slice of ~n_local_grid^2/G elements must stay below 2^31 or pdpotrf segfaults).
1552 : !> \param bs_env ...
1553 : !> \param para_env ...
1554 : !> \param ri_rs_grid_points ...
1555 : !> \param particle_set ...
1556 : !> \param cutoff_ri_per_atom ...
1557 : !> \param ri_blk_sizes per-atom ...
1558 : !> \param n_local_grid_atom ...
1559 : !> \param small_list ...
1560 : !> \param n_small ...
1561 : !> \param big_list ...
1562 : !> \param n_big ...
1563 : !> \param G ...
1564 : ! **************************************************************************************************
1565 42 : SUBROUTINE classify_z_lp_atoms(bs_env, para_env, ri_rs_grid_points, particle_set, &
1566 42 : cutoff_ri_per_atom, ri_blk_sizes, &
1567 : n_local_grid_atom, small_list, n_small, big_list, n_big, G)
1568 :
1569 : !$ USE OMP_LIB, ONLY: omp_get_max_threads
1570 :
1571 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
1572 : TYPE(mp_para_env_type), POINTER :: para_env
1573 : REAL(KIND=dp), DIMENSION(:, :), INTENT(IN) :: ri_rs_grid_points
1574 : TYPE(particle_type), DIMENSION(:), POINTER :: particle_set
1575 :
1576 : REAL(KIND=dp), DIMENSION(:), INTENT(IN) :: cutoff_ri_per_atom
1577 : INTEGER, DIMENSION(:), INTENT(IN) :: ri_blk_sizes
1578 : INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT) :: n_local_grid_atom, small_list, big_list
1579 : INTEGER, INTENT(OUT) :: n_small, n_big, G
1580 : CHARACTER(LEN=*), PARAMETER :: routineN = 'classify_z_lp_atoms'
1581 :
1582 : INTEGER :: handle
1583 :
1584 : ! Conservative fraction of measured available memory usable per rank for the Z_lP
1585 : REAL(KIND=dp), PARAMETER :: mem_safety = 0.8_dp
1586 :
1587 : ! ScaLAPACK/BLACS index the per-rank local block-cyclic slice (~n_local_grid^2/G
1588 : ! elements) with 32-bit integers; keep it safely below 2^31 or pdpotrf segfaults.
1589 : REAL(KIND=dp), PARAMETER :: scalapack_loc_limit = 2.0E9_dp
1590 :
1591 : INTEGER :: G_atom, G_int32, G_int32_max, l, &
1592 : n_ao_used_atom, n_grid_total, &
1593 : n_local_grid, natom, nthreads_cls, &
1594 : P_loop_atom
1595 : LOGICAL :: auto_mode
1596 : REAL(KIND=dp) :: budget_bytes, cutoff_ri, dlp_bytes, &
1597 : mem_avail_GB, ng, nri, peak_bytes, &
1598 : phi_bytes
1599 : REAL(KIND=dp), DIMENSION(3) :: pos_P
1600 :
1601 42 : CALL timeset(routineN, handle)
1602 :
1603 42 : natom = SIZE(particle_set)
1604 42 : n_grid_total = SIZE(ri_rs_grid_points, 2)
1605 :
1606 : ! Per-atom sphere size: n_local_grid(P) = number of l with |r_l - R_P| <= cutoff_ri(P).
1607 : ! It sets both the memory footprint (D_local is n_local_grid^2) and the solve
1608 : ! cost (~n_local_grid^3), so it drives classification and the LPT load balancing.
1609 210 : ALLOCATE (n_local_grid_atom(natom), small_list(natom), big_list(natom))
1610 142 : DO P_loop_atom = 1, natom
1611 400 : pos_P(:) = particle_set(P_loop_atom)%r(:)
1612 100 : cutoff_ri = cutoff_ri_per_atom(P_loop_atom)
1613 100 : n_local_grid = 0
1614 37524 : DO l = 1, n_grid_total
1615 149796 : IF (SUM((ri_rs_grid_points(1:3, l) - pos_P(1:3))**2) <= cutoff_ri**2) THEN
1616 36038 : n_local_grid = n_local_grid + 1
1617 : END IF
1618 : END DO
1619 142 : n_local_grid_atom(P_loop_atom) = n_local_grid
1620 : END DO
1621 :
1622 42 : nthreads_cls = 1
1623 42 : !$ nthreads_cls = omp_get_max_threads()
1624 : ! N_PROCS_PER_ATOM_Z_LP: -1 (default) = AUTO (classify by memory, auto-size G);
1625 : ! 1 = force single-rank BLAS for every atom; >1 = classify by memory but use this
1626 : ! fixed subgroup size G for the big atoms.
1627 42 : auto_mode = (bs_env%ri_rs%n_procs_per_atom_z_lp <= 0)
1628 42 : CALL ri_rs_mem_avail_per_proc_GB(bs_env, mem_avail_GB) ! collective over all ranks
1629 42 : budget_bytes = mem_safety*mem_avail_GB*1.0E9_dp
1630 :
1631 42 : n_small = 0
1632 42 : n_big = 0
1633 42 : G = 1
1634 42 : G_atom = 1 ! max G a big atom needs (memory + ScaLAPACK int32 floor)
1635 42 : G_int32_max = 1 ! max ScaLAPACK-int32 floor over the distributed atoms
1636 42 : IF (bs_env%ri_rs%n_procs_per_atom_z_lp == 1) THEN
1637 : ! Force single-rank BLAS for every atom.
1638 0 : DO P_loop_atom = 1, natom
1639 0 : n_small = n_small + 1
1640 0 : small_list(n_small) = P_loop_atom
1641 : END DO
1642 42 : ELSE IF (mem_avail_GB <= 0.0_dp) THEN
1643 : ! No /proc/meminfo => cannot size by memory.
1644 0 : IF (auto_mode) THEN
1645 0 : IF (bs_env%unit_nr > 0) THEN
1646 0 : CPWARN("RI-RS Z_lP: no meminfo; single-rank solve for all atoms")
1647 : END IF
1648 0 : DO P_loop_atom = 1, natom
1649 0 : n_small = n_small + 1
1650 0 : small_list(n_small) = P_loop_atom
1651 : END DO
1652 : ELSE
1653 : ! Fixed G, no meminfo: distribute all atoms; still floor G by the int32 limit.
1654 0 : DO P_loop_atom = 1, natom
1655 0 : ng = REAL(n_local_grid_atom(P_loop_atom), dp)
1656 0 : G_int32_max = MAX(G_int32_max, CEILING(ng*ng/scalapack_loc_limit))
1657 0 : n_big = n_big + 1
1658 0 : big_list(n_big) = P_loop_atom
1659 : END DO
1660 0 : G = MIN(bs_env%ri_rs%n_procs_per_atom_z_lp, para_env%num_pe)
1661 0 : IF (G < G_int32_max) THEN
1662 0 : G = MIN(G_int32_max, para_env%num_pe)
1663 0 : IF (bs_env%unit_nr > 0) THEN
1664 0 : CPWARN("RI-RS Z_lP: raised G to avoid ScaLAPACK overflow")
1665 : END IF
1666 : END IF
1667 : END IF
1668 : ELSE
1669 : ! Classify by memory: peak (D_local + phi_local + d_lp) vs budget. Small -> BLAS,
1670 : ! big -> distributed. Same classification for AUTO and fixed-G modes.
1671 142 : DO P_loop_atom = 1, natom
1672 100 : ng = REAL(n_local_grid_atom(P_loop_atom), dp)
1673 100 : nri = REAL(ri_blk_sizes(P_loop_atom), dp)
1674 : CALL get_n_ao_in_sphere(bs_env, particle_set, P_loop_atom, &
1675 100 : cutoff_ri_per_atom(P_loop_atom), n_ao_used_atom)
1676 100 : phi_bytes = 8.0_dp*ng*REAL(n_ao_used_atom, dp)
1677 100 : dlp_bytes = 8.0_dp*ng*nri*REAL(1 + nthreads_cls, dp)
1678 100 : peak_bytes = 8.0_dp*ng*ng + phi_bytes + dlp_bytes
1679 142 : IF (peak_bytes <= budget_bytes) THEN
1680 100 : n_small = n_small + 1
1681 100 : small_list(n_small) = P_loop_atom
1682 : ELSE
1683 0 : n_big = n_big + 1
1684 0 : big_list(n_big) = P_loop_atom
1685 : ! G must satisfy BOTH: (a) memory — distributed D_local (/G) fits next to the
1686 : ! replicated phi_local + d_lp; (b) ScaLAPACK — local ~ng^2/G below the int32 limit.
1687 0 : G_int32 = CEILING(ng*ng/scalapack_loc_limit)
1688 0 : G_int32_max = MAX(G_int32_max, G_int32)
1689 : G_atom = MAX(G_atom, G_int32, &
1690 0 : CEILING(8.0_dp*ng*ng/MAX(budget_bytes - phi_bytes - dlp_bytes, 1.0_dp)))
1691 : END IF
1692 : END DO
1693 42 : IF (n_big > 0) THEN
1694 0 : IF (auto_mode) THEN
1695 : ! Auto-size G from the most demanding big atom.
1696 0 : IF (G_atom > para_env%num_pe) THEN
1697 : CALL cp_abort(__LOCATION__, &
1698 : "RI-RS Z_lP: an atom is too large to fit even when "// &
1699 : "distributed over all ranks. Add nodes, use fewer MPI ranks "// &
1700 : "per node, lower CUTOFF_RADIUS_RL_RI, or raise EPS_FILTER "// &
1701 0 : "for more grid screening.")
1702 : END IF
1703 0 : G = MIN(MAX(G_atom, 2), para_env%num_pe)
1704 : ELSE
1705 : ! Fixed G from the keyword. Hard-floor by the ScaLAPACK int32 limit (below it
1706 : ! pdpotrf segfaults); warn if it is still below the memory recommendation.
1707 0 : G = MIN(bs_env%ri_rs%n_procs_per_atom_z_lp, para_env%num_pe)
1708 0 : IF (G < G_int32_max) THEN
1709 0 : G = MIN(G_int32_max, para_env%num_pe)
1710 0 : IF (bs_env%unit_nr > 0) THEN
1711 0 : CPWARN("RI-RS Z_lP: raised G to avoid ScaLAPACK overflow")
1712 : END IF
1713 0 : ELSE IF (G < G_atom .AND. bs_env%unit_nr > 0) THEN
1714 0 : CPWARN("RI-RS Z_lP: N_PROCS_PER_ATOM_Z_LP too small for the largest atom")
1715 : END IF
1716 : END IF
1717 : END IF
1718 : END IF
1719 :
1720 42 : CALL timestop(handle)
1721 :
1722 84 : END SUBROUTINE classify_z_lp_atoms
1723 :
1724 : ! **************************************************************************************************
1725 : !> \brief Number of AO basis functions that can be non-zero inside the RI-RS integration sphere
1726 : !> \param bs_env ...
1727 : !> \param particle_set ...
1728 : !> \param atom_P ...
1729 : !> \param cutoff_ri ...
1730 : !> \param n_ao_used ...
1731 : ! **************************************************************************************************
1732 190 : SUBROUTINE get_n_ao_in_sphere(bs_env, particle_set, atom_P, cutoff_ri, n_ao_used)
1733 :
1734 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
1735 : TYPE(particle_type), DIMENSION(:), POINTER :: particle_set
1736 : INTEGER, INTENT(IN) :: atom_P
1737 : REAL(KIND=dp), INTENT(IN) :: cutoff_ri
1738 : INTEGER, INTENT(OUT) :: n_ao_used
1739 :
1740 : INTEGER :: source_atom
1741 :
1742 190 : n_ao_used = 0
1743 672 : DO source_atom = 1, SIZE(particle_set)
1744 1928 : IF (NORM2(particle_set(source_atom)%r(:) - particle_set(atom_P)%r(:)) > &
1745 : bs_env%ri_rs%radius_ao_per_atom(source_atom) + cutoff_ri) CYCLE
1746 : n_ao_used = n_ao_used + bs_env%i_ao_end_from_atom(source_atom) - &
1747 672 : bs_env%i_ao_start_from_atom(source_atom) + 1
1748 : END DO
1749 :
1750 190 : END SUBROUTINE get_n_ao_in_sphere
1751 :
1752 : ! **************************************************************************************************
1753 : !> \brief Builds the sphere-local AO matrix phi_local(l, μ) = Φ_μ(r_l) for one RI atom P
1754 : !> \param bs_env ...
1755 : !> \param particle_set ...
1756 : !> \param qs_kind_set ...
1757 : !> \param cell ...
1758 : !> \param ri_rs_grid_points ...
1759 : !> \param atom_P ...
1760 : !> \param cutoff_ri ...
1761 : !> \param n_ao_total ...
1762 : !> \param local_grid_idx ...
1763 : !> \param n_local_grid ...
1764 : !> \param phi_local ...
1765 : !> \param ao_col_map ...
1766 : !> \param n_ao_used ...
1767 : ! **************************************************************************************************
1768 50 : SUBROUTINE build_phi_on_sphere(bs_env, particle_set, qs_kind_set, cell, ri_rs_grid_points, &
1769 : atom_P, cutoff_ri, n_ao_total, local_grid_idx, n_local_grid, &
1770 : phi_local, ao_col_map, n_ao_used)
1771 :
1772 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
1773 : TYPE(particle_type), DIMENSION(:), POINTER :: particle_set
1774 : TYPE(qs_kind_type), DIMENSION(:), POINTER :: qs_kind_set
1775 : TYPE(cell_type), POINTER :: cell
1776 : REAL(KIND=dp), DIMENSION(:, :), INTENT(IN) :: ri_rs_grid_points
1777 : INTEGER, INTENT(IN) :: atom_P
1778 : REAL(KIND=dp), INTENT(IN) :: cutoff_ri
1779 : INTEGER, INTENT(IN) :: n_ao_total
1780 : INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT) :: local_grid_idx
1781 : INTEGER, INTENT(OUT) :: n_local_grid
1782 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
1783 : INTENT(OUT) :: phi_local
1784 : INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT) :: ao_col_map
1785 : INTEGER, INTENT(OUT) :: n_ao_used
1786 :
1787 : CHARACTER(LEN=*), PARAMETER :: routineN = 'build_phi_on_sphere'
1788 :
1789 : INTEGER :: col_end, col_start, handle, j, k, l, &
1790 : loc_idx, n_grid_total, n_keep, &
1791 : source_atom
1792 : REAL(KIND=dp) :: d_sP, dist, r2_threshold
1793 50 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:) :: w_pt
1794 50 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :) :: phi_keep, sphere_grid
1795 : REAL(KIND=dp), DIMENSION(3) :: pos_P
1796 :
1797 50 : CALL timeset(routineN, handle)
1798 :
1799 50 : n_grid_total = SIZE(ri_rs_grid_points, 2)
1800 200 : pos_P(:) = particle_set(atom_P)%r(:)
1801 :
1802 50 : n_local_grid = 0
1803 18762 : DO l = 1, n_grid_total
1804 74848 : dist = SQRT(SUM((ri_rs_grid_points(1:3, l) - pos_P(1:3))**2))
1805 18762 : IF (dist <= cutoff_ri) n_local_grid = n_local_grid + 1
1806 : END DO
1807 :
1808 150 : ALLOCATE (local_grid_idx(n_local_grid))
1809 :
1810 50 : n_local_grid = 0
1811 18762 : DO l = 1, n_grid_total
1812 74848 : dist = SQRT(SUM((ri_rs_grid_points(1:3, l) - pos_P(1:3))**2))
1813 18762 : IF (dist <= cutoff_ri) THEN
1814 18019 : n_local_grid = n_local_grid + 1
1815 18019 : local_grid_idx(n_local_grid) = l
1816 : END IF
1817 : END DO
1818 :
1819 150 : ALLOCATE (sphere_grid(3, n_local_grid))
1820 18069 : DO loc_idx = 1, n_local_grid
1821 72126 : sphere_grid(:, loc_idx) = ri_rs_grid_points(:, local_grid_idx(loc_idx))
1822 : END DO
1823 :
1824 : ! Only AOs on atoms that reach into the sphere can be non-zero here
1825 150 : ALLOCATE (ao_col_map(n_ao_total))
1826 50 : ao_col_map(:) = 0
1827 50 : n_ao_used = 0
1828 174 : DO source_atom = 1, SIZE(particle_set)
1829 496 : d_sP = NORM2(particle_set(source_atom)%r(:) - pos_P(:))
1830 124 : IF (d_sP > bs_env%ri_rs%radius_ao_per_atom(source_atom) + cutoff_ri) CYCLE
1831 :
1832 646 : DO j = bs_env%i_ao_start_from_atom(source_atom), bs_env%i_ao_end_from_atom(source_atom)
1833 472 : n_ao_used = n_ao_used + 1
1834 596 : ao_col_map(j) = n_ao_used
1835 : END DO
1836 : END DO
1837 :
1838 200 : ALLOCATE (phi_local(n_local_grid, n_ao_used))
1839 50 : phi_local = 0.0_dp
1840 :
1841 174 : DO source_atom = 1, SIZE(particle_set)
1842 496 : d_sP = NORM2(particle_set(source_atom)%r(:) - pos_P(:))
1843 124 : IF (d_sP > bs_env%ri_rs%radius_ao_per_atom(source_atom) + cutoff_ri) CYCLE
1844 :
1845 124 : col_start = ao_col_map(bs_env%i_ao_start_from_atom(source_atom))
1846 124 : col_end = ao_col_map(bs_env%i_ao_end_from_atom(source_atom))
1847 : ! A positive CUTOFF_RADIUS_RI_AO overrides the per-atom Gaussian radius
1848 : ! with a user-defined hard cutoff.
1849 124 : IF (bs_env%ri_rs%cutoff_radius_ri_ao > 0.0_dp) THEN
1850 9 : r2_threshold = bs_env%ri_rs%cutoff_radius_ri_ao**2
1851 : ELSE
1852 115 : r2_threshold = bs_env%ri_rs%radius_ao_per_atom(source_atom)**2
1853 : END IF
1854 :
1855 : CALL fill_phi_for_atom(phi_local(:, col_start:col_end), sphere_grid, &
1856 : n_local_grid, source_atom, particle_set, qs_kind_set, &
1857 174 : cell, r2_threshold)
1858 : END DO
1859 :
1860 50 : DEALLOCATE (sphere_grid)
1861 :
1862 50 : IF (n_local_grid > 0) THEN
1863 150 : ALLOCATE (w_pt(n_local_grid))
1864 : !$OMP PARALLEL DO DEFAULT(NONE) &
1865 : !$OMP SHARED(n_local_grid, n_ao_used, phi_local, w_pt) &
1866 50 : !$OMP PRIVATE(l, j) SCHEDULE(STATIC)
1867 : DO l = 1, n_local_grid
1868 : w_pt(l) = 0.0_dp
1869 : DO j = 1, n_ao_used
1870 : w_pt(l) = MAX(w_pt(l), ABS(phi_local(l, j)))
1871 : END DO
1872 : END DO
1873 : !$OMP END PARALLEL DO
1874 18069 : n_keep = COUNT(w_pt > bs_env%eps_filter)
1875 50 : IF (n_keep < n_local_grid) THEN
1876 0 : ALLOCATE (phi_keep(n_keep, n_ao_used))
1877 0 : k = 0
1878 0 : DO l = 1, n_local_grid
1879 0 : IF (w_pt(l) > bs_env%eps_filter) THEN
1880 0 : k = k + 1
1881 0 : phi_keep(k, :) = phi_local(l, :)
1882 0 : local_grid_idx(k) = local_grid_idx(l)
1883 : END IF
1884 : END DO
1885 0 : CALL MOVE_ALLOC(phi_keep, phi_local)
1886 0 : n_local_grid = n_keep
1887 : END IF
1888 50 : DEALLOCATE (w_pt)
1889 : END IF
1890 :
1891 50 : CALL timestop(handle)
1892 :
1893 100 : END SUBROUTINE build_phi_on_sphere
1894 :
1895 : ! **************************************************************************************************
1896 : !> \brief Builds the dense Jacobi-conditioned Gram matrix and the conditioning vector for the
1897 : !> single-rank (BLAS/LAPACK) Z_lP solve:
1898 : !> D_ll' = [ Σ_μ Φ_μ(r_l) Φ_μ(r_l') ]² (dsyrk of phi_local, then squared)
1899 : !> d_l = 1 / sqrt(D_ll)
1900 : !> D'_ll' = d_l D_ll' d_l' + λ δ_ll'
1901 : !> Only the lower triangle is referenced by the subsequent dpotrf('L')
1902 : !> \param phi_local ...
1903 : !> \param n_local_grid ...
1904 : !> \param n_ao_used ...
1905 : !> \param tikhonov ...
1906 : !> \param D_local ...
1907 : !> \param d_vec_local ...
1908 : ! **************************************************************************************************
1909 50 : SUBROUTINE build_gram_jacobi_blas(phi_local, n_local_grid, n_ao_used, tikhonov, D_local, &
1910 50 : d_vec_local)
1911 :
1912 : REAL(KIND=dp), DIMENSION(:, :), INTENT(IN) :: phi_local
1913 : INTEGER, INTENT(IN) :: n_local_grid, n_ao_used
1914 : REAL(KIND=dp), INTENT(IN) :: tikhonov
1915 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
1916 : INTENT(OUT) :: D_local
1917 : REAL(KIND=dp), DIMENSION(:), INTENT(OUT) :: d_vec_local
1918 :
1919 : CHARACTER(LEN=*), PARAMETER :: routineN = 'build_gram_jacobi_blas'
1920 :
1921 : INTEGER :: handle, handle_dsyrk, i, j
1922 :
1923 50 : CALL timeset(routineN, handle)
1924 :
1925 200 : ALLOCATE (D_local(n_local_grid, n_local_grid))
1926 50 : D_local = 0.0_dp
1927 :
1928 : ! D_ll' = Σ_μ Φ_μ(r_l) Φ_μ(r_l') (lower triangle only)
1929 50 : CALL timeset(routineN//"_dsyrk", handle_dsyrk)
1930 : CALL dsyrk("L", "N", n_local_grid, n_ao_used, 1.0_dp, phi_local, &
1931 50 : n_local_grid, 0.0_dp, D_local, n_local_grid)
1932 50 : CALL timestop(handle_dsyrk)
1933 :
1934 : ! Diagonal: square, derive d_l = 1/sqrt(D_ll), scale, add Tikhonov λ
1935 : !$OMP PARALLEL DO DEFAULT(NONE) &
1936 : !$OMP SHARED(n_local_grid, D_local, d_vec_local, tikhonov) &
1937 : !$OMP PRIVATE(i) &
1938 50 : !$OMP SCHEDULE(STATIC)
1939 : DO i = 1, n_local_grid
1940 : D_local(i, i) = D_local(i, i)**2
1941 : d_vec_local(i) = 1.0_dp/SQRT(MAX(D_local(i, i), 1.0E-16_dp))
1942 : D_local(i, i) = (D_local(i, i)*d_vec_local(i)**2) + tikhonov
1943 : END DO
1944 : !$OMP END PARALLEL DO
1945 :
1946 : ! Off-diagonal: D'_ll' = d_l D_ll'^2 d_l' (mirror to the upper triangle)
1947 : !$OMP PARALLEL DO DEFAULT(NONE) &
1948 : !$OMP SHARED(n_local_grid, D_local, d_vec_local) &
1949 : !$OMP PRIVATE(j, i) &
1950 50 : !$OMP SCHEDULE(DYNAMIC)
1951 : DO j = 1, n_local_grid
1952 : DO i = j + 1, n_local_grid
1953 : D_local(i, j) = D_local(i, j)**2
1954 : D_local(i, j) = D_local(i, j)*d_vec_local(i)*d_vec_local(j)
1955 : D_local(j, i) = D_local(i, j)
1956 : END DO
1957 : END DO
1958 : !$OMP END PARALLEL DO
1959 :
1960 50 : CALL timestop(handle)
1961 :
1962 100 : END SUBROUTINE build_gram_jacobi_blas
1963 :
1964 : ! **************************************************************************************************
1965 : !> \brief Computes the Jacobi conditioning vector directly from phi for the distributed
1966 : !> (ScaLAPACK) Z_lP solve: d_l = 1 / Σ_μ Φ_μ(r_l)² = 1/sqrt(D_ll), without forming
1967 : !> the Gram matrix.
1968 : !> \param phi_local ...
1969 : !> \param n_local_grid ...
1970 : !> \param n_ao_used ...
1971 : !> \param d_vec_local ...
1972 : ! **************************************************************************************************
1973 0 : SUBROUTINE build_jacobi_diag_from_phi(phi_local, n_local_grid, n_ao_used, d_vec_local)
1974 :
1975 : REAL(KIND=dp), DIMENSION(:, :), INTENT(IN) :: phi_local
1976 : INTEGER, INTENT(IN) :: n_local_grid, n_ao_used
1977 : REAL(KIND=dp), DIMENSION(:), INTENT(OUT) :: d_vec_local
1978 :
1979 : CHARACTER(LEN=*), PARAMETER :: routineN = 'build_jacobi_diag_from_phi'
1980 :
1981 : INTEGER :: handle, i, j
1982 :
1983 0 : CALL timeset(routineN, handle)
1984 :
1985 : !$OMP PARALLEL DO DEFAULT(NONE) &
1986 : !$OMP SHARED(n_local_grid, n_ao_used, phi_local, d_vec_local) &
1987 : !$OMP PRIVATE(i, j) &
1988 0 : !$OMP SCHEDULE(STATIC)
1989 : DO i = 1, n_local_grid
1990 : d_vec_local(i) = 0.0_dp
1991 : DO j = 1, n_ao_used
1992 : d_vec_local(i) = d_vec_local(i) + phi_local(i, j)*phi_local(i, j)
1993 : END DO
1994 : d_vec_local(i) = 1.0_dp/MAX(d_vec_local(i), 1.0E-16_dp)
1995 : END DO
1996 : !$OMP END PARALLEL DO
1997 :
1998 0 : CALL timestop(handle)
1999 :
2000 0 : END SUBROUTINE build_jacobi_diag_from_phi
2001 :
2002 : ! **************************************************************************************************
2003 : !> \brief Scales every row of a matrix by the corresponding diagonal entry,
2004 : !> A(l, :) <- d_l * A(l, :). Used in Z_lP solve: to pre-scale the RHS
2005 : !> (D'_lP = d_l D_lP) and to undo the conditioning of the solution (Z_lP = d_l Z'_lP).
2006 : !> \param d_lp_local ...
2007 : !> \param d_vec_local ...
2008 : !> \param n_local_grid ...
2009 : !> \param n_loc_ri ...
2010 : ! **************************************************************************************************
2011 100 : SUBROUTINE scale_rows_by_diag(d_lp_local, d_vec_local, n_local_grid, n_loc_ri)
2012 :
2013 : REAL(KIND=dp), DIMENSION(:, :), INTENT(INOUT) :: d_lp_local
2014 : REAL(KIND=dp), DIMENSION(:), INTENT(IN) :: d_vec_local
2015 : INTEGER, INTENT(IN) :: n_local_grid, n_loc_ri
2016 :
2017 : CHARACTER(LEN=*), PARAMETER :: routineN = 'scale_rows_by_diag'
2018 :
2019 : INTEGER :: handle, i, j_ri
2020 :
2021 100 : CALL timeset(routineN, handle)
2022 :
2023 : !$OMP PARALLEL DO DEFAULT(NONE) &
2024 : !$OMP SHARED(n_loc_ri, n_local_grid, d_lp_local, d_vec_local) &
2025 : !$OMP PRIVATE(j_ri, i) &
2026 100 : !$OMP SCHEDULE(STATIC)
2027 : DO j_ri = 1, n_loc_ri
2028 : DO i = 1, n_local_grid
2029 : d_lp_local(i, j_ri) = d_lp_local(i, j_ri)*d_vec_local(i)
2030 : END DO
2031 : END DO
2032 : !$OMP END PARALLEL DO
2033 :
2034 100 : CALL timestop(handle)
2035 :
2036 100 : END SUBROUTINE scale_rows_by_diag
2037 :
2038 : ! **************************************************************************************************
2039 : !> \brief Scatters the solved Z columns of one atom P from the dense sphere-local solution back
2040 : !> into the global sparse mat_Z_lP
2041 : !> \param mat_Z_lP ...
2042 : !> \param d_lp_local ...
2043 : !> \param local_grid_idx ...
2044 : !> \param n_local_grid ...
2045 : !> \param n_loc_ri ...
2046 : !> \param atom_P ...
2047 : !> \param r_blk_sizes ...
2048 : !> \param row_offset ...
2049 : !> \param eps_filter ...
2050 : ! **************************************************************************************************
2051 50 : SUBROUTINE scatter_z_columns(mat_Z_lP, d_lp_local, local_grid_idx, n_local_grid, n_loc_ri, &
2052 50 : atom_P, r_blk_sizes, row_offset, eps_filter)
2053 :
2054 : TYPE(dbcsr_type), INTENT(INOUT) :: mat_Z_lP
2055 : REAL(KIND=dp), DIMENSION(:, :), INTENT(IN) :: d_lp_local
2056 : INTEGER, DIMENSION(:), INTENT(IN) :: local_grid_idx
2057 : INTEGER, INTENT(IN) :: n_local_grid, n_loc_ri, atom_P
2058 : INTEGER, DIMENSION(:), INTENT(IN) :: r_blk_sizes, row_offset
2059 : REAL(KIND=dp), INTENT(IN) :: eps_filter
2060 :
2061 : CHARACTER(LEN=*), PARAMETER :: routineN = 'scatter_z_columns'
2062 :
2063 : INTEGER :: current_chunk_size, g_pt, handle, i_blk, &
2064 : loc_ptr, r_end, r_start
2065 50 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :) :: Z_blk
2066 :
2067 50 : CALL timeset(routineN, handle)
2068 :
2069 870 : ALLOCATE (Z_blk(MAXVAL(r_blk_sizes), n_loc_ri))
2070 50 : loc_ptr = 1
2071 :
2072 720 : DO i_blk = 1, SIZE(r_blk_sizes)
2073 670 : r_start = row_offset(i_blk) + 1
2074 670 : r_end = row_offset(i_blk) + r_blk_sizes(i_blk)
2075 670 : current_chunk_size = r_blk_sizes(i_blk)
2076 :
2077 670 : Z_blk = 0.0_dp
2078 :
2079 : ! Copy the sphere points whose global grid index falls inside this block
2080 18689 : DO WHILE (loc_ptr <= n_local_grid)
2081 18635 : g_pt = local_grid_idx(loc_ptr)
2082 18635 : IF (g_pt > r_end) EXIT
2083 254814 : Z_blk(g_pt - r_start + 1, 1:n_loc_ri) = d_lp_local(loc_ptr, 1:n_loc_ri)
2084 18635 : loc_ptr = loc_ptr + 1
2085 : END DO
2086 :
2087 250858 : IF (MAXVAL(ABS(Z_blk(1:current_chunk_size, 1:n_loc_ri))) > eps_filter) THEN
2088 : CALL dbcsr_put_block(mat_Z_lP, row=i_blk, col=atom_P, &
2089 658 : block=Z_blk(1:current_chunk_size, 1:n_loc_ri))
2090 : END IF
2091 : END DO
2092 :
2093 50 : DEALLOCATE (Z_blk)
2094 :
2095 50 : CALL timestop(handle)
2096 :
2097 50 : END SUBROUTINE scatter_z_columns
2098 :
2099 : ! **************************************************************************************************
2100 : !> \brief LPT (longest-processing-time) assignment of the Z_lP atoms to workers (MPI ranks in
2101 : !> Phase A, rank subgroups in Phase B): sort by estimated solve cost n_local_grid^3
2102 : !> (the per-atom Cholesky dominates; the n^2 assembly terms order the atoms the same
2103 : !> way) and greedily give each atom to the least-loaded worker.
2104 : !> \param atom_list ...
2105 : !> \param n_atoms ...
2106 : !> \param n_local_grid_atom ...
2107 : !> \param n_workers ...
2108 : !> \param my_worker ...
2109 : !> \param my_atoms ...
2110 : !> \param max_over_mean ...
2111 : ! **************************************************************************************************
2112 42 : SUBROUTINE lpt_assign_atoms(atom_list, n_atoms, n_local_grid_atom, n_workers, my_worker, &
2113 : my_atoms, max_over_mean)
2114 :
2115 : INTEGER, DIMENSION(:), INTENT(IN) :: atom_list
2116 : INTEGER, INTENT(IN) :: n_atoms
2117 : INTEGER, DIMENSION(:), INTENT(IN) :: n_local_grid_atom
2118 : INTEGER, INTENT(IN) :: n_workers, my_worker
2119 : INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT) :: my_atoms
2120 : REAL(KIND=dp), INTENT(OUT) :: max_over_mean
2121 :
2122 : CHARACTER(LEN=*), PARAMETER :: routineN = 'lpt_assign_atoms'
2123 :
2124 : INTEGER :: handle, i, iw, n_mine, w_min
2125 42 : INTEGER, ALLOCATABLE, DIMENSION(:) :: mine_tmp, perm
2126 42 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:) :: cost, load
2127 :
2128 42 : CALL timeset(routineN, handle)
2129 :
2130 42 : max_over_mean = 1.0_dp
2131 42 : IF (n_atoms <= 0) THEN
2132 0 : ALLOCATE (my_atoms(0))
2133 0 : CALL timestop(handle)
2134 0 : RETURN
2135 : END IF
2136 :
2137 336 : ALLOCATE (cost(n_atoms), perm(n_atoms), mine_tmp(n_atoms), load(n_workers))
2138 142 : DO i = 1, n_atoms
2139 142 : cost(i) = REAL(n_local_grid_atom(atom_list(i)), dp)**3
2140 : END DO
2141 42 : CALL sort(cost, n_atoms, perm) ! ascending; walk backwards for largest-first
2142 :
2143 42 : load(:) = 0.0_dp
2144 42 : n_mine = 0
2145 142 : DO i = n_atoms, 1, -1
2146 : w_min = 1
2147 200 : DO iw = 2, n_workers
2148 200 : IF (load(iw) < load(w_min)) w_min = iw
2149 : END DO
2150 100 : load(w_min) = load(w_min) + cost(i)
2151 142 : IF (w_min - 1 == my_worker) THEN
2152 50 : n_mine = n_mine + 1
2153 50 : mine_tmp(n_mine) = atom_list(perm(i))
2154 : END IF
2155 : END DO
2156 :
2157 126 : ALLOCATE (my_atoms(n_mine))
2158 92 : my_atoms(:) = mine_tmp(1:n_mine)
2159 294 : IF (SUM(load) > 0.0_dp) max_over_mean = MAXVAL(load)*REAL(n_workers, dp)/SUM(load)
2160 :
2161 42 : CALL timestop(handle)
2162 :
2163 84 : END SUBROUTINE lpt_assign_atoms
2164 :
2165 : ! **************************************************************************************************
2166 : !> \brief Computes the dense localized RHS d_lp(l,P) = Σ_{μν} Φ_μ(r_l)·Φ_ν(r_l)·(μν|P) for one
2167 : !> RI atom P, OMP-threaded over (atom_j, atom_k) AO-pair blocks: per thread, build the 3c
2168 : !> block, then contract grid-chunked pair densities into a private d_lp partial; partials
2169 : !> are reduced into d_lp at the end.
2170 : !> Pair screening is handled inside build_3c_integral_block_ctx via the `screened` output.
2171 : !> \param bs_env ...
2172 : !> \param ctx ...
2173 : !> \param phi_val ...
2174 : !> \param ao_col_map ...
2175 : !> \param d_lp ...
2176 : !> \param n_grid_total ...
2177 : !> \param n_loc_ri ...
2178 : !> \param atom_P ...
2179 : !> \param max_ao_size ...
2180 : !> \param atom_j_mepos ...
2181 : !> \param atom_j_stride ...
2182 : ! **************************************************************************************************
2183 50 : SUBROUTINE compute_d_lp(bs_env, ctx, phi_val, ao_col_map, d_lp, n_grid_total, n_loc_ri, atom_P, &
2184 : max_ao_size, atom_j_mepos, atom_j_stride)
2185 :
2186 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
2187 : TYPE(gw_3c_ctx_type), INTENT(IN) :: ctx
2188 : REAL(KIND=dp), DIMENSION(:, :), INTENT(IN) :: phi_val
2189 : INTEGER, DIMENSION(:), INTENT(IN) :: ao_col_map
2190 : INTEGER, INTENT(IN) :: n_grid_total, n_loc_ri
2191 : REAL(KIND=dp), INTENT(INOUT) :: d_lp(n_grid_total, n_loc_ri)
2192 : INTEGER, INTENT(IN) :: atom_P, max_ao_size, atom_j_mepos, &
2193 : atom_j_stride
2194 :
2195 : CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_d_lp'
2196 : INTEGER, PARAMETER :: grid_chunk = 1024
2197 :
2198 : INTEGER :: atom_j, atom_k, c, handle, handle_dgemm, &
2199 : j, jk_idx, jsize, jstart, k, ksize, &
2200 : kstart, l, l0, n_grid_pair, point, ri
2201 50 : INTEGER, ALLOCATABLE :: grid_index(:)
2202 : LOGICAL :: screened
2203 50 : LOGICAL, ALLOCATABLE :: skip_grid_point(:, :)
2204 : REAL(KIND=dp) :: pair_factor
2205 50 : REAL(KIND=dp), ALLOCATABLE :: grid_result(:, :)
2206 50 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :) :: d_lp_prv, int_2d_prv, rho_chunk
2207 50 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :) :: int_3c_prv
2208 50 : TYPE(gw_3c_ws_type) :: ws
2209 :
2210 50 : CALL timeset(routineN, handle)
2211 :
2212 : !$OMP PARALLEL DEFAULT(NONE) &
2213 : !$OMP SHARED(skip_grid_point, bs_env, ctx, phi_val, ao_col_map, d_lp, n_grid_total, n_loc_ri, atom_P, max_ao_size, &
2214 : !$OMP atom_j_mepos, atom_j_stride) &
2215 : !$OMP PRIVATE(grid_index, grid_result, n_grid_pair, point, pair_factor, &
2216 : !$OMP atom_j, atom_k, c, handle_dgemm, j, jk_idx, jsize, jstart, k, ksize, kstart, &
2217 50 : !$OMP l, l0, ri, screened, d_lp_prv, int_2d_prv, rho_chunk, int_3c_prv, ws)
2218 :
2219 : CALL gw_3c_ws_create(ws, ctx)
2220 : ALLOCATE (int_3c_prv(max_ao_size, max_ao_size, n_loc_ri))
2221 : ALLOCATE (int_2d_prv(max_ao_size*max_ao_size, n_loc_ri))
2222 : ALLOCATE (rho_chunk(grid_chunk, max_ao_size*max_ao_size))
2223 : ALLOCATE (d_lp_prv(n_grid_total, n_loc_ri))
2224 : ALLOCATE (grid_index(n_grid_total), grid_result(grid_chunk, n_loc_ri))
2225 : d_lp_prv(:, :) = 0.0_dp
2226 :
2227 : !$OMP SINGLE
2228 : ALLOCATE (skip_grid_point(n_grid_total, bs_env%n_atom))
2229 : CALL compute_skip_grid_point(bs_env, phi_val, ao_col_map, skip_grid_point)
2230 : !$OMP END SINGLE
2231 :
2232 : ! MPI assigns each unordered pair through its first atom; OpenMP divides those atoms.
2233 : ! Skip only exact-zero pair-grid support, without a new screening threshold.
2234 : !$OMP DO SCHEDULE(DYNAMIC)
2235 : DO atom_j = atom_j_mepos + 1, bs_env%n_atom, atom_j_stride
2236 : DO atom_k = atom_j, bs_env%n_atom
2237 : jstart = ao_col_map(bs_env%i_ao_start_from_atom(atom_j))
2238 : kstart = ao_col_map(bs_env%i_ao_start_from_atom(atom_k))
2239 : IF (jstart == 0 .OR. kstart == 0) CYCLE
2240 : jsize = bs_env%i_ao_end_from_atom(atom_j) - bs_env%i_ao_start_from_atom(atom_j) + 1
2241 : ksize = bs_env%i_ao_end_from_atom(atom_k) - bs_env%i_ao_start_from_atom(atom_k) + 1
2242 :
2243 : n_grid_pair = 0
2244 : DO point = 1, n_grid_total
2245 : IF (skip_grid_point(point, atom_j) .OR. skip_grid_point(point, atom_k)) CYCLE
2246 : n_grid_pair = n_grid_pair + 1
2247 : grid_index(n_grid_pair) = point
2248 : END DO
2249 : IF (n_grid_pair == 0) CYCLE
2250 : ! (μν|P) = (νμ|P): distinct atom pairs contribute twice.
2251 : pair_factor = 1.0_dp
2252 : IF (atom_j /= atom_k) pair_factor = 2.0_dp
2253 :
2254 : int_3c_prv(1:jsize, 1:ksize, 1:n_loc_ri) = 0.0_dp
2255 :
2256 : ! Compute B_{μν,P} = (μν|P); ctx-internal triangle-inequality screening on
2257 : ! kind_radius sets `screened=.TRUE.` for negligible triples.
2258 : CALL build_3c_integral_block_ctx(int_3c_prv(1:jsize, 1:ksize, 1:n_loc_ri), &
2259 : ctx, ws, atom_j=atom_j, atom_k=atom_k, atom_i=atom_P, &
2260 : screened=screened)
2261 :
2262 : IF (screened) CYCLE
2263 :
2264 : ! Flatten 3D B_{μν, P} tensor to 2D B_{(μν), P} matrix for BLAS
2265 : DO ri = 1, n_loc_ri
2266 : DO k = 1, ksize
2267 : DO j = 1, jsize
2268 : jk_idx = (k - 1)*jsize + j
2269 : int_2d_prv(jk_idx, ri) = int_3c_prv(j, k, ri)
2270 : END DO
2271 : END DO
2272 : END DO
2273 :
2274 : ! Pair density ρ(l, μν) = Φ_μ(r_l) Φ_ν(r_l) in grid chunks, contracted on the fly:
2275 : ! d_{l,P} += ρ(l, μν) B_{(μν),P} (dgemm runs serially inside the parallel region)
2276 : DO l0 = 1, n_grid_pair, grid_chunk
2277 : c = MIN(grid_chunk, n_grid_pair - l0 + 1)
2278 : DO k = 1, ksize
2279 : DO j = 1, jsize
2280 : jk_idx = (k - 1)*jsize + j
2281 : DO l = 1, c
2282 : point = grid_index(l0 + l - 1)
2283 : rho_chunk(l, jk_idx) = phi_val(point, jstart + j - 1)* &
2284 : phi_val(point, kstart + k - 1)
2285 : END DO
2286 : END DO
2287 : END DO
2288 : CALL timeset(routineN//"_dgemm", handle_dgemm)
2289 : CALL dgemm("N", "N", c, n_loc_ri, jsize*ksize, &
2290 : pair_factor, rho_chunk, grid_chunk, &
2291 : int_2d_prv, max_ao_size*max_ao_size, &
2292 : 0.0_dp, grid_result, grid_chunk)
2293 : DO ri = 1, n_loc_ri
2294 : DO l = 1, c
2295 : point = grid_index(l0 + l - 1)
2296 : d_lp_prv(point, ri) = d_lp_prv(point, ri) + grid_result(l, ri)
2297 : END DO
2298 : END DO
2299 : CALL timestop(handle_dgemm)
2300 : END DO
2301 : END DO
2302 : END DO
2303 : !$OMP END DO
2304 :
2305 : !$OMP CRITICAL (compute_d_lp_reduce)
2306 : d_lp(1:n_grid_total, 1:n_loc_ri) = d_lp(1:n_grid_total, 1:n_loc_ri) + &
2307 : d_lp_prv(1:n_grid_total, 1:n_loc_ri)
2308 : !$OMP END CRITICAL (compute_d_lp_reduce)
2309 :
2310 : DEALLOCATE (int_3c_prv, int_2d_prv, rho_chunk, d_lp_prv, grid_index, grid_result)
2311 : CALL gw_3c_ws_release(ws)
2312 :
2313 : !$OMP SINGLE
2314 : DEALLOCATE (skip_grid_point)
2315 : !$OMP END SINGLE
2316 :
2317 : !$OMP END PARALLEL
2318 :
2319 50 : CALL timestop(handle)
2320 :
2321 50 : END SUBROUTINE compute_d_lp
2322 :
2323 : ! **************************************************************************************************
2324 : !> \brief Marks grid points where all stored AO values of an atom are exactly zero.
2325 : !> \param bs_env ...
2326 : !> \param phi_val ...
2327 : !> \param ao_col_map ...
2328 : !> \param skip_grid_point ...
2329 : ! **************************************************************************************************
2330 50 : SUBROUTINE compute_skip_grid_point(bs_env, phi_val, ao_col_map, skip_grid_point)
2331 :
2332 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
2333 : REAL(KIND=dp), DIMENSION(:, :), INTENT(IN) :: phi_val
2334 : INTEGER, DIMENSION(:), INTENT(IN) :: ao_col_map
2335 : LOGICAL, DIMENSION(:, :), INTENT(OUT) :: skip_grid_point
2336 :
2337 : INTEGER :: atom, first_ao, number_of_aos
2338 :
2339 45311 : skip_grid_point(:, :) = .TRUE.
2340 174 : DO atom = 1, bs_env%n_atom
2341 124 : first_ao = ao_col_map(bs_env%i_ao_start_from_atom(atom))
2342 124 : IF (first_ao == 0) CYCLE
2343 124 : number_of_aos = bs_env%i_ao_end_from_atom(atom) - bs_env%i_ao_start_from_atom(atom) + 1
2344 166003 : skip_grid_point(:, atom) = ALL(phi_val(:, first_ao:first_ao + number_of_aos - 1) == 0.0_dp, DIM=2)
2345 : END DO
2346 :
2347 50 : END SUBROUTINE compute_skip_grid_point
2348 :
2349 : ! **************************************************************************************************
2350 : !> \brief Distributed pdpotrf/pdpotrs solve of D x = b for one atom of the
2351 : !> RI-RS Z_lP build (Phase B, "big" atoms), called with a subgroup of
2352 : !> cooperating ranks and an associated BLACS context.
2353 : !> Each rank in the subgroup holds the (replicated) phi_local and the
2354 : !> (replicated) RHS d_lp; it builds its own block-cyclic slice of the
2355 : !> squared+Jacobi-scaled Gram matrix D via tiled DGEMM, factorizes via
2356 : !> cp_fm_cholesky_decompose (UPLO='U'), and solves with
2357 : !> cp_fm_cholesky_solve. The replicated d_lp is updated in place via
2358 : !> cp_fm_get_submatrix.
2359 : !> \param phi_local ...
2360 : !> \param d_vec ...
2361 : !> \param d_lp ...
2362 : !> \param n_loc ...
2363 : !> \param n_ao ...
2364 : !> \param n_rhs ...
2365 : !> \param tikhonov ...
2366 : !> \param para_env_sub ...
2367 : !> \param blacs_env_sub ...
2368 : !> \param fm_struct_D ...
2369 : !> \param fm_struct_b ...
2370 : !> \param fm_D ...
2371 : !> \param fm_b ...
2372 : !> \param info ...
2373 : ! **************************************************************************************************
2374 0 : SUBROUTINE solve_D_lp_distributed(phi_local, d_vec, d_lp, n_loc, n_ao, n_rhs, &
2375 : tikhonov, para_env_sub, blacs_env_sub, &
2376 : fm_struct_D, fm_struct_b, fm_D, fm_b, info)
2377 :
2378 : REAL(KIND=dp), DIMENSION(:, :), INTENT(IN) :: phi_local
2379 : REAL(KIND=dp), DIMENSION(:), INTENT(IN) :: d_vec
2380 : REAL(KIND=dp), DIMENSION(:, :), INTENT(INOUT) :: d_lp
2381 : INTEGER, INTENT(IN) :: n_loc, n_ao, n_rhs
2382 : REAL(KIND=dp), INTENT(IN) :: tikhonov
2383 : TYPE(mp_para_env_type), POINTER :: para_env_sub
2384 : TYPE(cp_blacs_env_type), POINTER :: blacs_env_sub
2385 : TYPE(cp_fm_struct_type), POINTER :: fm_struct_D, fm_struct_b
2386 : TYPE(cp_fm_type), INTENT(INOUT) :: fm_D, fm_b
2387 : INTEGER, INTENT(OUT) :: info
2388 :
2389 : CHARACTER(LEN=*), PARAMETER :: routineN = 'solve_D_lp_distributed'
2390 :
2391 : INTEGER :: handle, i_loc, ig, j_loc, jg, &
2392 : ncol_local, nrow_local
2393 0 : INTEGER, DIMENSION(:), POINTER :: col_indices, row_indices
2394 : REAL(KIND=dp), CONTIGUOUS, DIMENSION(:, :), &
2395 0 : POINTER :: local_data
2396 :
2397 0 : CALL timeset(routineN, handle)
2398 0 : info = 0
2399 :
2400 0 : NULLIFY (fm_struct_D, fm_struct_b)
2401 : CALL cp_fm_struct_create(fm_struct_D, para_env=para_env_sub, &
2402 : context=blacs_env_sub, &
2403 0 : nrow_global=n_loc, ncol_global=n_loc)
2404 : CALL cp_fm_struct_create(fm_struct_b, para_env=para_env_sub, &
2405 : context=blacs_env_sub, &
2406 0 : nrow_global=n_loc, ncol_global=n_rhs)
2407 0 : CALL cp_fm_create(fm_D, fm_struct_D)
2408 0 : CALL cp_fm_create(fm_b, fm_struct_b)
2409 :
2410 : ! ---- Build the local block-cyclic slice of fm_D --------------------
2411 : ! Tiled DGEMM build: for each (row_tile x col_tile) sub-block we
2412 : ! gather small phi strips, DGEMM into a small gram tile, then write
2413 : ! the squared & d_vec-scaled result directly into fm_D%local_data.
2414 : ! This avoids materialising the full (nrow_local x n_ao),
2415 : ! (n_ao x ncol_local), and (nrow_local x ncol_local) buffers.
2416 : CALL cp_fm_get_info(fm_D, nrow_local=nrow_local, ncol_local=ncol_local, &
2417 : row_indices=row_indices, col_indices=col_indices, &
2418 0 : local_data=local_data)
2419 :
2420 0 : IF (nrow_local > 0 .AND. ncol_local > 0) THEN
2421 0 : BLOCK
2422 : INTEGER, PARAMETER :: ntile = 1024
2423 : INTEGER :: ib, ie, jb, je, mb, kb, ti, tj, handle_dgemm
2424 0 : REAL(KIND=dp), ALLOCATABLE :: gram_t(:, :), phi_cols_t(:, :), phi_rows_t(:, :)
2425 0 : ALLOCATE (phi_rows_t(ntile, n_ao), phi_cols_t(n_ao, ntile), gram_t(ntile, ntile))
2426 0 : DO ib = 1, nrow_local, ntile
2427 0 : ie = MIN(ib + ntile - 1, nrow_local)
2428 0 : mb = ie - ib + 1
2429 : !$OMP PARALLEL DO DEFAULT(NONE) &
2430 : !$OMP SHARED(mb, n_ao, phi_rows_t, phi_local, row_indices, ib) &
2431 0 : !$OMP PRIVATE(ti, j_loc) SCHEDULE(STATIC)
2432 : DO j_loc = 1, n_ao
2433 : DO ti = 1, mb
2434 : phi_rows_t(ti, j_loc) = phi_local(row_indices(ib + ti - 1), j_loc)
2435 : END DO
2436 : END DO
2437 : !$OMP END PARALLEL DO
2438 0 : DO jb = 1, ncol_local, ntile
2439 0 : je = MIN(jb + ntile - 1, ncol_local)
2440 0 : kb = je - jb + 1
2441 : !$OMP PARALLEL DO DEFAULT(NONE) &
2442 : !$OMP SHARED(kb, n_ao, phi_cols_t, phi_local, col_indices, jb) &
2443 0 : !$OMP PRIVATE(tj, i_loc) SCHEDULE(STATIC)
2444 : DO tj = 1, kb
2445 : DO i_loc = 1, n_ao
2446 : phi_cols_t(i_loc, tj) = phi_local(col_indices(jb + tj - 1), i_loc)
2447 : END DO
2448 : END DO
2449 : !$OMP END PARALLEL DO
2450 0 : CALL timeset(routineN//"_dgemm", handle_dgemm)
2451 : CALL dgemm('N', 'N', mb, kb, n_ao, &
2452 : 1.0_dp, phi_rows_t, ntile, phi_cols_t, n_ao, &
2453 0 : 0.0_dp, gram_t, ntile)
2454 0 : CALL timestop(handle_dgemm)
2455 : !$OMP PARALLEL DO DEFAULT(NONE) &
2456 : !$OMP SHARED(mb, kb, gram_t, d_vec, row_indices, col_indices, ib, jb) &
2457 : !$OMP SHARED(local_data, tikhonov) &
2458 0 : !$OMP PRIVATE(ti, tj, ig, jg) SCHEDULE(STATIC)
2459 : DO tj = 1, kb
2460 : jg = col_indices(jb + tj - 1)
2461 : DO ti = 1, mb
2462 : ig = row_indices(ib + ti - 1)
2463 : local_data(ib + ti - 1, jb + tj - 1) = &
2464 : gram_t(ti, tj)*gram_t(ti, tj)*d_vec(ig)*d_vec(jg)
2465 : IF (ig == jg) THEN
2466 : local_data(ib + ti - 1, jb + tj - 1) = &
2467 : local_data(ib + ti - 1, jb + tj - 1) + tikhonov
2468 : END IF
2469 : END DO
2470 : END DO
2471 : !$OMP END PARALLEL DO
2472 : END DO
2473 : END DO
2474 0 : DEALLOCATE (phi_rows_t, phi_cols_t, gram_t)
2475 : END BLOCK
2476 : END IF
2477 :
2478 : ! Load the replicated d_lp into the block-cyclic fm_b
2479 0 : CALL cp_fm_set_submatrix(fm_b, d_lp)
2480 :
2481 : ! pdpotrf (Cholesky factorisation; cp_fm_cholesky_decompose
2482 : ! factors with UPLO='U', so pdpotrs must match)
2483 0 : CALL cp_fm_cholesky_decompose(fm_D, n=n_loc, info_out=info)
2484 0 : IF (info /= 0) THEN
2485 0 : CPABORT("pdpotrf failed in solve_D_lp_distributed")
2486 : END IF
2487 :
2488 : ! pdpotrs/dpotrs (solve in place on fm_b)
2489 0 : CALL cp_fm_cholesky_solve(fm_D, fm_b, n=n_loc, info_out=info)
2490 0 : IF (info /= 0) THEN
2491 0 : CPABORT("pdpotrs failed in solve_D_lp_distributed")
2492 : END IF
2493 :
2494 : ! Gather distributed solution back into the replicated d_lp
2495 0 : CALL cp_fm_get_submatrix(fm_b, d_lp)
2496 :
2497 0 : CALL cp_fm_release(fm_D)
2498 0 : CALL cp_fm_release(fm_b)
2499 0 : CALL cp_fm_struct_release(fm_struct_D)
2500 0 : CALL cp_fm_struct_release(fm_struct_b)
2501 :
2502 0 : CALL timestop(handle)
2503 :
2504 0 : END SUBROUTINE solve_D_lp_distributed
2505 :
2506 : ! **************************************************************************************************
2507 : !> \brief Computes the polarizability matrix in the RI basis for every
2508 : !> imaginary-time point:
2509 : !> G^occ/vir_μν(i|τ|) = Σ_n C_μn e^(-|(ϵ_n-ϵ_F)τ|) C_νn (AO x AO, build_G_ao)
2510 : !> χ_ll'(iτ) = [Σ_μν Φ_μ(r_l) G^occ_μν Φ_ν(r_l')] ∘ [Σ_μν Φ_μ(r_l) G^vir_μν Φ_ν(r_l')]
2511 : !> χ_PQ(iτ) = g_s Σ_ll' Z_lP χ_ll'(iτ) Z_l'Q (g_s = spin degeneracy)
2512 : !> The grid (l) index is streamed in panels (contract_grid_panels).
2513 : !> \param bs_env ...
2514 : !> \param mat_chi_Gamma_tau ...
2515 : !> \param mat_phi_mu_l ...
2516 : !> \param mat_Z_lP ...
2517 : ! **************************************************************************************************
2518 36 : SUBROUTINE get_mat_chi_Gamma_tau(bs_env, mat_chi_Gamma_tau, mat_phi_mu_l, mat_Z_lP)
2519 :
2520 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
2521 : TYPE(dbcsr_p_type), DIMENSION(:), POINTER :: mat_chi_Gamma_tau
2522 : TYPE(dbcsr_type), INTENT(INOUT) :: mat_phi_mu_l, mat_Z_lP
2523 :
2524 : CHARACTER(LEN=*), PARAMETER :: routineN = 'get_mat_chi_Gamma_tau'
2525 :
2526 : INTEGER :: handle, i_t, ispin, n_panels
2527 36 : INTEGER, ALLOCATABLE, DIMENSION(:) :: pan_first, pan_last
2528 : REAL(KIND=dp) :: grid_occ, t1, tau
2529 : TYPE(dbcsr_type) :: matrix_G_occ_ao, matrix_G_vir_ao
2530 :
2531 36 : CALL timeset(routineN, handle)
2532 :
2533 : ! Panel boundaries for the grid-streaming contraction.
2534 : ! The panels are identical for χ, Σ^x and Σ^c, so the count is reported once here for all three stages.
2535 :
2536 36 : CALL resolve_grid_panels(bs_env, mat_phi_mu_l, pan_first, pan_last)
2537 :
2538 36 : n_panels = SIZE(pan_first)
2539 36 : IF (bs_env%unit_nr > 0) THEN
2540 18 : WRITE (bs_env%unit_nr, '(T2,A,I44)') 'Number of batches for χ, Σ matrices', n_panels
2541 18 : CALL m_flush(bs_env%unit_nr)
2542 : END IF
2543 :
2544 : ! =========================================================================
2545 : ! IMAGINARY TIME LOOP
2546 : ! χ_PQ(iτ) = Σ_s g_s · Z^T ( (φ G^occ_s φ^T) ∘ (φ G^vir_s φ^T) ) Z
2547 : ! (g_s = spin degeneracy)
2548 : ! =========================================================================
2549 546 : DO i_t = 1, bs_env%num_time_freq_points
2550 510 : t1 = m_walltime()
2551 510 : tau = bs_env%imag_time_points(i_t)
2552 :
2553 1100 : DO ispin = 1, bs_env%n_spin
2554 :
2555 : ! AO-space Green's functions G^occ_µν, G^vir_µν (dense AO x AO, small)
2556 590 : CALL build_G_ao(bs_env, tau, ispin, .TRUE., .FALSE., mat_phi_mu_l, matrix_G_occ_ao)
2557 590 : CALL build_G_ao(bs_env, tau, ispin, .FALSE., .TRUE., mat_phi_mu_l, matrix_G_vir_ao)
2558 :
2559 : ! χ_PQ += g_s · Z^T ( (φ G^occ φ^T) ∘ (φ G^vir φ^T) ) Z
2560 : CALL contract_grid_panels(L_A=mat_phi_mu_l, M_A=matrix_G_occ_ao, &
2561 : L_B=mat_phi_mu_l, M_B=matrix_G_vir_ao, &
2562 : L_out=mat_Z_lP, mat_out=mat_chi_Gamma_tau(i_t)%matrix, &
2563 : scale=bs_env%spin_degeneracy, eps=bs_env%eps_filter, &
2564 : para_env=bs_env%para_env, &
2565 : pan_first=pan_first, pan_last=pan_last, &
2566 : lb_eq_la=.TRUE., lout_eq_la=.FALSE., &
2567 : zero_out=(ispin == 1), &
2568 : keep_sparsity=bs_env%ri_rs%keep_sparsity_rirs, &
2569 : centroids=bs_env%ri_rs%chunk_centroids, &
2570 : cutoff=bs_env%ri_rs%cutoff_radius_v_w, &
2571 590 : grid_occupation=grid_occ)
2572 :
2573 590 : CALL dbcsr_release(matrix_G_occ_ao)
2574 1100 : CALL dbcsr_release(matrix_G_vir_ao)
2575 :
2576 : END DO ! ispin
2577 :
2578 : ! Sparsity reports
2579 510 : IF (i_t == 1 .AND. bs_env%unit_nr > 0) THEN
2580 18 : WRITE (bs_env%unit_nr, '(A)') ' '
2581 : WRITE (bs_env%unit_nr, '(T2,A,F17.2,A)') &
2582 18 : 'Occupation of non-zero elements of G(l,l''), χ(l,l''), W(l,l'')', &
2583 36 : grid_occ*100.0_dp, ' %'
2584 18 : WRITE (bs_env%unit_nr, '(A)') ' '
2585 18 : CALL m_flush(bs_env%unit_nr)
2586 : END IF
2587 510 : IF (i_t == 1) THEN
2588 36 : CALL print_matrix_occupation(mat_chi_Gamma_tau(i_t)%matrix, 'χ(P,Q)', bs_env)
2589 : END IF
2590 :
2591 546 : IF (bs_env%unit_nr > 0) THEN
2592 : WRITE (bs_env%unit_nr, '(T2,A,I13,A,I3,A,F7.1,A)') &
2593 255 : 'Computed χ(iτ,k=0) for time point', i_t, ' /', bs_env%num_time_freq_points, &
2594 510 : ', Execution time', m_walltime() - t1, ' s'
2595 : END IF
2596 :
2597 : END DO ! i_t
2598 :
2599 36 : IF (bs_env%unit_nr > 0) WRITE (bs_env%unit_nr, '(A)') ' '
2600 :
2601 36 : CALL timestop(handle)
2602 :
2603 72 : END SUBROUTINE get_mat_chi_Gamma_tau
2604 :
2605 : ! **************************************************************************************************
2606 : !> \brief Prints the non-zero occupation percentage of a DBCSR matrix on one line.
2607 : !> \param matrix ...
2608 : !> \param label ...
2609 : !> \param bs_env ...
2610 : ! **************************************************************************************************
2611 122 : SUBROUTINE print_matrix_occupation(matrix, label, bs_env)
2612 :
2613 : TYPE(dbcsr_type), INTENT(IN) :: matrix
2614 : CHARACTER(LEN=*), INTENT(IN) :: label
2615 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
2616 :
2617 : CHARACTER(LEN=*), PARAMETER :: routineN = 'print_matrix_occupation'
2618 :
2619 : CHARACTER(LEN=max_line_length) :: msg
2620 : INTEGER :: handle
2621 : REAL(KIND=dp) :: frac_2p31, max_loc, occ
2622 :
2623 122 : CALL timeset(routineN, handle)
2624 :
2625 122 : occ = dbcsr_get_occupation(matrix)
2626 122 : max_loc = REAL(dbcsr_get_data_size(matrix), dp)
2627 122 : CALL bs_env%para_env%max(max_loc)
2628 :
2629 122 : IF (bs_env%unit_nr > 0) THEN
2630 61 : frac_2p31 = max_loc/REAL(bs_env%dbcsr_msg_elem_limit, dp)
2631 61 : WRITE (bs_env%unit_nr, '(A)') ' '
2632 : WRITE (bs_env%unit_nr, '(T2,A,F36.2,A)') &
2633 61 : 'Occupation of non-zero elements of '//TRIM(label), occ*100.0_dp, ' %'
2634 61 : IF (frac_2p31 > 0.5_dp) THEN
2635 : WRITE (msg, '(3A,F0.2,A)') &
2636 0 : "The largest per-rank message of ", TRIM(label), " reaches ", frac_2p31, &
2637 : " of the 32-bit limit that DBCSR uses for its message length. Beyond it the "// &
2638 : "length overflows and multiply_cannon fails. Reduce the per-rank block size, "// &
2639 0 : "for instance with more MPI ranks or a larger N_PANELS."
2640 0 : CPWARN(TRIM(msg))
2641 : END IF
2642 61 : CALL m_flush(bs_env%unit_nr)
2643 : END IF
2644 :
2645 122 : CALL timestop(handle)
2646 :
2647 122 : END SUBROUTINE print_matrix_occupation
2648 :
2649 : ! **************************************************************************************************
2650 : !> \brief Marks the grid blocks whose centroid lies within cutoff of the bounding box of the
2651 : !> panel [blk0, blk1]'s chunk centroids.
2652 : !> \param centroids ...
2653 : !> \param blk0 ...
2654 : !> \param blk1 ...
2655 : !> \param cutoff ...
2656 : !> \param used ...
2657 : ! **************************************************************************************************
2658 92 : SUBROUTINE mask_grid_blocks_near_panel(centroids, blk0, blk1, cutoff, used)
2659 :
2660 : REAL(KIND=dp), DIMENSION(:, :), INTENT(IN) :: centroids
2661 : INTEGER, INTENT(IN) :: blk0, blk1
2662 : REAL(KIND=dp), INTENT(IN) :: cutoff
2663 : LOGICAL, ALLOCATABLE, DIMENSION(:), INTENT(OUT) :: used
2664 :
2665 : CHARACTER(LEN=*), PARAMETER :: routineN = 'mask_grid_blocks_near_panel'
2666 :
2667 : INTEGER :: c, handle, k
2668 : REAL(KIND=dp) :: cutoff2, d2, dx
2669 : REAL(KIND=dp), DIMENSION(3) :: hi, lo
2670 :
2671 92 : CALL timeset(routineN, handle)
2672 :
2673 92 : cutoff2 = cutoff**2
2674 92 : lo(:) = MINVAL(centroids(:, blk0:blk1), DIM=2)
2675 92 : hi(:) = MAXVAL(centroids(:, blk0:blk1), DIM=2)
2676 :
2677 276 : ALLOCATE (used(SIZE(centroids, 2)))
2678 2024 : DO c = 1, SIZE(centroids, 2)
2679 : d2 = 0.0_dp
2680 7728 : DO k = 1, 3
2681 5796 : dx = MAX(0.0_dp, lo(k) - centroids(k, c), centroids(k, c) - hi(k))
2682 7728 : d2 = d2 + dx*dx
2683 : END DO
2684 2024 : used(c) = (d2 <= cutoff2)
2685 : END DO
2686 :
2687 92 : CALL timestop(handle)
2688 :
2689 92 : END SUBROUTINE mask_grid_blocks_near_panel
2690 :
2691 : ! **************************************************************************************************
2692 : !> \brief Exact allocated-element count of the geo template of panel [blk0, blk1]: the very same
2693 : !> per-block-pair centroid test as build_geo_template_panel, so this is the true DBCSR
2694 : !> data size of A_pan/B_pan/C_pan (DBCSR stores whole blocks).
2695 : !> \param r_blk_sizes ...
2696 : !> \param centroids ...
2697 : !> \param used ...
2698 : !> \param blk0 ...
2699 : !> \param blk1 ...
2700 : !> \param cutoff ...
2701 : !> \param nze_tmpl ...
2702 : ! **************************************************************************************************
2703 8 : SUBROUTINE panel_template_elems(r_blk_sizes, centroids, used, blk0, blk1, cutoff, nze_tmpl)
2704 :
2705 : INTEGER, DIMENSION(:), INTENT(IN) :: r_blk_sizes
2706 : REAL(KIND=dp), DIMENSION(:, :), INTENT(IN) :: centroids
2707 : LOGICAL, DIMENSION(:), INTENT(IN) :: used
2708 : INTEGER, INTENT(IN) :: blk0, blk1
2709 : REAL(KIND=dp), INTENT(IN) :: cutoff
2710 : INTEGER(KIND=int_8), INTENT(OUT) :: nze_tmpl
2711 :
2712 : CHARACTER(LEN=*), PARAMETER :: routineN = 'panel_template_elems'
2713 :
2714 : INTEGER :: c, handle, ib, n_used
2715 8 : INTEGER, ALLOCATABLE, DIMENSION(:) :: used_idx
2716 : REAL(KIND=dp) :: cutoff2
2717 :
2718 8 : CALL timeset(routineN, handle)
2719 :
2720 : ! Compress the near mask once so the pair loop only visits candidate columns.
2721 176 : n_used = COUNT(used)
2722 24 : ALLOCATE (used_idx(n_used))
2723 8 : n_used = 0
2724 176 : DO c = 1, SIZE(used)
2725 176 : IF (used(c)) THEN
2726 168 : n_used = n_used + 1
2727 168 : used_idx(n_used) = c
2728 : END IF
2729 : END DO
2730 :
2731 8 : cutoff2 = cutoff**2
2732 8 : nze_tmpl = 0_int_8
2733 : !$OMP PARALLEL DO DEFAULT(NONE) SHARED(blk0, blk1, n_used, used_idx, centroids, cutoff2, &
2734 8 : !$OMP r_blk_sizes) PRIVATE(ib, c) REDUCTION(+:nze_tmpl)
2735 : DO ib = blk0, blk1
2736 : DO c = 1, n_used
2737 : IF (SUM((centroids(:, ib) - centroids(:, used_idx(c)))**2) <= cutoff2) THEN
2738 : nze_tmpl = nze_tmpl + INT(r_blk_sizes(ib), int_8)*INT(r_blk_sizes(used_idx(c)), int_8)
2739 : END IF
2740 : END DO
2741 : END DO
2742 : !$OMP END PARALLEL DO
2743 :
2744 8 : CALL timestop(handle)
2745 :
2746 16 : END SUBROUTINE panel_template_elems
2747 :
2748 : ! **************************************************************************************************
2749 : !> \brief Per-rank peak memory (GB) of one panel step of the neighborhood-restricted
2750 : !> contractions: three grid x grid panels of the template size (A_pan, B_pan, C_pan)
2751 : !> plus the grid x RI intermediates (tmp2 and the accumulation operand) and the
2752 : !> grid x AO intermediate (tmpA), whose column support is the panel's geometric
2753 : !> neighborhood fraction f_near = width/n_grid. Shared by the panel planner and
2754 : !> \param nze_tmpl ...
2755 : !> \param pan_rows ...
2756 : !> \param width ...
2757 : !> \param n_grid_total ...
2758 : !> \param n_RI ...
2759 : !> \param n_ao ...
2760 : !> \param n_procs ...
2761 : !> \param mem_GB ...
2762 : ! **************************************************************************************************
2763 8 : SUBROUTINE panel_mem_estimate_GB(nze_tmpl, pan_rows, width, n_grid_total, n_RI, n_ao, &
2764 : n_procs, mem_GB)
2765 :
2766 : INTEGER(KIND=int_8), INTENT(IN) :: nze_tmpl
2767 : INTEGER, INTENT(IN) :: pan_rows, width, n_grid_total, n_RI, &
2768 : n_ao, n_procs
2769 : REAL(KIND=dp), INTENT(OUT) :: mem_GB
2770 :
2771 : REAL(KIND=dp) :: f_near
2772 :
2773 8 : f_near = REAL(width, dp)/REAL(MAX(n_grid_total, 1), dp)
2774 : mem_GB = (3.0_dp*REAL(nze_tmpl, dp) + &
2775 : REAL(pan_rows, dp)*f_near*(2.0_dp*REAL(n_RI, dp) + REAL(n_ao, dp)))* &
2776 8 : 8.0_dp/REAL(MAX(n_procs, 1), dp)*1.0E-9_dp
2777 :
2778 8 : END SUBROUTINE panel_mem_estimate_GB
2779 :
2780 : ! **************************************************************************************************
2781 : !> \brief Plans the panel boundaries for the streaming contractions. Panels grow by whole grid
2782 : !> row-blocks towards ~panel_size rows. When the neighborhood restriction is active
2783 : !> (centroids+cutoff), each candidate panel is additionally checked against
2784 : !> (a) the 32-bit message bound with the panel's TRUE occupancy
2785 : !> (b) the per-rank memory budget: panel_mem_estimate_GB <= mem_budget_GB.
2786 : !> \param bs_env ...
2787 : !> \param r_blk_sizes ...
2788 : !> \param panel_size ...
2789 : !> \param min_dim ...
2790 : !> \param pan_first ...
2791 : !> \param pan_last ...
2792 : !> \param centroids ...
2793 : !> \param cutoff ...
2794 : !> \param n_RI ...
2795 : !> \param n_ao ...
2796 : !> \param n_procs ...
2797 : !> \param mem_budget_GB ...
2798 : !> \param honor_exact ...
2799 : !> \param unsafe ...
2800 : ! **************************************************************************************************
2801 36 : SUBROUTINE plan_grid_panels(bs_env, r_blk_sizes, panel_size, min_dim, pan_first, pan_last, &
2802 36 : centroids, cutoff, n_RI, n_ao, n_procs, mem_budget_GB, &
2803 : honor_exact, unsafe)
2804 :
2805 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
2806 : INTEGER, DIMENSION(:), INTENT(IN) :: r_blk_sizes
2807 : INTEGER, INTENT(IN) :: panel_size, min_dim
2808 : INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT) :: pan_first, pan_last
2809 : REAL(KIND=dp), DIMENSION(:, :), INTENT(IN), &
2810 : OPTIONAL :: centroids
2811 : REAL(KIND=dp), INTENT(IN), OPTIONAL :: cutoff
2812 : INTEGER, INTENT(IN), OPTIONAL :: n_RI, n_ao, n_procs
2813 : REAL(KIND=dp), INTENT(IN), OPTIONAL :: mem_budget_GB
2814 : LOGICAL, INTENT(IN), OPTIONAL :: honor_exact
2815 : LOGICAL, INTENT(OUT), OPTIONAL :: unsafe
2816 :
2817 : CHARACTER(LEN=*), PARAMETER :: routineN = 'plan_grid_panels'
2818 :
2819 : INTEGER :: blk0, blk1, handle, ib, n_grid_blocks, &
2820 : n_grid_total, n_panels, rows_acc, &
2821 : TARGET, width
2822 : INTEGER(KIND=int_8) :: msg, nze_tmpl, side
2823 36 : INTEGER, ALLOCATABLE, DIMENSION(:) :: tmp_first, tmp_last
2824 : LOGICAL :: fits, my_honor_exact, my_unsafe, &
2825 : use_cutoff
2826 36 : LOGICAL, ALLOCATABLE, DIMENSION(:) :: used
2827 : REAL(KIND=dp) :: f_near, mem_GB
2828 :
2829 36 : CALL timeset(routineN, handle)
2830 :
2831 36 : use_cutoff = PRESENT(centroids) .AND. PRESENT(cutoff)
2832 36 : IF (use_cutoff) use_cutoff = cutoff > 0.0_dp
2833 2 : IF (use_cutoff) THEN
2834 2 : CPASSERT(PRESENT(n_RI) .AND. PRESENT(n_ao) .AND. PRESENT(n_procs))
2835 : END IF
2836 :
2837 : ! honor_exact: use N_PANELS as requested -- do NOT split a panel further even if it trips
2838 : ! the message-overflow / memory-budget check; instead flag `unsafe` so the caller can warn.
2839 36 : my_honor_exact = .FALSE.
2840 36 : IF (PRESENT(honor_exact)) my_honor_exact = honor_exact
2841 36 : my_unsafe = .FALSE.
2842 :
2843 36 : n_grid_blocks = SIZE(r_blk_sizes)
2844 546 : n_grid_total = SUM(r_blk_sizes)
2845 144 : ALLOCATE (tmp_first(n_grid_blocks), tmp_last(n_grid_blocks))
2846 :
2847 36 : n_panels = 0
2848 36 : blk0 = 1
2849 74 : DO WHILE (blk0 <= n_grid_blocks)
2850 38 : TARGET = panel_size
2851 0 : DO
2852 38 : rows_acc = 0
2853 38 : blk1 = blk0
2854 512 : DO ib = blk0, n_grid_blocks
2855 510 : rows_acc = rows_acc + r_blk_sizes(ib)
2856 510 : blk1 = ib
2857 512 : IF (rows_acc >= TARGET) EXIT
2858 : END DO
2859 38 : IF (.NOT. use_cutoff .OR. blk1 == blk0) EXIT
2860 4 : CALL mask_grid_blocks_near_panel(centroids, blk0, blk1, cutoff, used)
2861 88 : width = SUM(r_blk_sizes, MASK=used)
2862 4 : CALL panel_template_elems(r_blk_sizes, centroids, used, blk0, blk1, cutoff, nze_tmpl)
2863 4 : f_near = REAL(width, dp)/REAL(MAX(n_grid_total, 1), dp)
2864 4 : side = INT(REAL(rows_acc, dp)*f_near*REAL(MAX(n_RI, n_ao), dp), int_8)
2865 4 : msg = MAX(nze_tmpl, side)/INT(MAX(min_dim, 1), int_8)
2866 4 : fits = (msg <= bs_env%dbcsr_msg_elem_limit/4)
2867 4 : IF (fits .AND. PRESENT(mem_budget_GB)) THEN
2868 4 : IF (mem_budget_GB > 0.0_dp) THEN
2869 : CALL panel_mem_estimate_GB(nze_tmpl, rows_acc, width, n_grid_total, &
2870 4 : n_RI, n_ao, n_procs, mem_GB)
2871 4 : fits = (mem_GB <= mem_budget_GB)
2872 : END IF
2873 : END IF
2874 : ! Panel size is bounded only by the message-overflow and memory checks above; there is
2875 : ! no neighborhood-width (f_near) cap. mp_waitall is dominated by the NUMBER of panel
2876 : ! multiplies, so fewer/larger panels are cheaper here -- panel count is driven DOWN by
2877 : ! the N_PANELS keyword (panel_size), not split up by a width heuristic.
2878 4 : IF (my_honor_exact) THEN
2879 : ! Keep exactly the requested grouping; just record if it exceeds a safety limit.
2880 4 : IF (.NOT. fits) my_unsafe = .TRUE.
2881 : EXIT
2882 : END IF
2883 0 : IF (fits) EXIT
2884 34 : TARGET = MAX(1, MIN(TARGET, rows_acc)/2)
2885 : END DO
2886 38 : n_panels = n_panels + 1
2887 38 : tmp_first(n_panels) = blk0
2888 38 : tmp_last(n_panels) = blk1
2889 38 : blk0 = blk1 + 1
2890 : END DO
2891 :
2892 144 : ALLOCATE (pan_first(n_panels), pan_last(n_panels))
2893 74 : pan_first(:) = tmp_first(1:n_panels)
2894 74 : pan_last(:) = tmp_last(1:n_panels)
2895 36 : DEALLOCATE (tmp_first, tmp_last)
2896 :
2897 36 : IF (PRESENT(unsafe)) unsafe = my_unsafe
2898 :
2899 36 : CALL timestop(handle)
2900 :
2901 72 : END SUBROUTINE plan_grid_panels
2902 :
2903 : ! **************************************************************************************************
2904 : !> \brief Resolves the panel boundaries for the streaming contractions from the bs_env settings:
2905 : !> \param bs_env ...
2906 : !> \param mat_phi_mu_l ...
2907 : !> \param pan_first ...
2908 : !> \param pan_last ...
2909 : ! **************************************************************************************************
2910 158 : SUBROUTINE resolve_grid_panels(bs_env, mat_phi_mu_l, pan_first, pan_last)
2911 :
2912 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
2913 : TYPE(dbcsr_type), INTENT(INOUT) :: mat_phi_mu_l
2914 : INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT) :: pan_first, pan_last
2915 :
2916 : CHARACTER(LEN=*), PARAMETER :: routineN = 'resolve_grid_panels'
2917 :
2918 : CHARACTER(LEN=max_line_length) :: msg
2919 : INTEGER :: handle, min_dim, n_grid_total, &
2920 : n_panels_req, npcols, nprows, &
2921 : panel_size, safe_max
2922 158 : INTEGER, DIMENSION(:), POINTER :: r_blk_sizes
2923 : LOGICAL :: honor_exact, panels_unsafe, use_cutoff
2924 : REAL(KIND=dp) :: mem_avail_GB, mem_budget_GB
2925 : TYPE(dbcsr_distribution_type) :: dist
2926 :
2927 158 : CALL timeset(routineN, handle)
2928 :
2929 158 : IF (ALLOCATED(bs_env%ri_rs%pan_first)) THEN
2930 494 : ALLOCATE (pan_first, SOURCE=bs_env%ri_rs%pan_first)
2931 494 : ALLOCATE (pan_last, SOURCE=bs_env%ri_rs%pan_last)
2932 122 : CALL timestop(handle)
2933 : RETURN
2934 : END IF
2935 :
2936 : use_cutoff = bs_env%ri_rs%cutoff_radius_v_w > 0.0_dp .AND. &
2937 36 : ALLOCATED(bs_env%ri_rs%chunk_centroids)
2938 :
2939 : ! MIN(nprows, npcols) is the divisor that bounds the worst-rank Cannon message: a
2940 : ! P x n_grid panel is replicated into block row strips (P/nprows x n_grid) or column
2941 : ! strips (P x n_grid/npcols) during multiply_cannon, so the largest single-rank
2942 : ! message is ~ P*n_grid / MIN(nprows,npcols) elements.
2943 : CALL dbcsr_get_info(mat_phi_mu_l, nfullrows_total=n_grid_total, row_blk_size=r_blk_sizes, &
2944 36 : distribution=dist)
2945 36 : CALL dbcsr_distribution_get(dist, nprows=nprows, npcols=npcols)
2946 36 : min_dim = MAX(MIN(nprows, npcols), 1)
2947 :
2948 : ! Panel height such that NO per-rank DBCSR message can overflow the 32-bit length field
2949 : ! (see bs_env%dbcsr_msg_elem_limit): requiring the worst-rank message to stay under
2950 : ! 0.5 * HUGE(int_4) gives the safe height P_safe = 0.5 * HUGE(int_4) * min_dim / n_grid.
2951 36 : IF (use_cutoff) THEN
2952 2 : safe_max = n_grid_total
2953 : ELSE
2954 : safe_max = INT(0.5_dp*REAL(bs_env%dbcsr_msg_elem_limit, dp)*REAL(min_dim, dp)/ &
2955 34 : REAL(n_grid_total, dp))
2956 34 : safe_max = MAX(1, MIN(safe_max, n_grid_total))
2957 : END IF
2958 :
2959 : ! A user-set N_PANELS ( > 1 ) is honored EXACTLY: the planner produces that many panels
2960 : ! (up to grid-block granularity) and never force-splits them for the message/memory safety
2961 : ! limits -- if a limit is tripped it warns instead of silently changing the count.
2962 36 : n_panels_req = bs_env%ri_rs%n_panels
2963 36 : honor_exact = (n_panels_req > 1)
2964 36 : panels_unsafe = .FALSE.
2965 36 : IF (n_panels_req > 1) THEN
2966 : ! ceil(n_grid/n_panels_req) rows per panel => exactly n_panels_req panels. With the
2967 : ! cutoff active safe_max = n_grid_total (no clamp, honored exactly); without it, safe_max
2968 : ! is the int32-overflow ceiling and MUST still bound the panel (the non-cutoff planner
2969 : ! loop has no in-loop message-size check).
2970 2 : panel_size = MIN((n_grid_total + n_panels_req - 1)/n_panels_req, safe_max)
2971 : ELSE
2972 : ! Default (<= 1): a single whole-grid panel, clamped to the overflow-safe ceiling.
2973 34 : panel_size = safe_max
2974 : END IF
2975 36 : panel_size = MAX(1, panel_size)
2976 :
2977 36 : IF (use_cutoff) THEN
2978 : ! Half of the measured free memory as panel budget.
2979 2 : CALL ri_rs_mem_avail_per_proc_GB(bs_env, mem_avail_GB)
2980 2 : mem_budget_GB = 0.5_dp*mem_avail_GB
2981 : CALL plan_grid_panels(bs_env, r_blk_sizes, panel_size, min_dim, pan_first, pan_last, &
2982 : centroids=bs_env%ri_rs%chunk_centroids, &
2983 : cutoff=bs_env%ri_rs%cutoff_radius_v_w, &
2984 : n_RI=bs_env%n_RI, n_ao=bs_env%n_ao, &
2985 : n_procs=bs_env%para_env%num_pe, mem_budget_GB=mem_budget_GB, &
2986 2 : honor_exact=honor_exact, unsafe=panels_unsafe)
2987 : ELSE
2988 34 : CALL plan_grid_panels(bs_env, r_blk_sizes, panel_size, min_dim, pan_first, pan_last)
2989 : END IF
2990 :
2991 36 : IF (honor_exact .AND. panels_unsafe) THEN
2992 : WRITE (msg, '(A,I0,A)') &
2993 0 : "N_PANELS = ", n_panels_req, " is used as requested, but one or more panels "// &
2994 : "exceed the DBCSR 32-bit message length or the memory budget. The run may abort "// &
2995 0 : "or swap; increase N_PANELS if it does."
2996 0 : CPWARN(TRIM(msg))
2997 : END IF
2998 :
2999 146 : ALLOCATE (bs_env%ri_rs%pan_first, SOURCE=pan_first)
3000 146 : ALLOCATE (bs_env%ri_rs%pan_last, SOURCE=pan_last)
3001 :
3002 36 : CALL timestop(handle)
3003 :
3004 194 : END SUBROUTINE resolve_grid_panels
3005 :
3006 : ! **************************************************************************************************
3007 : !> \brief Available memory per MPI process (GB). /proc/meminfo reports node-wide memory, so
3008 : !> every rank on a node reads the SAME MemLikelyFree; the per-process share is
3009 : !> node_free / ranks_per_node (ranks grouped by a hostname hash exchanged via allgather).
3010 : !> Returns the MIN across all ranks (most-constrained node)
3011 : !> \param bs_env ...
3012 : !> \param mem_avail_GB ...
3013 : ! **************************************************************************************************
3014 80 : SUBROUTINE ri_rs_mem_avail_per_proc_GB(bs_env, mem_avail_GB)
3015 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
3016 : REAL(KIND=dp), INTENT(OUT) :: mem_avail_GB
3017 :
3018 : CHARACTER(LEN=*), PARAMETER :: routineN = 'ri_rs_mem_avail_per_proc_GB'
3019 :
3020 : CHARACTER(LEN=default_string_length) :: hostname
3021 : INTEGER :: handle, host_hash, ic, n_procs, &
3022 : ranks_per_node
3023 : INTEGER(KIND=int_8) :: h8, mem_buffers, mem_cached, mem_free, &
3024 : mem_likely_free, mem_slab, &
3025 : mem_sreclaimable, mem_total
3026 : INTEGER, ALLOCATABLE, DIMENSION(:) :: all_host_hashes
3027 :
3028 80 : CALL timeset(routineN, handle)
3029 :
3030 80 : n_procs = bs_env%para_env%num_pe
3031 80 : CALL m_hostnm(hostname)
3032 80 : h8 = 0_int_8
3033 1040 : DO ic = 1, LEN_TRIM(hostname)
3034 1040 : h8 = MOD(h8*127_int_8 + INT(ICHAR(hostname(ic:ic)), int_8), 2147483647_int_8)
3035 : END DO
3036 80 : host_hash = INT(h8)
3037 240 : ALLOCATE (all_host_hashes(n_procs))
3038 80 : CALL bs_env%para_env%allgather(host_hash, all_host_hashes)
3039 240 : ranks_per_node = MAX(COUNT(all_host_hashes == host_hash), 1)
3040 80 : DEALLOCATE (all_host_hashes)
3041 :
3042 : CALL m_memory_details(MemTotal=mem_total, MemFree=mem_free, Buffers=mem_buffers, &
3043 : Cached=mem_cached, Slab=mem_slab, SReclaimable=mem_sreclaimable, &
3044 80 : MemLikelyFree=mem_likely_free)
3045 80 : mem_avail_GB = REAL(mem_likely_free, dp)*1.0E-9_dp/REAL(ranks_per_node, dp)
3046 80 : CALL bs_env%para_env%min(mem_avail_GB)
3047 :
3048 80 : CALL timestop(handle)
3049 :
3050 80 : END SUBROUTINE ri_rs_mem_avail_per_proc_GB
3051 :
3052 : ! **************************************************************************************************
3053 : !> \brief Estimates and prints per-process memory requirements for the RI-RS GW calculation.
3054 : !> \param qs_env ...
3055 : !> \param bs_env ...
3056 : ! **************************************************************************************************
3057 36 : SUBROUTINE print_ri_rs_memory_estimate(qs_env, bs_env)
3058 :
3059 : !$ USE OMP_LIB, ONLY: omp_get_max_threads
3060 :
3061 : TYPE(qs_environment_type), POINTER :: qs_env
3062 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
3063 :
3064 : CHARACTER(LEN=*), PARAMETER :: routineN = 'print_ri_rs_memory_estimate'
3065 :
3066 : CHARACTER(LEN=max_line_length) :: msg
3067 : INTEGER :: handle, iatom, ipan, l, &
3068 : max_n_ao_used, max_n_local_grid, &
3069 : n_ao_used_atom, n_grid_total, &
3070 : n_local_grid, n_loc_ri_max, n_procs, &
3071 : n_procs_per_atom, n_RI, n_threads, &
3072 : natom, pan_rows, pan_width
3073 : INTEGER(KIND=int_8) :: nze_tmpl
3074 36 : INTEGER, ALLOCATABLE, DIMENSION(:) :: pan_first, pan_last
3075 36 : INTEGER, DIMENSION(:), POINTER :: r_blk_sizes
3076 : LOGICAL :: use_cutoff
3077 36 : LOGICAL, ALLOCATABLE, DIMENSION(:) :: grid_used
3078 : REAL(KIND=dp) :: cutoff_ri, mem_avail_GB, mem_D_local_GB, &
3079 : mem_dlp_GB, mem_pan_GB, mem_panels_GB, &
3080 : mem_phi_local_GB, mem_Z_lP_GB, &
3081 : mem_Zlp_peak_GB, pos_P(3)
3082 36 : TYPE(particle_type), DIMENSION(:), POINTER :: particle_set
3083 :
3084 36 : CALL timeset(routineN, handle)
3085 :
3086 : CALL dbcsr_get_info(bs_env%ri_rs%mat_phi_mu_l, nfullrows_total=n_grid_total, &
3087 36 : row_blk_size=r_blk_sizes)
3088 36 : n_RI = bs_env%n_RI
3089 36 : n_procs = bs_env%para_env%num_pe
3090 :
3091 : ! Z_lP upper bound: dense n_grid × n_RI, distributed evenly across all ranks.
3092 : ! The actual sparse Z_lP is smaller due to the per-atom locality cutoff.
3093 : mem_Z_lP_GB = REAL(n_grid_total, dp)*REAL(n_RI, dp)*8.0_dp/ &
3094 36 : REAL(n_procs, dp)*1.0E-9_dp
3095 :
3096 : ! Peak panel memory during Σ^c: two G panels (A_occ, A_vir) + one W panel plus the
3097 : ! grid × RI / grid × AO intermediates. With the CUTOFF_RADIUS_RL_W restriction the panel
3098 : ! matrices only allocate the geo-template blocks, so use the same nze-aware model as the
3099 : ! panel planner (panel_mem_estimate_GB); without the cutoff, dense panel_rows × n_grid.
3100 : ! Plus the n_RI × n_RI W_aux matrix. Distributed over n_procs ranks.
3101 : use_cutoff = bs_env%ri_rs%cutoff_radius_v_w > 0.0_dp .AND. &
3102 36 : ALLOCATED(bs_env%ri_rs%chunk_centroids)
3103 36 : CALL resolve_grid_panels(bs_env, bs_env%ri_rs%mat_phi_mu_l, pan_first, pan_last)
3104 36 : mem_panels_GB = 0.0_dp
3105 74 : DO ipan = 1, SIZE(pan_first)
3106 548 : pan_rows = SUM(r_blk_sizes(pan_first(ipan):pan_last(ipan)))
3107 38 : IF (use_cutoff) THEN
3108 : CALL mask_grid_blocks_near_panel(bs_env%ri_rs%chunk_centroids, pan_first(ipan), &
3109 : pan_last(ipan), bs_env%ri_rs%cutoff_radius_v_w, &
3110 4 : grid_used)
3111 88 : pan_width = SUM(r_blk_sizes, MASK=grid_used)
3112 : CALL panel_template_elems(r_blk_sizes, bs_env%ri_rs%chunk_centroids, &
3113 : grid_used, pan_first(ipan), pan_last(ipan), &
3114 4 : bs_env%ri_rs%cutoff_radius_v_w, nze_tmpl)
3115 : CALL panel_mem_estimate_GB(nze_tmpl, pan_rows, pan_width, n_grid_total, &
3116 4 : n_RI, bs_env%n_ao, n_procs, mem_pan_GB)
3117 : ELSE
3118 34 : pan_width = n_grid_total
3119 : mem_pan_GB = (3.0_dp*REAL(pan_rows, dp)*REAL(pan_width, dp) + &
3120 : 2.0_dp*REAL(pan_rows, dp)*REAL(n_RI, dp))* &
3121 34 : 8.0_dp/REAL(n_procs, dp)*1.0E-9_dp
3122 : END IF
3123 74 : mem_panels_GB = MAX(mem_panels_GB, mem_pan_GB)
3124 : END DO
3125 : mem_panels_GB = mem_panels_GB + &
3126 36 : REAL(n_RI, dp)*REAL(n_RI, dp)*8.0_dp/REAL(n_procs, dp)*1.0E-9_dp
3127 :
3128 : ! Z_lP SOLVE peak (compute_coeff_Z_lP). For the atom P with the largest integration
3129 : ! sphere, one rank holds simultaneously:
3130 : ! D_local : n_local_grid x n_local_grid (dense Gram, BLAS path only; O(n_local_grid^2))
3131 : ! phi_local: n_local_grid x n_ao_used (AOs reaching into the sphere only)
3132 : ! d_lp : n_local_grid x n_loc_ri, replicated once + one private copy per OMP thread
3133 : ! n_local_grid = # grid points within cutoff_ri(P) = CUTOFF_RADIUS_RL_RI (if > 0) else
3134 : ! r_c(RI metric) + r_RI(P). This is NOT evenly distributed: n_local_grid depends on the
3135 : ! local density of atoms/grid, so the rank owning the densest atom peaks well above the
3136 : ! average. We report the worst-case (max over atoms) as a per-rank upper bound.
3137 36 : CALL get_qs_env(qs_env, particle_set=particle_set)
3138 36 : natom = SIZE(particle_set)
3139 :
3140 36 : max_n_local_grid = 0
3141 36 : n_loc_ri_max = 0
3142 36 : max_n_ao_used = 0
3143 126 : DO iatom = 1, natom
3144 90 : IF (bs_env%ri_rs%cutoff_radius_ri_rs > 0.0_dp) THEN
3145 6 : cutoff_ri = bs_env%ri_rs%cutoff_radius_ri_rs
3146 : ELSE
3147 84 : cutoff_ri = bs_env%ri_metric%cutoff_radius + bs_env%ri_rs%radius_ri_per_atom(iatom)
3148 : END IF
3149 360 : pos_P(:) = particle_set(iatom)%r(:)
3150 : n_local_grid = 0
3151 36562 : DO l = 1, n_grid_total
3152 145978 : IF (SUM((bs_env%ri_rs%grid_points(1:3, l) - pos_P(1:3))**2) <= cutoff_ri**2) THEN
3153 35086 : n_local_grid = n_local_grid + 1
3154 : END IF
3155 : END DO
3156 90 : max_n_local_grid = MAX(max_n_local_grid, n_local_grid)
3157 90 : CALL get_n_ao_in_sphere(bs_env, particle_set, iatom, cutoff_ri, n_ao_used_atom)
3158 90 : max_n_ao_used = MAX(max_n_ao_used, n_ao_used_atom)
3159 : n_loc_ri_max = MAX(n_loc_ri_max, &
3160 126 : bs_env%i_RI_end_from_atom(iatom) - bs_env%i_RI_start_from_atom(iatom) + 1)
3161 : END DO
3162 :
3163 36 : n_procs_per_atom = MIN(MAX(bs_env%ri_rs%n_procs_per_atom_z_lp, 1), n_procs)
3164 36 : n_threads = 1
3165 36 : !$ n_threads = omp_get_max_threads()
3166 :
3167 : ! D_local: dense on one rank for the BLAS path; block-cyclic over the subgroup (=> /G) for
3168 : ! the ScaLAPACK path (N_PROCS_PER_ATOM_Z_LP = G > 1). phi_local/d_lp stay per-rank either way.
3169 36 : IF (n_procs_per_atom > 1) THEN
3170 0 : mem_D_local_GB = REAL(max_n_local_grid, dp)**2*8.0_dp/REAL(n_procs_per_atom, dp)*1.0E-9_dp
3171 : ELSE
3172 36 : mem_D_local_GB = REAL(max_n_local_grid, dp)**2*8.0_dp*1.0E-9_dp
3173 : END IF
3174 36 : mem_phi_local_GB = REAL(max_n_local_grid, dp)*REAL(max_n_ao_used, dp)*8.0_dp*1.0E-9_dp
3175 : mem_dlp_GB = REAL(max_n_local_grid, dp)*REAL(n_loc_ri_max, dp)*8.0_dp* &
3176 36 : REAL(1 + n_threads, dp)*1.0E-9_dp
3177 36 : mem_Zlp_peak_GB = mem_D_local_GB + mem_phi_local_GB + mem_dlp_GB
3178 :
3179 : ! Available memory per process = node MemLikelyFree / ranks-per-node, min across ranks
3180 : ! (0 on non-Linux => warnings suppressed below).
3181 36 : CALL ri_rs_mem_avail_per_proc_GB(bs_env, mem_avail_GB)
3182 :
3183 36 : IF (bs_env%unit_nr > 0) THEN
3184 18 : WRITE (bs_env%unit_nr, '(A)') ' '
3185 18 : WRITE (bs_env%unit_nr, '(T2,A)') 'RI-RS memory estimate per MPI process:'
3186 : WRITE (bs_env%unit_nr, '(T4,A,F37.2,A)') &
3187 18 : 'Available memory per process (system)', mem_avail_GB, ' GB'
3188 : WRITE (bs_env%unit_nr, '(T4,A,F18.2,A)') &
3189 18 : 'Required for Z_lP (dense upper bound; actual is sparser)', mem_Z_lP_GB, ' GB'
3190 : WRITE (bs_env%unit_nr, '(T4,A,F25.2,A)') &
3191 18 : 'Required for χ, W, Σ panels (peak per panel step)', mem_panels_GB, ' GB'
3192 : WRITE (bs_env%unit_nr, '(T4,A,F17.2,A)') &
3193 18 : 'Required for Z_lP solve peak (D_local+φ, worst-case atom)', mem_Zlp_peak_GB, ' GB'
3194 : WRITE (bs_env%unit_nr, '(T6,A,I21,A,F10.2,A)') &
3195 18 : 'worst-case n_local_grid', max_n_local_grid, ' points (D_local', mem_D_local_GB, ' GB)'
3196 18 : WRITE (bs_env%unit_nr, '(A)') ' '
3197 :
3198 : END IF
3199 :
3200 36 : IF (mem_avail_GB > 0.0_dp .AND. mem_Z_lP_GB > mem_avail_GB) THEN
3201 : WRITE (msg, '(A,F0.2,A,F0.2,A)') &
3202 0 : "The estimated memory for Z_lP, ", mem_Z_lP_GB, " GB per process, exceeds the "// &
3203 0 : "available ", mem_avail_GB, " GB. Z_lP (n_grid x n_RI) is distributed across all "// &
3204 : "MPI ranks, so add nodes, use fewer MPI ranks per node, or raise "// &
3205 : "N_PROCS_PER_ATOM_Z_LP to distribute each atom block via ScaLAPACK, which reduces "// &
3206 0 : "the per-rank memory roughly by the number of ranks per atom."
3207 0 : CPWARN(TRIM(msg))
3208 : END IF
3209 :
3210 36 : IF (mem_avail_GB > 0.0_dp .AND. mem_panels_GB > mem_avail_GB) THEN
3211 : WRITE (msg, '(A,F0.2,A,F0.2,A)') &
3212 0 : "The estimated peak memory of the chi/W/Sigma panels, ", mem_panels_GB, &
3213 0 : " GB per process, exceeds the available ", mem_avail_GB, " GB. Panel memory "// &
3214 : "scales roughly as 3*panel_size*n_grid/n_procs, so add nodes, use fewer MPI ranks "// &
3215 0 : "per node, or raise N_PANELS for more but smaller panels."
3216 0 : CPWARN(TRIM(msg))
3217 : END IF
3218 :
3219 36 : IF (mem_avail_GB > 0.0_dp .AND. mem_Zlp_peak_GB > mem_avail_GB) THEN
3220 : WRITE (msg, '(A,F0.2,A,F0.2,A)') &
3221 0 : "The estimated peak memory of the Z_lP solve, ", mem_Zlp_peak_GB, &
3222 0 : " GB per process, exceeds the available ", mem_avail_GB, &
3223 : " GB. The per-atom Gram matrix D_local(n_local_grid, n_local_grid) dominates and "// &
3224 : "scales as n_local_grid^2, and it is not balanced across ranks: the rank owning "// &
3225 : "the atom with the largest integration sphere peaks well above the average. "// &
3226 : "Either raise N_PROCS_PER_ATOM_Z_LP to distribute D_local block-cyclic via "// &
3227 : "ScaLAPACK, which reduces that term roughly by the number of ranks per atom at no "// &
3228 : "loss of accuracy, or lower CUTOFF_RADIUS_RL_RI, which shrinks D_local as "// &
3229 : "n_local_grid^2 but trades accuracy, or use fewer MPI ranks per node so that each "// &
3230 0 : "rank has more memory for the peak atom."
3231 0 : CPWARN(TRIM(msg))
3232 : END IF
3233 :
3234 36 : CALL timestop(handle)
3235 :
3236 108 : END SUBROUTINE print_ri_rs_memory_estimate
3237 :
3238 : ! **************************************************************************************************
3239 : !> \brief Creates an empty (panel_chunks x neighborhood_chunks) DBCSR matrix with zero blocks
3240 : !> pre-allocated only where |centroid(panel_row r) - centroid(column c)| <= cutoff.
3241 : !> Used with retain_sparsity=.TRUE. in the subsequent dbcsr_multiply so distant blocks
3242 : !> of the grid-basis panels (φ G φ^T, Z W Z^T, ...) are never computed at all.
3243 : !> \param L_pan ...
3244 : !> \param L_full ...
3245 : !> \param centroids ...
3246 : !> \param cutoff ...
3247 : !> \param blk0 ...
3248 : !> \param A_template ...
3249 : !> \param col_map ...
3250 : ! **************************************************************************************************
3251 124 : SUBROUTINE build_geo_template_panel(L_pan, L_full, centroids, cutoff, blk0, A_template, col_map)
3252 : TYPE(dbcsr_type), INTENT(IN) :: L_pan, L_full
3253 : REAL(KIND=dp), DIMENSION(:, :), INTENT(IN) :: centroids
3254 : REAL(KIND=dp), INTENT(IN) :: cutoff
3255 : INTEGER, INTENT(IN) :: blk0
3256 : TYPE(dbcsr_type), INTENT(OUT) :: A_template
3257 : INTEGER, DIMENSION(:), INTENT(IN), OPTIONAL :: col_map
3258 :
3259 : CHARACTER(LEN=*), PARAMETER :: routineN = 'build_geo_template_panel'
3260 :
3261 : INTEGER :: c, cg, cs, handle, my_pcol, my_prow, &
3262 : n_grid_blks, n_pan_blks, npcols, &
3263 : nprows, r, rs
3264 124 : INTEGER, DIMENSION(:), POINTER :: grid_blk_sizes, pan_blk_sizes
3265 : REAL(KIND=dp) :: cutoff2
3266 124 : REAL(KIND=dp), ALLOCATABLE :: zero_blk(:, :)
3267 : TYPE(dbcsr_distribution_type) :: dist
3268 :
3269 124 : CALL timeset(routineN, handle)
3270 :
3271 124 : cutoff2 = cutoff**2
3272 124 : CALL dbcsr_get_info(L_pan, nblkrows_total=n_pan_blks, row_blk_size=pan_blk_sizes)
3273 124 : CALL dbcsr_get_info(L_full, nblkrows_total=n_grid_blks, row_blk_size=grid_blk_sizes)
3274 :
3275 : ! create_product_matrix assigns row r to process MOD(r-1,nprows) and
3276 : ! col c to MOD(c-1,npcols), so we can determine local ownership analytically.
3277 124 : CALL create_product_matrix(L_pan, L_full, 'N', 'T', A_template)
3278 124 : CALL dbcsr_get_info(A_template, distribution=dist)
3279 : CALL dbcsr_distribution_get(dist, nprows=nprows, npcols=npcols, &
3280 124 : myprow=my_prow, mypcol=my_pcol)
3281 :
3282 : ALLOCATE (zero_blk(MAXVAL(pan_blk_sizes(1:n_pan_blks)), &
3283 4402 : MAXVAL(grid_blk_sizes(1:n_grid_blks))))
3284 124 : zero_blk(:, :) = 0.0_dp
3285 :
3286 1426 : DO r = 1, n_pan_blks
3287 1302 : IF (MOD(r - 1, nprows) /= my_prow) CYCLE
3288 651 : rs = pan_blk_sizes(r)
3289 14446 : DO c = 1, n_grid_blks
3290 13671 : IF (MOD(c - 1, npcols) /= my_pcol) CYCLE
3291 13671 : cg = c
3292 13671 : IF (PRESENT(col_map)) cg = col_map(c)
3293 13671 : IF ((centroids(1, blk0 + r - 1) - centroids(1, cg))**2 + &
3294 : (centroids(2, blk0 + r - 1) - centroids(2, cg))**2 + &
3295 1302 : (centroids(3, blk0 + r - 1) - centroids(3, cg))**2 <= cutoff2) THEN
3296 12741 : cs = grid_blk_sizes(c)
3297 12741 : CALL dbcsr_put_block(A_template, r, c, zero_blk(1:rs, 1:cs))
3298 : END IF
3299 : END DO
3300 : END DO
3301 124 : CALL dbcsr_finalize(A_template)
3302 :
3303 124 : DEALLOCATE (zero_blk)
3304 124 : CALL timestop(handle)
3305 :
3306 124 : END SUBROUTINE build_geo_template_panel
3307 :
3308 : ! **************************************************************************************************
3309 : !> \brief Slices a contiguous range of grid row-blocks [blk0, blk1] out of a (grid x n) DBCSR
3310 : !> matrix into a new (P x n) panel matrix: iterate the source's local blocks, put the
3311 : !> in-range ones into the panel with a remapped row-block index, then finalize. Row-block
3312 : !> index i of the panel corresponds to source row-block blk0+i-1.
3313 : !> \param mat_full ...
3314 : !> \param blk0 ...
3315 : !> \param blk1 ...
3316 : !> \param mat_panel ...
3317 : ! **************************************************************************************************
3318 2928 : SUBROUTINE extract_grid_panel(mat_full, blk0, blk1, mat_panel)
3319 :
3320 : TYPE(dbcsr_type), INTENT(INOUT) :: mat_full
3321 : INTEGER, INTENT(IN) :: blk0, blk1
3322 : TYPE(dbcsr_type), INTENT(OUT) :: mat_panel
3323 :
3324 : CHARACTER(LEN=*), PARAMETER :: routineN = 'extract_grid_panel'
3325 :
3326 : INTEGER :: handle, ib, jb, npb
3327 2928 : INTEGER, DIMENSION(:), POINTER :: col_blk_full, col_dist_full, &
3328 2928 : row_blk_full, row_blk_pan, &
3329 2928 : row_dist_full, row_dist_pan
3330 2928 : REAL(KIND=dp), DIMENSION(:, :), POINTER :: blk
3331 : TYPE(dbcsr_distribution_type) :: dist_full, dist_pan
3332 : TYPE(dbcsr_iterator_type) :: iter
3333 :
3334 2928 : CALL timeset(routineN, handle)
3335 :
3336 : CALL dbcsr_get_info(mat_full, distribution=dist_full, &
3337 2928 : row_blk_size=row_blk_full, col_blk_size=col_blk_full)
3338 2928 : CALL dbcsr_distribution_get(dist_full, row_dist=row_dist_full, col_dist=col_dist_full)
3339 :
3340 2928 : npb = blk1 - blk0 + 1
3341 11712 : ALLOCATE (row_dist_pan(npb), row_blk_pan(npb))
3342 90504 : row_dist_pan(:) = row_dist_full(blk0:blk1)
3343 90504 : row_blk_pan(:) = row_blk_full(blk0:blk1)
3344 :
3345 : CALL dbcsr_distribution_new(dist_pan, template=dist_full, &
3346 2928 : row_dist=row_dist_pan, col_dist=col_dist_full)
3347 : CALL dbcsr_create(mat_panel, name="grid_panel", dist=dist_pan, &
3348 : matrix_type=dbcsr_type_no_symmetry, &
3349 2928 : row_blk_size=row_blk_pan, col_blk_size=col_blk_full)
3350 :
3351 2928 : CALL dbcsr_iterator_start(iter, mat_full)
3352 54828 : DO WHILE (dbcsr_iterator_blocks_left(iter))
3353 51900 : CALL dbcsr_iterator_next_block(iter, ib, jb, blk)
3354 51900 : IF (ib < blk0 .OR. ib > blk1) CYCLE
3355 54828 : CALL dbcsr_put_block(mat_panel, ib - blk0 + 1, jb, blk)
3356 : END DO
3357 2928 : CALL dbcsr_iterator_stop(iter)
3358 2928 : CALL dbcsr_finalize(mat_panel)
3359 :
3360 2928 : CALL dbcsr_distribution_release(dist_pan)
3361 2928 : DEALLOCATE (row_dist_pan, row_blk_pan)
3362 :
3363 2928 : CALL timestop(handle)
3364 :
3365 8784 : END SUBROUTINE extract_grid_panel
3366 :
3367 : ! **************************************************************************************************
3368 : !> \brief Marks which column blocks of a DBCSR matrix carry at least one non-zero block anywhere
3369 : !> (global union). Used to restrict the inner index of the panel multiplies to the
3370 : !> AO/RI atoms that actually touch the panel (exact: dropped rows only meet zeros).
3371 : !> \param matrix ...
3372 : !> \param para_env ...
3373 : !> \param used ...
3374 : ! **************************************************************************************************
3375 2318 : SUBROUTINE collect_used_col_blocks(matrix, para_env, used)
3376 :
3377 : TYPE(dbcsr_type), INTENT(INOUT) :: matrix
3378 : TYPE(mp_para_env_type), INTENT(IN), POINTER :: para_env
3379 : LOGICAL, ALLOCATABLE, DIMENSION(:), INTENT(OUT) :: used
3380 :
3381 : CHARACTER(LEN=*), PARAMETER :: routineN = 'collect_used_col_blocks'
3382 :
3383 : INTEGER :: handle, ib, jb, nblkcols
3384 : INTEGER, ALLOCATABLE, DIMENSION(:) :: iused
3385 2318 : REAL(KIND=dp), DIMENSION(:, :), POINTER :: blk
3386 : TYPE(dbcsr_iterator_type) :: iter
3387 :
3388 2318 : CALL timeset(routineN, handle)
3389 :
3390 2318 : CALL dbcsr_get_info(matrix, nblkcols_total=nblkcols)
3391 6954 : ALLOCATE (iused(nblkcols))
3392 2318 : iused(:) = 0
3393 :
3394 2318 : CALL dbcsr_iterator_start(iter, matrix)
3395 42591 : DO WHILE (dbcsr_iterator_blocks_left(iter))
3396 40273 : CALL dbcsr_iterator_next_block(iter, ib, jb, blk)
3397 40273 : iused(jb) = 1
3398 : END DO
3399 2318 : CALL dbcsr_iterator_stop(iter)
3400 :
3401 2318 : CALL para_env%sum(iused)
3402 :
3403 4636 : ALLOCATE (used(nblkcols))
3404 7784 : used(:) = (iused(:) > 0)
3405 2318 : DEALLOCATE (iused)
3406 :
3407 2318 : CALL timestop(handle)
3408 :
3409 4636 : END SUBROUTINE collect_used_col_blocks
3410 :
3411 : ! **************************************************************************************************
3412 : !> \brief Copies the flagged block rows (compress_rows=.TRUE.) or block columns (.FALSE.) of a
3413 : !> DBCSR matrix into a compressed matrix. The subset keeps the parent's process assignment
3414 : !> along the compressed dimension, so every block stays on its owning rank: the extraction
3415 : !> is purely local (zero communication), like extract_grid_panel.
3416 : !> \param mat_full ...
3417 : !> \param used ...
3418 : !> \param mat_out ...
3419 : !> \param compress_rows ...
3420 : !> \param blk_map ...
3421 : ! **************************************************************************************************
3422 6224 : SUBROUTINE extract_masked_blocks(mat_full, used, mat_out, compress_rows, blk_map)
3423 :
3424 : TYPE(dbcsr_type), INTENT(INOUT) :: mat_full
3425 : LOGICAL, DIMENSION(:), INTENT(IN) :: used
3426 : TYPE(dbcsr_type), INTENT(OUT) :: mat_out
3427 : LOGICAL, INTENT(IN) :: compress_rows
3428 : INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT), &
3429 : OPTIONAL :: blk_map
3430 :
3431 : CHARACTER(LEN=*), PARAMETER :: routineN = 'extract_masked_blocks'
3432 :
3433 : INTEGER :: handle, ib, jb, n_blk, n_sub, r
3434 6224 : INTEGER, ALLOCATABLE, DIMENSION(:) :: inv_map
3435 6224 : INTEGER, DIMENSION(:), POINTER :: blk_full, blk_sub, col_blk_full, &
3436 6224 : col_dist_full, dist_full_1d, &
3437 6224 : dist_sub_1d, row_blk_full, &
3438 6224 : row_dist_full
3439 6224 : REAL(KIND=dp), DIMENSION(:, :), POINTER :: blk
3440 : TYPE(dbcsr_distribution_type) :: dist_full, dist_sub
3441 : TYPE(dbcsr_iterator_type) :: iter
3442 :
3443 6224 : CALL timeset(routineN, handle)
3444 :
3445 : CALL dbcsr_get_info(mat_full, distribution=dist_full, &
3446 6224 : row_blk_size=row_blk_full, col_blk_size=col_blk_full)
3447 6224 : CALL dbcsr_distribution_get(dist_full, row_dist=row_dist_full, col_dist=col_dist_full)
3448 :
3449 6224 : IF (compress_rows) THEN
3450 3906 : blk_full => row_blk_full
3451 3906 : dist_full_1d => row_dist_full
3452 : ELSE
3453 2318 : blk_full => col_blk_full
3454 2318 : dist_full_1d => col_dist_full
3455 : END IF
3456 6224 : n_blk = SIZE(blk_full)
3457 :
3458 24024 : n_sub = COUNT(used)
3459 6224 : CPASSERT(n_sub > 0)
3460 37344 : ALLOCATE (inv_map(n_blk), blk_sub(n_sub), dist_sub_1d(n_sub))
3461 6308 : IF (PRESENT(blk_map)) ALLOCATE (blk_map(n_sub))
3462 6224 : inv_map(:) = 0
3463 6224 : r = 0
3464 24024 : DO ib = 1, n_blk
3465 24024 : IF (used(ib)) THEN
3466 17800 : r = r + 1
3467 17800 : inv_map(ib) = r
3468 17800 : blk_sub(r) = blk_full(ib)
3469 17800 : dist_sub_1d(r) = dist_full_1d(ib)
3470 17800 : IF (PRESENT(blk_map)) blk_map(r) = ib
3471 : END IF
3472 : END DO
3473 :
3474 6224 : IF (compress_rows) THEN
3475 : CALL dbcsr_distribution_new(dist_sub, template=dist_full, &
3476 3906 : row_dist=dist_sub_1d, col_dist=col_dist_full)
3477 : CALL dbcsr_create(mat_out, name="row_subset", dist=dist_sub, &
3478 : matrix_type=dbcsr_type_no_symmetry, &
3479 3906 : row_blk_size=blk_sub, col_blk_size=col_blk_full)
3480 : ELSE
3481 : CALL dbcsr_distribution_new(dist_sub, template=dist_full, &
3482 2318 : row_dist=row_dist_full, col_dist=dist_sub_1d)
3483 : CALL dbcsr_create(mat_out, name="col_subset", dist=dist_sub, &
3484 : matrix_type=dbcsr_type_no_symmetry, &
3485 2318 : row_blk_size=row_blk_full, col_blk_size=blk_sub)
3486 : END IF
3487 :
3488 6224 : CALL dbcsr_iterator_start(iter, mat_full)
3489 61582 : DO WHILE (dbcsr_iterator_blocks_left(iter))
3490 55358 : CALL dbcsr_iterator_next_block(iter, ib, jb, blk)
3491 61582 : IF (compress_rows) THEN
3492 15085 : IF (inv_map(ib) > 0) CALL dbcsr_put_block(mat_out, inv_map(ib), jb, blk)
3493 : ELSE
3494 40273 : IF (inv_map(jb) > 0) CALL dbcsr_put_block(mat_out, ib, inv_map(jb), blk)
3495 : END IF
3496 : END DO
3497 6224 : CALL dbcsr_iterator_stop(iter)
3498 6224 : CALL dbcsr_finalize(mat_out)
3499 :
3500 6224 : CALL dbcsr_distribution_release(dist_sub)
3501 6224 : DEALLOCATE (inv_map, blk_sub, dist_sub_1d)
3502 :
3503 6224 : CALL timestop(handle)
3504 :
3505 12448 : END SUBROUTINE extract_masked_blocks
3506 :
3507 : ! **************************************************************************************************
3508 : !> \brief Pre-seeds a square blocked DBCSR matrix with zero blocks only for block pairs whose
3509 : !> centers lie within radius, for use with copy_fm_to_dbcsr(keep_sparsity=T) or
3510 : !> dbcsr_multiply(retain_sparsity=T). Consumers: the CUTOFF_RADIUS_G_W operator truncation
3511 : !> (atom-blocked, centers = atom_centers) and the RT-BSE CUTOFF_RADIUS_W0 truncation of the
3512 : !> grid-basis W^0 (grid-blocked, centers = chunk_centroids).
3513 : !> \param matrix ...
3514 : !> \param centers block positions, one column per block row/column of matrix
3515 : !> \param radius truncation radius, same units as centers (bohr)
3516 : ! **************************************************************************************************
3517 6 : SUBROUTINE reserve_blocks_within_radius(matrix, centers, radius)
3518 :
3519 : TYPE(dbcsr_type), INTENT(INOUT) :: matrix
3520 : REAL(KIND=dp), DIMENSION(:, :), INTENT(IN) :: centers
3521 : REAL(KIND=dp), INTENT(IN) :: radius
3522 :
3523 : CHARACTER(LEN=*), PARAMETER :: routineN = 'reserve_blocks_within_radius'
3524 :
3525 : INTEGER :: handle, i, j, my_pcol, my_prow, &
3526 : nblkcols, nblkrows
3527 6 : INTEGER, DIMENSION(:), POINTER :: col_blk, col_dist, row_blk, row_dist
3528 : REAL(KIND=dp) :: radius2
3529 6 : REAL(KIND=dp), ALLOCATABLE :: zero_blk(:, :)
3530 : TYPE(dbcsr_distribution_type) :: dist
3531 :
3532 6 : CALL timeset(routineN, handle)
3533 :
3534 : CALL dbcsr_get_info(matrix, nblkrows_total=nblkrows, nblkcols_total=nblkcols, &
3535 6 : row_blk_size=row_blk, col_blk_size=col_blk, distribution=dist)
3536 : CALL dbcsr_distribution_get(dist, row_dist=row_dist, col_dist=col_dist, &
3537 6 : myprow=my_prow, mypcol=my_pcol)
3538 6 : CPASSERT(nblkrows == SIZE(centers, 2))
3539 6 : CPASSERT(nblkcols == SIZE(centers, 2))
3540 :
3541 6 : radius2 = radius**2
3542 144 : ALLOCATE (zero_blk(MAXVAL(row_blk(1:nblkrows)), MAXVAL(col_blk(1:nblkcols))))
3543 6 : zero_blk(:, :) = 0.0_dp
3544 :
3545 66 : DO i = 1, nblkrows
3546 60 : IF (row_dist(i) /= my_prow) CYCLE
3547 336 : DO j = 1, nblkcols
3548 300 : IF (col_dist(j) /= my_pcol) CYCLE
3549 300 : IF ((centers(1, i) - centers(1, j))**2 + (centers(2, i) - centers(2, j))**2 + &
3550 60 : (centers(3, i) - centers(3, j))**2 <= radius2) THEN
3551 168 : CALL dbcsr_put_block(matrix, i, j, zero_blk(1:row_blk(i), 1:col_blk(j)))
3552 : END IF
3553 : END DO
3554 : END DO
3555 6 : CALL dbcsr_finalize(matrix)
3556 :
3557 6 : DEALLOCATE (zero_blk)
3558 6 : CALL timestop(handle)
3559 :
3560 6 : END SUBROUTINE reserve_blocks_within_radius
3561 :
3562 : ! **************************************************************************************************
3563 : !> \brief Creates the (empty) result matrix of op(mat_left) * op(mat_right) with the correct block
3564 : !> structure and a distribution on the shared process grid, ready to be filled by
3565 : !> dbcsr_multiply. Row structure comes from op(left), column structure from op(right).
3566 : !> \param mat_left ...
3567 : !> \param mat_right ...
3568 : !> \param transa 'N' or 'T' applied to mat_left
3569 : !> \param transb 'N' or 'T' applied to mat_right
3570 : !> \param mat_out ...
3571 : ! **************************************************************************************************
3572 8286 : SUBROUTINE create_product_matrix(mat_left, mat_right, transa, transb, mat_out)
3573 :
3574 : TYPE(dbcsr_type), INTENT(IN) :: mat_left, mat_right
3575 : CHARACTER(LEN=1), INTENT(IN) :: transa, transb
3576 : TYPE(dbcsr_type), INTENT(OUT) :: mat_out
3577 :
3578 : CHARACTER(LEN=*), PARAMETER :: routineN = 'create_product_matrix'
3579 :
3580 : INTEGER :: handle, i, npcols, nprows
3581 8286 : INTEGER, DIMENSION(:), POINTER :: col_blk_l, col_blk_r, out_col_blk, &
3582 8286 : out_col_dist, out_row_blk, &
3583 8286 : out_row_dist, row_blk_l, row_blk_r
3584 : TYPE(dbcsr_distribution_type) :: dist_l, dist_out
3585 :
3586 8286 : CALL timeset(routineN, handle)
3587 :
3588 8286 : CALL dbcsr_get_info(mat_left, distribution=dist_l, row_blk_size=row_blk_l, col_blk_size=col_blk_l)
3589 8286 : CALL dbcsr_get_info(mat_right, row_blk_size=row_blk_r, col_blk_size=col_blk_r)
3590 8286 : CALL dbcsr_distribution_get(dist_l, nprows=nprows, npcols=npcols)
3591 :
3592 : ! block SIZES follow op(left)/op(right); DISTRIBUTIONS are freshly round-robined onto the
3593 : ! shared process grid (a transposed operand's row-dist is NOT a valid col-dist on a
3594 : ! non-square grid). dbcsr_multiply redistributes internally, so any valid mapping works.
3595 8286 : IF (transa == 'N') THEN
3596 8286 : out_row_blk => row_blk_l
3597 : ELSE
3598 0 : out_row_blk => col_blk_l
3599 : END IF
3600 8286 : IF (transb == 'N') THEN
3601 6012 : out_col_blk => col_blk_r
3602 : ELSE
3603 2274 : out_col_blk => row_blk_r
3604 : END IF
3605 :
3606 41430 : ALLOCATE (out_row_dist(SIZE(out_row_blk)), out_col_dist(SIZE(out_col_blk)))
3607 130224 : DO i = 1, SIZE(out_row_blk)
3608 130224 : out_row_dist(i) = MOD(i - 1, nprows)
3609 : END DO
3610 57344 : DO i = 1, SIZE(out_col_blk)
3611 57344 : out_col_dist(i) = MOD(i - 1, npcols)
3612 : END DO
3613 :
3614 : CALL dbcsr_distribution_new(dist_out, template=dist_l, &
3615 8286 : row_dist=out_row_dist, col_dist=out_col_dist)
3616 : CALL dbcsr_create(mat_out, name="panel_product", dist=dist_out, &
3617 : matrix_type=dbcsr_type_no_symmetry, &
3618 8286 : row_blk_size=out_row_blk, col_blk_size=out_col_blk)
3619 8286 : CALL dbcsr_distribution_release(dist_out)
3620 8286 : DEALLOCATE (out_row_dist, out_col_dist)
3621 :
3622 8286 : CALL timestop(handle)
3623 :
3624 24858 : END SUBROUTINE create_product_matrix
3625 :
3626 : ! **************************************************************************************************
3627 : !> \brief Builds the AO-space Green's function operator G^occ/vir_µν (AO x AO DBCSR)
3628 : !> \param bs_env ...
3629 : !> \param tau ...
3630 : !> \param ispin ...
3631 : !> \param occ ...
3632 : !> \param vir ...
3633 : !> \param template ...
3634 : !> \param matrix_G_ao ...
3635 : ! **************************************************************************************************
3636 5604 : SUBROUTINE build_G_ao(bs_env, tau, ispin, occ, vir, template, matrix_G_ao)
3637 :
3638 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
3639 : REAL(KIND=dp), INTENT(IN) :: tau
3640 : INTEGER, INTENT(IN) :: ispin
3641 : LOGICAL, INTENT(IN) :: occ, vir
3642 : TYPE(dbcsr_type), INTENT(INOUT) :: template
3643 : TYPE(dbcsr_type), INTENT(OUT) :: matrix_G_ao
3644 :
3645 : CHARACTER(LEN=*), PARAMETER :: routineN = 'build_G_ao'
3646 :
3647 : INTEGER :: handle
3648 2802 : INTEGER, DIMENSION(:), POINTER :: blk_ao, dist_row_ao
3649 : TYPE(cp_fm_type), POINTER :: fm_G
3650 : TYPE(dbcsr_distribution_type) :: dist_ao_ao
3651 :
3652 2802 : CALL timeset(routineN, handle)
3653 :
3654 2802 : IF (occ) THEN
3655 1422 : fm_G => bs_env%fm_Gocc
3656 : ELSE
3657 1380 : fm_G => bs_env%fm_Gvir
3658 : END IF
3659 :
3660 2802 : CALL G_occ_vir(bs_env, tau, fm_G, ispin, occ=occ, vir=vir)
3661 :
3662 2802 : CALL setup_square_topology(template, dist_ao_ao, blk_ao, dist_row_ao)
3663 : CALL dbcsr_create(matrix_G_ao, name="G_ao", dist=dist_ao_ao, &
3664 : matrix_type=dbcsr_type_no_symmetry, &
3665 2802 : row_blk_size=blk_ao, col_blk_size=blk_ao)
3666 :
3667 : ! Optional CUTOFF_RADIUS_G_W operator truncation: only atom-pair blocks within the radius
3668 : ! are reserved and filled.
3669 2802 : IF (bs_env%ri_rs%cutoff_radius_g_w > 0.0_dp .AND. &
3670 : ALLOCATED(bs_env%ri_rs%atom_centers)) THEN
3671 : CALL reserve_blocks_within_radius(matrix_G_ao, bs_env%ri_rs%atom_centers, &
3672 0 : bs_env%ri_rs%cutoff_radius_g_w)
3673 0 : CALL copy_fm_to_dbcsr(fm_G, matrix_G_ao, keep_sparsity=.TRUE.)
3674 : ELSE
3675 2802 : CALL copy_fm_to_dbcsr(fm_G, matrix_G_ao, keep_sparsity=.FALSE.)
3676 : END IF
3677 2802 : CALL dbcsr_filter(matrix_G_ao, bs_env%eps_filter)
3678 :
3679 : ! release only the topology; keep matrix_G_ao for the caller
3680 2802 : CALL release_square_topology(dist=dist_ao_ao, mapped_dist=dist_row_ao)
3681 :
3682 2802 : CALL timestop(handle)
3683 :
3684 2802 : END SUBROUTINE build_G_ao
3685 :
3686 : ! **************************************************************************************************
3687 : !> \brief Panel-streaming evaluation of out += scale * L_out^T (A_grid ∘ B_grid) L_out,
3688 : !> where A_grid = L_A M_A L_A^T and B_grid = L_B M_B L_B^T, WITHOUT ever forming the full
3689 : !> grid x grid objects. The grid (row) index is processed in panels of ~panel_size rows; for
3690 : !> each panel only P x grid slabs are built, Hadamard-multiplied, and contracted into the
3691 : !> (small) output. Algebraically identical to L_out^T (A_grid ∘ B_grid) L_out summed over
3692 : !> grid rows, so the result matches the non-streamed path to eps_filter.
3693 : !>
3694 : !> Mapping (L in {phi (grid x AO), Z (grid x RI)}, M the AO/RI-space operator):
3695 : !> chi : L_A=L_B=phi, M_A=G_occ_ao, M_B=G_vir_ao, L_out=Z -> RI x RI
3696 : !> Sig : L_A=phi (M_A=D/G), L_B=Z (M_B=V/W), L_out=phi -> AO x AO
3697 : !> \param L_A ...
3698 : !> \param M_A ...
3699 : !> \param L_B ...
3700 : !> \param M_B ...
3701 : !> \param L_out ...
3702 : !> \param mat_out ...
3703 : !> \param scale ...
3704 : !> \param eps ...
3705 : !> \param para_env ...
3706 : !> \param pan_first ...
3707 : !> \param pan_last ...
3708 : !> \param lb_eq_la ...
3709 : !> \param lout_eq_la ...
3710 : !> \param zero_out ...
3711 : !> \param keep_sparsity ...
3712 : !> \param centroids ...
3713 : !> \param cutoff ...
3714 : !> \param grid_occupation ...
3715 : ! **************************************************************************************************
3716 632 : SUBROUTINE contract_grid_panels(L_A, M_A, L_B, M_B, L_out, mat_out, scale, eps, para_env, &
3717 632 : pan_first, pan_last, lb_eq_la, lout_eq_la, zero_out, &
3718 632 : keep_sparsity, centroids, cutoff, grid_occupation)
3719 :
3720 : TYPE(dbcsr_type), INTENT(INOUT), TARGET :: L_A
3721 : TYPE(dbcsr_type), INTENT(INOUT) :: M_A
3722 : TYPE(dbcsr_type), INTENT(INOUT), TARGET :: L_B
3723 : TYPE(dbcsr_type), INTENT(INOUT) :: M_B
3724 : TYPE(dbcsr_type), INTENT(INOUT), TARGET :: L_out
3725 : TYPE(dbcsr_type), INTENT(INOUT) :: mat_out
3726 : REAL(KIND=dp), INTENT(IN) :: scale, eps
3727 : TYPE(mp_para_env_type), INTENT(IN), POINTER :: para_env
3728 : INTEGER, DIMENSION(:), INTENT(IN) :: pan_first, pan_last
3729 : LOGICAL, INTENT(IN) :: lb_eq_la, lout_eq_la, zero_out
3730 : LOGICAL, INTENT(IN), OPTIONAL :: keep_sparsity
3731 : REAL(KIND=dp), DIMENSION(:, :), INTENT(IN), &
3732 : OPTIONAL :: centroids
3733 : REAL(KIND=dp), INTENT(IN), OPTIONAL :: cutoff
3734 : REAL(KIND=dp), INTENT(OUT), OPTIONAL :: grid_occupation
3735 :
3736 : CHARACTER(LEN=*), PARAMETER :: routineN = 'contract_grid_panels'
3737 :
3738 : INTEGER :: blk0, blk1, handle, ipan, n_grid_total, &
3739 : ncols_pan, nrows_pan
3740 632 : INTEGER, ALLOCATABLE, DIMENSION(:) :: gmap
3741 : LOGICAL :: my_keep_sparsity, use_cutoff
3742 632 : LOGICAL, ALLOCATABLE, DIMENSION(:) :: grid_used, usedA, usedB
3743 : TYPE(dbcsr_type) :: A_pan, B_pan, C_pan, LA_pan, LA_panC, &
3744 : LB_pan, LB_panC, Lout_pan, MA_sub, &
3745 : MB_sub, tmp2, tmpA, tmpB
3746 : TYPE(dbcsr_type), POINTER :: RB_A, RB_B, RB_out
3747 : TYPE(dbcsr_type), TARGET :: LA_near, LB_near, Lout_near
3748 :
3749 632 : CALL timeset(routineN, handle)
3750 :
3751 632 : my_keep_sparsity = .FALSE.
3752 632 : IF (PRESENT(keep_sparsity)) my_keep_sparsity = keep_sparsity
3753 632 : use_cutoff = PRESENT(centroids) .AND. PRESENT(cutoff)
3754 632 : IF (use_cutoff) use_cutoff = cutoff > 0.0_dp
3755 632 : IF (PRESENT(grid_occupation)) grid_occupation = 0.0_dp
3756 :
3757 632 : CALL dbcsr_get_info(L_A, nfullrows_total=n_grid_total)
3758 :
3759 632 : IF (zero_out) CALL dbcsr_set(mat_out, 0.0_dp)
3760 :
3761 1286 : DO ipan = 1, SIZE(pan_first)
3762 654 : blk0 = pan_first(ipan)
3763 654 : blk1 = pan_last(ipan)
3764 :
3765 : ! phi/Z panel slices (P x n)
3766 654 : CALL extract_grid_panel(L_A, blk0, blk1, LA_pan)
3767 654 : IF (.NOT. lb_eq_la) CALL extract_grid_panel(L_B, blk0, blk1, LB_pan)
3768 654 : IF (.NOT. lout_eq_la) CALL extract_grid_panel(L_out, blk0, blk1, Lout_pan)
3769 :
3770 : ! Which AO/RI atoms (column blocks) actually touch this panel: the inner index of
3771 : ! every multiply below is restricted to them, so only the matching rows of the
3772 : ! system-wide operators M_A/M_B ever enter Cannon (exact: dropped rows meet zeros).
3773 654 : CALL collect_used_col_blocks(LA_pan, para_env, usedA)
3774 654 : IF (.NOT. lb_eq_la) THEN
3775 44 : CALL collect_used_col_blocks(LB_pan, para_env, usedB)
3776 : ELSE
3777 610 : IF (ALLOCATED(usedB)) DEALLOCATE (usedB)
3778 3260 : ALLOCATE (usedB, SOURCE=usedA)
3779 : END IF
3780 654 : IF (.NOT. (ANY(usedA) .AND. ANY(usedB))) THEN
3781 : ! empty panel slice: its Hadamard contribution is exactly zero on all ranks
3782 0 : CALL dbcsr_release(LA_pan)
3783 0 : IF (.NOT. lb_eq_la) CALL dbcsr_release(LB_pan)
3784 0 : IF (.NOT. lout_eq_la) CALL dbcsr_release(Lout_pan)
3785 : CYCLE
3786 : END IF
3787 :
3788 : ! Grid rows within reach of the panel: with the CUTOFF_RADIUS_RL_W truncation only
3789 : ! they can appear as columns of the panel products / inner rows of the L_out multiply,
3790 : ! so the system-wide phi/Z right operands are cut down to this neighborhood slice
3791 : ! (local extraction, zero communication; exact w.r.t. the geo template).
3792 654 : IF (use_cutoff) THEN
3793 44 : CALL mask_grid_blocks_near_panel(centroids, blk0, blk1, cutoff, grid_used)
3794 44 : CALL extract_masked_blocks(L_A, grid_used, LA_near, compress_rows=.TRUE., blk_map=gmap)
3795 44 : IF (.NOT. lb_eq_la) CALL extract_masked_blocks(L_B, grid_used, LB_near, compress_rows=.TRUE.)
3796 44 : IF (.NOT. lout_eq_la) CALL extract_masked_blocks(L_out, grid_used, Lout_near, compress_rows=.TRUE.)
3797 : RB_A => LA_near
3798 : ELSE
3799 : RB_A => L_A
3800 : END IF
3801 654 : IF (lb_eq_la) THEN
3802 : RB_B => RB_A
3803 44 : ELSE IF (use_cutoff) THEN
3804 : RB_B => LB_near
3805 : ELSE
3806 40 : RB_B => L_B
3807 : END IF
3808 654 : IF (lout_eq_la) THEN
3809 : RB_out => RB_A
3810 610 : ELSE IF (use_cutoff) THEN
3811 : RB_out => Lout_near
3812 : ELSE
3813 570 : RB_out => L_out
3814 : END IF
3815 :
3816 : ! A_pan = LA_pan * M_A * L_A^T (P x grid_near).
3817 : ! When cutoff is active, A_pan is pre-seeded with only nearby blocks via
3818 : ! build_geo_template_panel, and the multiply uses retain_sparsity to skip
3819 : ! computing distant blocks entirely (exact: they are zero by locality).
3820 654 : CALL extract_masked_blocks(LA_pan, usedA, LA_panC, compress_rows=.FALSE.)
3821 654 : CALL extract_masked_blocks(M_A, usedA, MA_sub, compress_rows=.TRUE.)
3822 654 : CALL create_product_matrix(LA_panC, MA_sub, 'N', 'N', tmpA)
3823 654 : CALL dbcsr_multiply('N', 'N', 1.0_dp, LA_panC, MA_sub, 0.0_dp, tmpA, filter_eps=eps)
3824 654 : CALL dbcsr_release(MA_sub)
3825 654 : IF (use_cutoff) THEN
3826 : CALL build_geo_template_panel(LA_pan, LA_near, centroids, cutoff, blk0, A_pan, &
3827 44 : col_map=gmap)
3828 : ELSE
3829 610 : CALL create_product_matrix(tmpA, RB_A, 'N', 'T', A_pan)
3830 : END IF
3831 : CALL dbcsr_multiply('N', 'T', 1.0_dp, tmpA, RB_A, 0.0_dp, A_pan, &
3832 654 : filter_eps=eps, retain_sparsity=use_cutoff)
3833 654 : CALL dbcsr_release(tmpA)
3834 :
3835 : ! Grid-basis occupation of A_pan = φ G φ^T, accumulated over ALL panels into the
3836 : ! occupation of the (never formed) full grid x grid object:
3837 : ! sum_panels nnz(A_pan) / n_grid^2, with nnz = occ * pan_rows * pan_cols.
3838 : ! Panel-independent by construction -- a single-panel sample would instead report the
3839 : ! local neighbor count of whichever region happens to land in that panel.
3840 654 : IF (PRESENT(grid_occupation)) THEN
3841 610 : CALL dbcsr_get_info(A_pan, nfullrows_total=nrows_pan, nfullcols_total=ncols_pan)
3842 : grid_occupation = grid_occupation + dbcsr_get_occupation(A_pan)* &
3843 : REAL(ncols_pan, dp)*REAL(nrows_pan, dp)/ &
3844 610 : (REAL(n_grid_total, dp)*REAL(n_grid_total, dp))
3845 : END IF
3846 :
3847 : ! B_pan = LB_pan * M_B * L_B^T (P x grid_near); reuse the L_A slices when L_B == L_A.
3848 : ! With keep_sparsity, B_pan is pre-populated with A_pan's block structure so that
3849 : ! retain_sparsity forces the final multiply to fill only those blocks (exact for ∘).
3850 654 : IF (lb_eq_la) THEN
3851 610 : CALL extract_masked_blocks(M_B, usedA, MB_sub, compress_rows=.TRUE.)
3852 610 : CALL create_product_matrix(LA_panC, MB_sub, 'N', 'N', tmpB)
3853 610 : CALL dbcsr_multiply('N', 'N', 1.0_dp, LA_panC, MB_sub, 0.0_dp, tmpB, filter_eps=eps)
3854 : ELSE
3855 44 : CALL extract_masked_blocks(LB_pan, usedB, LB_panC, compress_rows=.FALSE.)
3856 44 : CALL extract_masked_blocks(M_B, usedB, MB_sub, compress_rows=.TRUE.)
3857 44 : CALL create_product_matrix(LB_panC, MB_sub, 'N', 'N', tmpB)
3858 44 : CALL dbcsr_multiply('N', 'N', 1.0_dp, LB_panC, MB_sub, 0.0_dp, tmpB, filter_eps=eps)
3859 44 : CALL dbcsr_release(LB_panC)
3860 : END IF
3861 654 : CALL dbcsr_release(MB_sub)
3862 654 : IF (my_keep_sparsity) THEN
3863 654 : CALL dbcsr_create(B_pan, template=A_pan)
3864 654 : CALL dbcsr_copy(B_pan, A_pan)
3865 654 : CALL dbcsr_set(B_pan, 0.0_dp)
3866 : CALL dbcsr_multiply('N', 'T', 1.0_dp, tmpB, RB_B, 0.0_dp, B_pan, &
3867 654 : filter_eps=eps, retain_sparsity=.TRUE.)
3868 : ELSE
3869 0 : CALL create_product_matrix(tmpB, RB_B, 'N', 'T', B_pan)
3870 0 : CALL dbcsr_multiply('N', 'T', 1.0_dp, tmpB, RB_B, 0.0_dp, B_pan, filter_eps=eps)
3871 : END IF
3872 654 : CALL dbcsr_release(tmpB)
3873 654 : CALL dbcsr_release(LA_panC)
3874 :
3875 : ! C_pan = scale * (A_pan ∘ B_pan) (P x grid_near)
3876 654 : CALL dbcsr_create(C_pan, template=A_pan)
3877 654 : CALL hadamard_product(A_pan, B_pan, C_pan, scale)
3878 654 : CALL dbcsr_release(A_pan)
3879 654 : CALL dbcsr_release(B_pan)
3880 :
3881 : ! tmp2 = C_pan * L_out (P x n_out; inner index restricted to the neighborhood)
3882 654 : CALL create_product_matrix(C_pan, RB_out, 'N', 'N', tmp2)
3883 654 : CALL dbcsr_multiply('N', 'N', 1.0_dp, C_pan, RB_out, 0.0_dp, tmp2, filter_eps=eps)
3884 654 : CALL dbcsr_release(C_pan)
3885 :
3886 : ! mat_out += L_out_pan^T * tmp2 (accumulate: beta = 1)
3887 654 : IF (lout_eq_la) THEN
3888 44 : CALL dbcsr_multiply('T', 'N', 1.0_dp, LA_pan, tmp2, 1.0_dp, mat_out, filter_eps=eps)
3889 : ELSE
3890 610 : CALL dbcsr_multiply('T', 'N', 1.0_dp, Lout_pan, tmp2, 1.0_dp, mat_out, filter_eps=eps)
3891 610 : CALL dbcsr_release(Lout_pan)
3892 : END IF
3893 654 : CALL dbcsr_release(tmp2)
3894 654 : IF (.NOT. lb_eq_la) CALL dbcsr_release(LB_pan)
3895 654 : CALL dbcsr_release(LA_pan)
3896 3248 : IF (use_cutoff) THEN
3897 44 : CALL dbcsr_release(LA_near)
3898 44 : IF (.NOT. lb_eq_la) CALL dbcsr_release(LB_near)
3899 44 : IF (.NOT. lout_eq_la) CALL dbcsr_release(Lout_near)
3900 : END IF
3901 :
3902 : END DO
3903 :
3904 632 : CALL timestop(handle)
3905 :
3906 1264 : END SUBROUTINE contract_grid_panels
3907 :
3908 : ! **************************************************************************************************
3909 : !> \brief Σ^c-specific panel loop: computes both the occupied (neg) and virtual (pos) contributions
3910 : !> in a single pass over grid panels, forming W_pan = Z_panel × W_aux × Z^T only ONCE per
3911 : !> panel and reusing it for both the G^occ and G^vir Hadamard contractions.
3912 : !>
3913 : !> Computes:
3914 : !> mat_Sigma_neg = φ^T ( (φ G^occ φ^T) ∘ (Z W^MIC Z^T) ) φ
3915 : !> mat_Sigma_pos = φ^T ( (φ G^vir φ^T) ∘ (Z W^MIC Z^T) ) φ
3916 : !>
3917 : !> \param mat_phi ...
3918 : !> \param mat_Z ...
3919 : !> \param mat_G_occ_ao ...
3920 : !> \param mat_G_vir_ao ...
3921 : !> \param mat_W_aux ...
3922 : !> \param mat_Sigma_neg ...
3923 : !> \param mat_Sigma_pos ...
3924 : !> \param eps ...
3925 : !> \param para_env ...
3926 : !> \param pan_first ...
3927 : !> \param pan_last ...
3928 : !> \param keep_sparsity ...
3929 : !> \param centroids ...
3930 : !> \param cutoff ...
3931 : ! **************************************************************************************************
3932 790 : SUBROUTINE contract_grid_panels_sigma_c(mat_phi, mat_Z, mat_G_occ_ao, mat_G_vir_ao, &
3933 : mat_W_aux, mat_Sigma_neg, mat_Sigma_pos, eps, para_env, &
3934 790 : pan_first, pan_last, keep_sparsity, centroids, cutoff)
3935 :
3936 : TYPE(dbcsr_type), INTENT(INOUT), TARGET :: mat_phi, mat_Z
3937 : TYPE(dbcsr_type), INTENT(INOUT) :: mat_G_occ_ao, mat_G_vir_ao, mat_W_aux, &
3938 : mat_Sigma_neg, mat_Sigma_pos
3939 : REAL(KIND=dp), INTENT(IN) :: eps
3940 : TYPE(mp_para_env_type), INTENT(IN), POINTER :: para_env
3941 : INTEGER, DIMENSION(:), INTENT(IN) :: pan_first, pan_last
3942 : LOGICAL, INTENT(IN), OPTIONAL :: keep_sparsity
3943 : REAL(KIND=dp), DIMENSION(:, :), INTENT(IN), &
3944 : OPTIONAL :: centroids
3945 : REAL(KIND=dp), INTENT(IN), OPTIONAL :: cutoff
3946 :
3947 : CHARACTER(LEN=*), PARAMETER :: routineN = 'contract_grid_panels_sigma_c'
3948 :
3949 : INTEGER :: blk0, blk1, handle, ipan
3950 790 : INTEGER, ALLOCATABLE, DIMENSION(:) :: gmap
3951 : LOGICAL :: my_keep_sparsity, use_cutoff
3952 790 : LOGICAL, ALLOCATABLE, DIMENSION(:) :: grid_used, used_ao, used_ri
3953 : TYPE(dbcsr_type) :: A_occ, A_vir, C_pan, G_occ_sub, &
3954 : G_vir_sub, phi_pan, phi_panC, tmp2, &
3955 : tmpA, tmpB, W_pan, W_sub, Z_pan, Z_panC
3956 : TYPE(dbcsr_type), POINTER :: RB_phi, RB_Z
3957 : TYPE(dbcsr_type), TARGET :: phi_near, Z_near
3958 :
3959 790 : CALL timeset(routineN, handle)
3960 :
3961 790 : my_keep_sparsity = .FALSE.
3962 790 : IF (PRESENT(keep_sparsity)) my_keep_sparsity = keep_sparsity
3963 790 : use_cutoff = PRESENT(centroids) .AND. PRESENT(cutoff)
3964 790 : IF (use_cutoff) use_cutoff = cutoff > 0.0_dp
3965 :
3966 790 : CALL dbcsr_set(mat_Sigma_neg, 0.0_dp)
3967 790 : CALL dbcsr_set(mat_Sigma_pos, 0.0_dp)
3968 :
3969 1600 : DO ipan = 1, SIZE(pan_first)
3970 810 : blk0 = pan_first(ipan)
3971 810 : blk1 = pan_last(ipan)
3972 :
3973 810 : CALL extract_grid_panel(mat_phi, blk0, blk1, phi_pan)
3974 810 : CALL extract_grid_panel(mat_Z, blk0, blk1, Z_pan)
3975 :
3976 : ! AO/RI atoms touching this panel: only the matching rows of G_occ/G_vir/W ever
3977 : ! enter the multiplies below (exact: dropped rows meet zero columns of the panel).
3978 810 : CALL collect_used_col_blocks(phi_pan, para_env, used_ao)
3979 810 : CALL collect_used_col_blocks(Z_pan, para_env, used_ri)
3980 810 : IF (.NOT. (ANY(used_ao) .AND. ANY(used_ri))) THEN
3981 0 : CALL dbcsr_release(phi_pan)
3982 0 : CALL dbcsr_release(Z_pan)
3983 0 : CYCLE
3984 : END IF
3985 :
3986 : ! Neighborhood slices of phi/Z (grid rows within cutoff of the panel): they replace
3987 : ! the system-wide right operands in every multiply (local extraction, zero comm).
3988 810 : IF (use_cutoff) THEN
3989 40 : CALL mask_grid_blocks_near_panel(centroids, blk0, blk1, cutoff, grid_used)
3990 40 : CALL extract_masked_blocks(mat_phi, grid_used, phi_near, compress_rows=.TRUE., blk_map=gmap)
3991 40 : CALL extract_masked_blocks(mat_Z, grid_used, Z_near, compress_rows=.TRUE.)
3992 40 : RB_phi => phi_near
3993 40 : RB_Z => Z_near
3994 : ELSE
3995 : RB_phi => mat_phi
3996 : RB_Z => mat_Z
3997 : END IF
3998 :
3999 810 : CALL extract_masked_blocks(phi_pan, used_ao, phi_panC, compress_rows=.FALSE.)
4000 810 : CALL extract_masked_blocks(Z_pan, used_ri, Z_panC, compress_rows=.FALSE.)
4001 810 : CALL extract_masked_blocks(mat_G_occ_ao, used_ao, G_occ_sub, compress_rows=.TRUE.)
4002 810 : CALL extract_masked_blocks(mat_G_vir_ao, used_ao, G_vir_sub, compress_rows=.TRUE.)
4003 810 : CALL extract_masked_blocks(mat_W_aux, used_ri, W_sub, compress_rows=.TRUE.)
4004 :
4005 : ! A_occ = phi_pan × G_occ × phi^T (built first so W_pan can inherit its pattern).
4006 : ! With cutoff active, A_occ is pre-seeded with geo-local blocks so that the
4007 : ! phi^T multiply uses retain_sparsity and never computes distant blocks.
4008 810 : CALL create_product_matrix(phi_panC, G_occ_sub, 'N', 'N', tmpA)
4009 810 : CALL dbcsr_multiply('N', 'N', 1.0_dp, phi_panC, G_occ_sub, 0.0_dp, tmpA, filter_eps=eps)
4010 810 : IF (use_cutoff) THEN
4011 : CALL build_geo_template_panel(phi_pan, phi_near, centroids, cutoff, blk0, A_occ, &
4012 40 : col_map=gmap)
4013 : ELSE
4014 770 : CALL create_product_matrix(tmpA, RB_phi, 'N', 'T', A_occ)
4015 : END IF
4016 : CALL dbcsr_multiply('N', 'T', 1.0_dp, tmpA, RB_phi, 0.0_dp, A_occ, &
4017 810 : filter_eps=eps, retain_sparsity=use_cutoff)
4018 810 : CALL dbcsr_release(tmpA)
4019 810 : CALL dbcsr_release(G_occ_sub)
4020 :
4021 : ! A_vir = phi_pan × G_vir × phi^T (same pre-screen as A_occ)
4022 810 : CALL create_product_matrix(phi_panC, G_vir_sub, 'N', 'N', tmpA)
4023 810 : CALL dbcsr_multiply('N', 'N', 1.0_dp, phi_panC, G_vir_sub, 0.0_dp, tmpA, filter_eps=eps)
4024 810 : IF (use_cutoff) THEN
4025 : CALL build_geo_template_panel(phi_pan, phi_near, centroids, cutoff, blk0, A_vir, &
4026 40 : col_map=gmap)
4027 : ELSE
4028 770 : CALL create_product_matrix(tmpA, RB_phi, 'N', 'T', A_vir)
4029 : END IF
4030 : CALL dbcsr_multiply('N', 'T', 1.0_dp, tmpA, RB_phi, 0.0_dp, A_vir, &
4031 810 : filter_eps=eps, retain_sparsity=use_cutoff)
4032 810 : CALL dbcsr_release(tmpA)
4033 810 : CALL dbcsr_release(G_vir_sub)
4034 :
4035 : ! W_pan = Z_pan × W_aux × Z^T (computed once, reused for both Σ^c terms).
4036 : ! With keep_sparsity, W_pan is pre-seeded with the union of A_occ and A_vir block
4037 : ! patterns so that retain_sparsity forces the multiply to fill only those blocks:
4038 : ! exact since W outside G_occ∪G_vir is multiplied by zero in the Hadamard.
4039 810 : CALL create_product_matrix(Z_panC, W_sub, 'N', 'N', tmpB)
4040 810 : CALL dbcsr_multiply('N', 'N', 1.0_dp, Z_panC, W_sub, 0.0_dp, tmpB, filter_eps=eps)
4041 810 : IF (my_keep_sparsity) THEN
4042 810 : CALL dbcsr_create(W_pan, template=A_occ)
4043 810 : CALL dbcsr_copy(W_pan, A_occ)
4044 810 : CALL dbcsr_add(W_pan, A_vir, 1.0_dp, 1.0_dp)
4045 810 : CALL dbcsr_set(W_pan, 0.0_dp)
4046 : CALL dbcsr_multiply('N', 'T', 1.0_dp, tmpB, RB_Z, 0.0_dp, W_pan, &
4047 810 : filter_eps=eps, retain_sparsity=.TRUE.)
4048 : ELSE
4049 0 : CALL create_product_matrix(tmpB, RB_Z, 'N', 'T', W_pan)
4050 0 : CALL dbcsr_multiply('N', 'T', 1.0_dp, tmpB, RB_Z, 0.0_dp, W_pan, filter_eps=eps)
4051 : END IF
4052 810 : CALL dbcsr_release(tmpB)
4053 810 : CALL dbcsr_release(W_sub)
4054 810 : CALL dbcsr_release(phi_panC)
4055 810 : CALL dbcsr_release(Z_panC)
4056 :
4057 : ! Σ^c_neg: φ^T ( A_occ ∘ W_pan ) φ
4058 810 : CALL dbcsr_create(C_pan, template=A_occ)
4059 810 : CALL hadamard_product(A_occ, W_pan, C_pan, 1.0_dp)
4060 810 : CALL dbcsr_release(A_occ)
4061 810 : CALL create_product_matrix(C_pan, RB_phi, 'N', 'N', tmp2)
4062 810 : CALL dbcsr_multiply('N', 'N', 1.0_dp, C_pan, RB_phi, 0.0_dp, tmp2, filter_eps=eps)
4063 810 : CALL dbcsr_release(C_pan)
4064 810 : CALL dbcsr_multiply('T', 'N', 1.0_dp, phi_pan, tmp2, 1.0_dp, mat_Sigma_neg, filter_eps=eps)
4065 810 : CALL dbcsr_release(tmp2)
4066 :
4067 : ! Σ^c_pos: φ^T ( A_vir ∘ W_pan ) φ — W_pan reused
4068 810 : CALL dbcsr_create(C_pan, template=A_vir)
4069 810 : CALL hadamard_product(A_vir, W_pan, C_pan, 1.0_dp)
4070 810 : CALL dbcsr_release(A_vir)
4071 810 : CALL create_product_matrix(C_pan, RB_phi, 'N', 'N', tmp2)
4072 810 : CALL dbcsr_multiply('N', 'N', 1.0_dp, C_pan, RB_phi, 0.0_dp, tmp2, filter_eps=eps)
4073 810 : CALL dbcsr_release(C_pan)
4074 810 : CALL dbcsr_multiply('T', 'N', 1.0_dp, phi_pan, tmp2, 1.0_dp, mat_Sigma_pos, filter_eps=eps)
4075 810 : CALL dbcsr_release(tmp2)
4076 :
4077 810 : CALL dbcsr_release(W_pan)
4078 810 : CALL dbcsr_release(Z_pan)
4079 810 : CALL dbcsr_release(phi_pan)
4080 6460 : IF (use_cutoff) THEN
4081 40 : CALL dbcsr_release(phi_near)
4082 40 : CALL dbcsr_release(Z_near)
4083 : END IF
4084 :
4085 : END DO
4086 :
4087 790 : CALL timestop(handle)
4088 :
4089 1580 : END SUBROUTINE contract_grid_panels_sigma_c
4090 :
4091 : ! **************************************************************************************************
4092 : !> \brief Computes C = A ◦ B (Element-wise Hadamard product) for sparse DBCSR matrices.
4093 : !> \param matrix_A ...
4094 : !> \param matrix_B ...
4095 : !> \param matrix_C ...
4096 : !> \param fac (Scaling factor applied to the product)
4097 : ! **************************************************************************************************
4098 4548 : SUBROUTINE hadamard_product(matrix_A, matrix_B, matrix_C, fac)
4099 :
4100 : TYPE(dbcsr_type), INTENT(INOUT) :: matrix_A, matrix_B, matrix_C
4101 : REAL(KIND=dp), INTENT(IN) :: fac
4102 :
4103 : CHARACTER(LEN=*), PARAMETER :: routineN = 'hadamard_product'
4104 :
4105 : INTEGER :: col, handle, row
4106 : LOGICAL :: found
4107 2274 : REAL(KIND=dp), DIMENSION(:, :), POINTER :: blk_B, blk_C
4108 : TYPE(dbcsr_iterator_type) :: iter
4109 :
4110 2274 : CALL timeset(routineN, handle)
4111 :
4112 2274 : CALL dbcsr_copy(matrix_C, matrix_A)
4113 :
4114 2274 : CALL dbcsr_iterator_start(iter, matrix_C)
4115 297836 : DO WHILE (dbcsr_iterator_blocks_left(iter))
4116 295562 : CALL dbcsr_iterator_next_block(iter, row, col, blk_C)
4117 :
4118 295562 : CALL dbcsr_get_block_p(matrix_B, row, col, blk_B, found)
4119 :
4120 297836 : IF (found) THEN
4121 527048742 : blk_C(:, :) = fac*blk_C(:, :)*blk_B(:, :)
4122 : ELSE
4123 : ! If B is sparse here, the product is zero
4124 0 : blk_C(:, :) = 0.0_dp
4125 : END IF
4126 : END DO
4127 2274 : CALL dbcsr_iterator_stop(iter)
4128 :
4129 2274 : CALL timestop(handle)
4130 :
4131 2274 : END SUBROUTINE hadamard_product
4132 :
4133 : ! **************************************************************************************************
4134 : !> \brief Computes the screened Coulomb interaction on the imaginary-time grid, entirely in the
4135 : !> RI auxiliary (PQ) basis:
4136 : !> χ_PQ(iω) = Σ_τ w(ω,τ) cos(ωτ) χ_PQ(iτ) (cosine transform)
4137 : !> ε(iω) = Id - V^0.5 M^-1 χ(iω) M^-1 V^0.5 (dielectric function)
4138 : !> W(iω) = V^0.5 ( ε^-1(iω) - Id ) V^0.5 (correlation part only)
4139 : !> W(iτ) = Σ_ω w̃(τ,ω) cos(ωτ) W(iω) (back transform)
4140 : !> W(iτ) <- M^-1 W(iτ) M^-1 (fold in the RI metric)
4141 : !> where V is the bare Coulomb matrix and M the RI metric.
4142 : !> \param bs_env ...
4143 : !> \param qs_env ...
4144 : !> \param mat_chi_Gamma_tau ...
4145 : !> \param fm_W_time ...
4146 : ! **************************************************************************************************
4147 144 : SUBROUTINE compute_W(bs_env, qs_env, mat_chi_Gamma_tau, fm_W_time)
4148 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
4149 : TYPE(qs_environment_type), POINTER :: qs_env
4150 : TYPE(dbcsr_p_type), DIMENSION(:), POINTER :: mat_chi_Gamma_tau
4151 : TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:) :: fm_W_time
4152 :
4153 : CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_W'
4154 :
4155 : INTEGER :: handle, i_t, j_w
4156 : REAL(KIND=dp) :: t1
4157 : TYPE(cp_fm_type) :: fm_M_inv_V_sqrt, fm_V, fm_V_sqrt
4158 :
4159 36 : CALL timeset(routineN, handle)
4160 :
4161 36 : t1 = m_walltime()
4162 :
4163 36 : CALL create_fm_W_MIC_time(bs_env, fm_W_time)
4164 :
4165 : ! 1. Allocate V and M matrices
4166 36 : CALL cp_fm_create(fm_V, bs_env%fm_RI_RI%matrix_struct)
4167 36 : CALL cp_fm_create(fm_V_sqrt, bs_env%fm_RI_RI%matrix_struct)
4168 36 : CALL cp_fm_create(fm_M_inv_V_sqrt, bs_env%fm_RI_RI%matrix_struct)
4169 :
4170 : ! Compute V and M^-1 * V^0.5
4171 36 : CALL compute_V_MinvVsqrt(bs_env, qs_env, fm_V, fm_V_sqrt, fm_M_inv_V_sqrt)
4172 :
4173 : ! 2. Loop over frequencies
4174 546 : DO j_w = 1, bs_env%num_time_freq_points
4175 : ! Fourier transformation of χ_PQ(iτ) to χ_PQ(iω_j)
4176 510 : CALL compute_fm_chi_Gamma_freq(bs_env, bs_env%fm_chi_Gamma_freq, j_w, mat_chi_Gamma_tau)
4177 :
4178 : ! ε(iω_j) = Id - V^0.5*M^-1*χ(iω_j)*M^-1*V^0.5
4179 : ! W(iω_j) = V^0.5*(ε^-1(iω_j)-Id)*V^0.5
4180 : CALL compute_fm_W_freq(bs_env, bs_env%fm_chi_Gamma_freq, fm_V_sqrt, &
4181 510 : fm_M_inv_V_sqrt, bs_env%fm_W_MIC_freq)
4182 :
4183 : ! Fourier transform from W_PQ^MIC(iω_j) to W_PQ^MIC(iτ)
4184 546 : CALL Fourier_transform_w_to_t(bs_env, fm_W_time, bs_env%fm_W_MIC_freq, j_w)
4185 : END DO
4186 :
4187 : ! M^-1*W^MIC(iτ)*M^-1
4188 36 : CALL multiply_fm_W_MIC_time_with_Minv_Gamma(bs_env, qs_env, fm_W_time)
4189 :
4190 36 : IF (bs_env%unit_nr > 0) THEN
4191 : WRITE (bs_env%unit_nr, '(T2,A,T58,A,F7.1,A)') &
4192 18 : 'Computed W(iτ),', ' Execution time', m_walltime() - t1, ' s'
4193 : END IF
4194 :
4195 36 : CALL dbcsr_deallocate_matrix_set(mat_chi_Gamma_tau)
4196 :
4197 : ! Cleanup
4198 36 : CALL cp_fm_release(fm_V)
4199 36 : CALL cp_fm_release(fm_V_sqrt)
4200 36 : CALL cp_fm_release(fm_M_inv_V_sqrt)
4201 :
4202 : ! Marek : Fourier transform W^MIC(itau) back to get it at a specific im.frequency point - iomega = 0
4203 36 : IF (bs_env%rtp_method == rtp_method_bse) THEN
4204 14 : t1 = m_walltime()
4205 14 : CALL cp_fm_create(bs_env%fm_W_MIC_freq_zero, bs_env%fm_W_MIC_freq%matrix_struct)
4206 : ! Set to zero
4207 14 : CALL cp_fm_set_all(bs_env%fm_W_MIC_freq_zero, 0.0_dp)
4208 : ! Sum over all times
4209 294 : DO i_t = 1, bs_env%num_time_freq_points
4210 : ! Add the relevant structure with correct weight
4211 : CALL cp_fm_scale_and_add(1.0_dp, bs_env%fm_W_MIC_freq_zero, &
4212 294 : bs_env%imag_time_weights_freq_zero(i_t), fm_W_time(i_t))
4213 : END DO
4214 : ! Done, save to file
4215 14 : CALL fm_write(bs_env%fm_W_MIC_freq_zero, 0, "W_freq_rtp", qs_env)
4216 : ! Report calculation
4217 14 : IF (bs_env%unit_nr > 0) THEN
4218 : WRITE (bs_env%unit_nr, '(T2,A,T57,A,F7.1,A)') &
4219 7 : 'Computed W(0),', ' Execution time', m_walltime() - t1, ' s'
4220 : END IF
4221 : END IF
4222 :
4223 36 : IF (bs_env%unit_nr > 0) WRITE (bs_env%unit_nr, '(A)') ' '
4224 :
4225 36 : CALL timestop(handle)
4226 :
4227 36 : END SUBROUTINE compute_W
4228 :
4229 : ! **************************************************************************************************
4230 : !> \brief Computes the static RI-basis Coulomb operators entering the dielectric function:
4231 : !> the bare Coulomb matrix V_PQ(k=0), its Cholesky/matrix square root V^0.5, and
4232 : !> M^-1 V^0.5 with M the RI metric (2c integrals of the RI_METRIC operator).
4233 : !> \param bs_env ...
4234 : !> \param qs_env ...
4235 : !> \param fm_V ...
4236 : !> \param fm_V_sqrt ...
4237 : !> \param fm_Minv_Vsqrt ...
4238 : ! **************************************************************************************************
4239 36 : SUBROUTINE compute_V_MinvVsqrt(bs_env, qs_env, fm_V, fm_V_sqrt, fm_Minv_Vsqrt)
4240 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
4241 : TYPE(qs_environment_type), POINTER :: qs_env
4242 : TYPE(cp_fm_type), INTENT(INOUT) :: fm_V, fm_V_sqrt, fm_Minv_Vsqrt
4243 :
4244 : CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_V_MinvVsqrt'
4245 :
4246 : INTEGER :: handle, info, n_RI, ndep
4247 36 : TYPE(atomic_kind_type), DIMENSION(:), POINTER :: atomic_kind_set
4248 : TYPE(cell_type), POINTER :: cell
4249 : TYPE(cp_fm_type) :: fm_work
4250 36 : TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:, :) :: fm_M
4251 36 : TYPE(dbcsr_p_type), DIMENSION(:, :), POINTER :: mat_V_kp
4252 36 : TYPE(particle_type), DIMENSION(:), POINTER :: particle_set
4253 36 : TYPE(qs_kind_type), DIMENSION(:), POINTER :: qs_kind_set
4254 :
4255 36 : CALL timeset(routineN, handle)
4256 :
4257 36 : n_RI = bs_env%n_RI
4258 36 : CALL cp_fm_create(fm_work, fm_V%matrix_struct)
4259 :
4260 : ! -----------------------------------------------------------------------
4261 : ! 1. Build Coulomb Matrix V(k=0) using the kp-routine but only for ikp=1
4262 : ! -----------------------------------------------------------------------
4263 : CALL get_qs_env(qs_env=qs_env, particle_set=particle_set, cell=cell, &
4264 36 : qs_kind_set=qs_kind_set, atomic_kind_set=atomic_kind_set)
4265 :
4266 180 : ALLOCATE (mat_V_kp(1:1, 1:2))
4267 36 : NULLIFY (mat_V_kp(1, 1)%matrix, mat_V_kp(1, 2)%matrix)
4268 36 : ALLOCATE (mat_V_kp(1, 1)%matrix, mat_V_kp(1, 2)%matrix)
4269 :
4270 36 : CALL dbcsr_create(mat_V_kp(1, 1)%matrix, template=bs_env%mat_RI_RI%matrix)
4271 36 : CALL dbcsr_reserve_all_blocks(mat_V_kp(1, 1)%matrix)
4272 36 : CALL dbcsr_set(mat_V_kp(1, 1)%matrix, 0.0_dp)
4273 :
4274 : ! Dummy imaginary part just to satisfy the routine
4275 36 : CALL dbcsr_create(mat_V_kp(1, 2)%matrix, template=bs_env%mat_RI_RI%matrix)
4276 36 : CALL dbcsr_reserve_all_blocks(mat_V_kp(1, 2)%matrix)
4277 36 : CALL dbcsr_set(mat_V_kp(1, 2)%matrix, 0.0_dp)
4278 :
4279 144 : bs_env%kpoints_chi_eps_W%nkp_grid = bs_env%nkp_grid_chi_eps_W_orig
4280 :
4281 : CALL build_2c_coulomb_matrix_kp(mat_V_kp, bs_env%kpoints_chi_eps_W, "RI_AUX", cell, &
4282 : particle_set, qs_kind_set, atomic_kind_set, &
4283 36 : bs_env%size_lattice_sum_V, operator_coulomb, 1, 1)
4284 :
4285 : ! Copy real part to fm_V
4286 36 : CALL copy_dbcsr_to_fm(mat_V_kp(1, 1)%matrix, fm_V)
4287 :
4288 36 : CALL dbcsr_deallocate_matrix(mat_V_kp(1, 1)%matrix)
4289 36 : CALL dbcsr_deallocate_matrix(mat_V_kp(1, 2)%matrix)
4290 36 : DEALLOCATE (mat_V_kp)
4291 :
4292 : ! -----------------------------------------------------------------------
4293 : ! 2. Get RI-Metric Matrix M(k=0)
4294 : ! -----------------------------------------------------------------------
4295 : CALL RI_2c_integral_mat(qs_env, fm_M, fm_V, n_RI, bs_env%ri_metric, &
4296 36 : do_kpoints=.FALSE., regularization_RI=bs_env%regularization_RI)
4297 :
4298 : ! -----------------------------------------------------------------------
4299 : ! 3. M -> M^-1
4300 : ! -----------------------------------------------------------------------
4301 36 : CALL cp_fm_cholesky_decompose(fm_M(1, 1), info_out=info)
4302 36 : IF (info == 0) THEN
4303 36 : CALL cp_fm_cholesky_invert(fm_M(1, 1))
4304 36 : CALL cp_fm_uplo_to_full(fm_M(1, 1), fm_work)
4305 : ELSE
4306 : ! Fallback if Cholesky fails due to conditioning
4307 0 : CALL cp_fm_power(fm_M(1, 1), fm_work, -1.0_dp, bs_env%eps_eigval_mat_RI, ndep)
4308 0 : CALL cp_fm_to_fm(fm_work, fm_M(1, 1))
4309 : END IF
4310 :
4311 : ! -----------------------------------------------------------------------
4312 : ! 4. V -> V^0.5
4313 : ! -----------------------------------------------------------------------
4314 36 : CALL cp_fm_to_fm(fm_V, fm_V_sqrt)
4315 36 : CALL cp_fm_cholesky_decompose(fm_V_sqrt, info_out=info)
4316 36 : IF (info == 0) THEN
4317 36 : CALL clean_lower_part(fm_V_sqrt)
4318 : ELSE
4319 0 : CALL cp_fm_power(fm_V, fm_V_sqrt, 0.5_dp, bs_env%eps_eigval_mat_RI, ndep)
4320 : END IF
4321 :
4322 : ! -----------------------------------------------------------------------
4323 : ! 5. M^-1 * V^0.5
4324 : ! -----------------------------------------------------------------------
4325 : CALL parallel_gemm("N", "T", n_RI, n_RI, n_RI, 1.0_dp, fm_M(1, 1), fm_V_sqrt, &
4326 36 : 0.0_dp, fm_Minv_Vsqrt)
4327 :
4328 36 : CALL cp_fm_release(fm_M)
4329 36 : CALL cp_fm_release(fm_work)
4330 :
4331 36 : CALL timestop(handle)
4332 :
4333 144 : END SUBROUTINE compute_V_MinvVsqrt
4334 :
4335 : ! **************************************************************************************************
4336 : !> \brief Computes the screened interaction at one imaginary frequency:
4337 : !> ε(iω_j) = Id - (M^-1 V^0.5)^T χ(iω_j) (M^-1 V^0.5)
4338 : !> W(iω_j) = V^0.5^T ( ε^-1(iω_j) - Id ) V^0.5
4339 : !> ε is inverted via Cholesky; if that fails due to conditioning, via
4340 : !> eigendecomposition (cp_fm_power) with eigenvalue filtering.
4341 : !> \param bs_env ...
4342 : !> \param fm_chi_freq_j ...
4343 : !> \param fm_V_sqrt ...
4344 : !> \param fm_Minv_Vsqrt ...
4345 : !> \param fm_W_freq_j ...
4346 : ! **************************************************************************************************
4347 2040 : SUBROUTINE compute_fm_W_freq(bs_env, fm_chi_freq_j, fm_V_sqrt, fm_Minv_Vsqrt, fm_W_freq_j)
4348 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
4349 : TYPE(cp_fm_type), INTENT(IN) :: fm_chi_freq_j, fm_V_sqrt, fm_Minv_Vsqrt
4350 : TYPE(cp_fm_type), INTENT(INOUT) :: fm_W_freq_j
4351 :
4352 : CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_fm_W_freq'
4353 :
4354 : INTEGER :: handle, info, n_RI, ndep
4355 : TYPE(cp_fm_type) :: fm_eps_freq_j, fm_work
4356 :
4357 510 : CALL timeset(routineN, handle)
4358 :
4359 510 : n_RI = bs_env%n_RI
4360 :
4361 510 : CALL cp_fm_create(fm_eps_freq_j, fm_chi_freq_j%matrix_struct)
4362 510 : CALL cp_fm_create(fm_work, fm_chi_freq_j%matrix_struct)
4363 :
4364 : ! -----------------------------------------------------------------------
4365 : ! 1. ε(iω_j) = Id - (M^-1 * V^0.5)^T * χ(iω_j) * (M^-1 * V^0.5)
4366 : ! -----------------------------------------------------------------------
4367 : ! work = χ(iω_j) * (M^-1 * V^0.5)
4368 : CALL parallel_gemm('N', 'N', n_RI, n_RI, n_RI, 1.0_dp, &
4369 510 : fm_chi_freq_j, fm_Minv_Vsqrt, 0.0_dp, fm_work)
4370 :
4371 : ! eps_work = (M^-1 * V^0.5)^T * work
4372 : CALL parallel_gemm('T', 'N', n_RI, n_RI, n_RI, 1.0_dp, &
4373 510 : fm_Minv_Vsqrt, fm_work, 0.0_dp, fm_eps_freq_j)
4374 :
4375 : ! ε(iω_j) = Id - eps_work --> -eps_work + Id
4376 510 : CALL fm_add_on_diag(fm_eps_freq_j, 1.0_dp)
4377 :
4378 : ! Force perfect symmetry before Cholesky to avoid info != 0 due to GEMM noise
4379 510 : CALL cp_fm_uplo_to_full(fm_eps_freq_j, fm_work)
4380 :
4381 : ! -----------------------------------------------------------------------
4382 : ! 2. W(iω_j) = V^0.5^T * (ε^-1(iω_j) - Id) * V^0.5
4383 : ! -----------------------------------------------------------------------
4384 :
4385 : ! a) Cholesky decomposition of ε(iω_j)
4386 510 : CALL cp_fm_cholesky_decompose(fm_eps_freq_j, info_out=info)
4387 :
4388 : ! b) Inversion
4389 510 : IF (info == 0) THEN
4390 510 : CALL cp_fm_cholesky_invert(fm_eps_freq_j)
4391 510 : CALL cp_fm_uplo_to_full(fm_eps_freq_j, fm_work)
4392 : ELSE
4393 : ! Fallback to expensive diagonalization if Cholesky fails
4394 0 : CALL cp_fm_power(fm_eps_freq_j, fm_work, -1.0_dp, bs_env%eps_eigval_mat_RI, ndep)
4395 0 : CALL cp_fm_to_fm(fm_work, fm_eps_freq_j)
4396 : END IF
4397 :
4398 : ! c) ε^-1(iω_j) - Id
4399 510 : CALL fm_add_on_diag(fm_eps_freq_j, -1.0_dp)
4400 :
4401 : ! d) work = (ε^-1(iω_j) - Id) * V^0.5
4402 : CALL parallel_gemm('N', 'N', n_RI, n_RI, n_RI, 1.0_dp, fm_eps_freq_j, fm_V_sqrt, &
4403 510 : 0.0_dp, fm_work)
4404 :
4405 : ! e) W(iw) = V^0.5^T * work
4406 : CALL parallel_gemm('T', 'N', n_RI, n_RI, n_RI, 1.0_dp, fm_V_sqrt, fm_work, &
4407 510 : 0.0_dp, fm_W_freq_j)
4408 :
4409 : ! Cleanup
4410 510 : CALL cp_fm_release(fm_work)
4411 510 : CALL cp_fm_release(fm_eps_freq_j)
4412 :
4413 510 : CALL timestop(handle)
4414 :
4415 510 : END SUBROUTINE compute_fm_W_freq
4416 :
4417 : ! **************************************************************************************************
4418 : !> \brief Adds a real scalar value to the diagonal of a real full matrix
4419 : !> \param fm ...
4420 : !> \param alpha ...
4421 : ! **************************************************************************************************
4422 2040 : SUBROUTINE fm_add_on_diag(fm, alpha)
4423 : TYPE(cp_fm_type), INTENT(INOUT) :: fm
4424 : REAL(KIND=dp), INTENT(IN) :: alpha
4425 :
4426 : CHARACTER(LEN=*), PARAMETER :: routineN = 'fm_add_on_diag'
4427 :
4428 : INTEGER :: handle, i_global, i_row, j_col, &
4429 : j_global, ncol_local, nrow_local
4430 1020 : INTEGER, DIMENSION(:), POINTER :: col_indices, row_indices
4431 :
4432 1020 : CALL timeset(routineN, handle)
4433 :
4434 : CALL cp_fm_get_info(matrix=fm, &
4435 : nrow_local=nrow_local, &
4436 : ncol_local=ncol_local, &
4437 : row_indices=row_indices, &
4438 1020 : col_indices=col_indices)
4439 :
4440 36520 : DO j_col = 1, ncol_local
4441 35500 : j_global = col_indices(j_col)
4442 732150 : DO i_row = 1, nrow_local
4443 695630 : i_global = row_indices(i_row)
4444 731130 : IF (j_global == i_global) THEN
4445 17750 : fm%local_data(i_row, j_col) = fm%local_data(i_row, j_col) + alpha
4446 : END IF
4447 : END DO
4448 : END DO
4449 :
4450 1020 : CALL timestop(handle)
4451 :
4452 1020 : END SUBROUTINE fm_add_on_diag
4453 :
4454 : ! **************************************************************************************************
4455 : !> \brief Zeroes out the strictly lower triangular part of a real matrix
4456 : !> \param fm_mat ...
4457 : ! **************************************************************************************************
4458 72 : SUBROUTINE clean_lower_part(fm_mat)
4459 : TYPE(cp_fm_type) :: fm_mat
4460 :
4461 : CHARACTER(LEN=*), PARAMETER :: routineN = 'clean_lower_part'
4462 :
4463 : INTEGER :: handle, i_row, j_col, j_global, &
4464 : ncol_local, nrow_local
4465 36 : INTEGER, DIMENSION(:), POINTER :: col_indices, row_indices
4466 :
4467 36 : CALL timeset(routineN, handle)
4468 :
4469 : CALL cp_fm_get_info(matrix=fm_mat, &
4470 : nrow_local=nrow_local, ncol_local=ncol_local, &
4471 36 : row_indices=row_indices, col_indices=col_indices)
4472 :
4473 1146 : DO j_col = 1, ncol_local
4474 1110 : j_global = col_indices(j_col)
4475 20895 : DO i_row = 1, nrow_local
4476 20859 : IF (j_global < row_indices(i_row)) fm_mat%local_data(i_row, j_col) = 0.0_dp
4477 : END DO
4478 : END DO
4479 :
4480 36 : CALL timestop(handle)
4481 :
4482 36 : END SUBROUTINE clean_lower_part
4483 :
4484 : ! **************************************************************************************************
4485 : !> \brief Computes the exact-exchange part of the GW self-energy:
4486 : !> D_μν = Σ_n^occ C_μn C_νn (density matrix = G^occ at τ=0)
4487 : !> V^tr_PQ = M^-1 (P|Q)_trunc M^-1 (truncated Coulomb, RI basis)
4488 : !> Σ^x_λσ(k=0) = -Σ_ll' Φ_λ(r_l) [ (φ D φ^T)_ll' ∘ (Z V^tr Z^T)_ll' ] Φ_σ(r_l')
4489 : !> \param bs_env ...
4490 : !> \param qs_env ...
4491 : !> \param mat_phi_mu_l ...
4492 : !> \param mat_Z_lP ...
4493 : !> \param fm_Sigma_x_Gamma ...
4494 : ! **************************************************************************************************
4495 36 : SUBROUTINE compute_Sigma_x(bs_env, qs_env, mat_phi_mu_l, mat_Z_lP, fm_Sigma_x_Gamma)
4496 :
4497 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
4498 : TYPE(qs_environment_type), POINTER :: qs_env
4499 : TYPE(dbcsr_type), INTENT(INOUT) :: mat_phi_mu_l, mat_Z_lP
4500 : TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:) :: fm_Sigma_x_Gamma
4501 :
4502 : CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_Sigma_x'
4503 :
4504 : INTEGER :: handle, ispin
4505 36 : INTEGER, ALLOCATABLE, DIMENSION(:) :: pan_first, pan_last
4506 36 : INTEGER, DIMENSION(:), POINTER :: blk_aux, dist_row_aux
4507 : REAL(KIND=dp) :: t1
4508 36 : TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:, :) :: fm_Vtr_Gamma
4509 : TYPE(dbcsr_distribution_type) :: dist_aux_aux
4510 : TYPE(dbcsr_type) :: mat_Sigma_x_Gamma, matrix_D_ao, &
4511 : matrix_V_aux
4512 :
4513 36 : CALL timeset(routineN, handle)
4514 :
4515 36 : t1 = m_walltime()
4516 :
4517 150 : ALLOCATE (fm_Sigma_x_Gamma(bs_env%n_spin))
4518 78 : DO ispin = 1, bs_env%n_spin
4519 78 : CALL cp_fm_create(fm_Sigma_x_Gamma(ispin), bs_env%fm_s_Gamma%matrix_struct)
4520 : END DO
4521 :
4522 36 : CALL dbcsr_create(mat_Sigma_x_Gamma, template=bs_env%mat_ao_ao%matrix)
4523 :
4524 36 : CALL resolve_grid_panels(bs_env, mat_phi_mu_l, pan_first, pan_last)
4525 :
4526 : ! =========================================================================
4527 : ! 1. COMPUTE V^tr_PQ (RI x RI)
4528 : ! =========================================================================
4529 36 : CALL setup_square_topology(mat_Z_lP, dist_aux_aux, blk_aux, dist_row_aux)
4530 :
4531 : CALL RI_2c_integral_mat(qs_env, fm_Vtr_Gamma, bs_env%fm_RI_RI, bs_env%n_RI, &
4532 36 : bs_env%trunc_coulomb, do_kpoints=.FALSE.)
4533 :
4534 : ! M^-1 * V^tr * M^-1 directly modifies fm_Vtr_Gamma(:, 1)
4535 36 : CALL multiply_fm_W_MIC_time_with_Minv_Gamma(bs_env, qs_env, fm_Vtr_Gamma(:, 1))
4536 :
4537 36 : CALL dbcsr_create(matrix_V_aux, "V_aux", dist_aux_aux, dbcsr_type_no_symmetry, blk_aux, blk_aux)
4538 : ! Optional CUTOFF_RADIUS_G_W operator truncation + filter (see build_G_ao)
4539 36 : IF (bs_env%ri_rs%cutoff_radius_g_w > 0.0_dp .AND. &
4540 : ALLOCATED(bs_env%ri_rs%atom_centers)) THEN
4541 : CALL reserve_blocks_within_radius(matrix_V_aux, bs_env%ri_rs%atom_centers, &
4542 0 : bs_env%ri_rs%cutoff_radius_g_w)
4543 0 : CALL copy_fm_to_dbcsr(fm_Vtr_Gamma(1, 1), matrix_V_aux, keep_sparsity=.TRUE.)
4544 : ELSE
4545 36 : CALL copy_fm_to_dbcsr(fm_Vtr_Gamma(1, 1), matrix_V_aux, keep_sparsity=.FALSE.)
4546 : END IF
4547 36 : CALL dbcsr_filter(matrix_V_aux, bs_env%eps_filter)
4548 :
4549 : ! =========================================================================
4550 : ! 2. SPIN LOOP FOR EXACT EXCHANGE
4551 : ! Σ^x_λσ = -Σ_ll' Φ_λ(r_l) ( D_ll' V^tr_ll' ) Φ_σ(r_l')
4552 : ! = -φ^T ( (φ D φ^T) ∘ (Z V^tr Z^T) ) φ
4553 : ! =========================================================================
4554 78 : DO ispin = 1, bs_env%n_spin
4555 :
4556 : ! AO-space density matrix D_µν = G^occ at τ = 0
4557 42 : CALL build_G_ao(bs_env, 0.0_dp, ispin, .TRUE., .FALSE., mat_phi_mu_l, matrix_D_ao)
4558 :
4559 : CALL contract_grid_panels(L_A=mat_phi_mu_l, M_A=matrix_D_ao, &
4560 : L_B=mat_Z_lP, M_B=matrix_V_aux, &
4561 : L_out=mat_phi_mu_l, mat_out=mat_Sigma_x_Gamma, &
4562 : scale=1.0_dp, eps=bs_env%eps_filter, &
4563 : para_env=bs_env%para_env, &
4564 : pan_first=pan_first, pan_last=pan_last, &
4565 : lb_eq_la=.FALSE., lout_eq_la=.TRUE., zero_out=.TRUE., &
4566 : keep_sparsity=bs_env%ri_rs%keep_sparsity_rirs, &
4567 : centroids=bs_env%ri_rs%chunk_centroids, &
4568 42 : cutoff=bs_env%ri_rs%cutoff_radius_v_w)
4569 42 : CALL dbcsr_scale(mat_Sigma_x_Gamma, -1.0_dp)
4570 :
4571 42 : CALL dbcsr_release(matrix_D_ao)
4572 :
4573 : ! Data I/O and Export to CP2K Full Matrices
4574 78 : CALL copy_dbcsr_to_fm(mat_Sigma_x_Gamma, fm_Sigma_x_Gamma(ispin))
4575 :
4576 : END DO ! ispin
4577 :
4578 36 : IF (bs_env%unit_nr > 0) THEN
4579 : WRITE (bs_env%unit_nr, '(T2,A,T58,A,F7.1,A)') &
4580 18 : 'Computed Σ^x(k=0),', ' Execution time', m_walltime() - t1, ' s'
4581 18 : WRITE (bs_env%unit_nr, '(A)') ' '
4582 : END IF
4583 :
4584 : ! =========================================================================
4585 : ! 3. CLEANUP
4586 : ! =========================================================================
4587 36 : CALL dbcsr_release(matrix_V_aux)
4588 36 : CALL dbcsr_release(mat_Sigma_x_Gamma)
4589 36 : CALL release_square_topology(dist=dist_aux_aux, mapped_dist=dist_row_aux)
4590 :
4591 36 : CALL cp_fm_release(fm_Vtr_Gamma)
4592 :
4593 36 : CALL timestop(handle)
4594 :
4595 108 : END SUBROUTINE compute_Sigma_x
4596 :
4597 : ! **************************************************************************************************
4598 : !> \brief Computes the correlation part of the GW self-energy on the imaginary-time grid:
4599 : !> Σ^c_λσ(iτ<0) = -Σ_ll' Φ_λ(r_l) [ (φ G^occ φ^T)_ll' ∘ (Z W^MIC Z^T)_ll' ] Φ_σ(r_l')
4600 : !> Σ^c_λσ(iτ>0) = +Σ_ll' Φ_λ(r_l) [ (φ G^vir φ^T)_ll' ∘ (Z W^MIC Z^T)_ll' ] Φ_σ(r_l')
4601 : !> \param bs_env ...
4602 : !> \param fm_W_time ...
4603 : !> \param mat_phi_mu_l ...
4604 : !> \param mat_Z_lP ...
4605 : !> \param fm_Sigma_c_Gamma_time ...
4606 : ! **************************************************************************************************
4607 50 : SUBROUTINE compute_Sigma_c(bs_env, fm_W_time, mat_phi_mu_l, mat_Z_lP, fm_Sigma_c_Gamma_time)
4608 :
4609 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
4610 : TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:) :: fm_W_time
4611 : TYPE(dbcsr_type), INTENT(INOUT) :: mat_phi_mu_l, mat_Z_lP
4612 : TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:, :, :) :: fm_Sigma_c_Gamma_time
4613 :
4614 : CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_Sigma_c'
4615 :
4616 : INTEGER :: handle, i_t, ispin
4617 50 : INTEGER, ALLOCATABLE, DIMENSION(:) :: pan_first, pan_last
4618 50 : INTEGER, DIMENSION(:), POINTER :: blk_aux, dist_row_aux
4619 : REAL(KIND=dp) :: t1, tau
4620 : TYPE(dbcsr_distribution_type) :: dist_aux_aux
4621 50 : TYPE(dbcsr_p_type), DIMENSION(:, :), POINTER :: mat_Sigma_neg_tau, mat_Sigma_pos_tau
4622 : TYPE(dbcsr_type) :: matrix_G_occ_ao, matrix_G_vir_ao, &
4623 : matrix_W_aux
4624 :
4625 50 : CALL timeset(routineN, handle)
4626 :
4627 : ! =========================================================================
4628 : ! 1. SETUP AUXILIARY TOPOLOGY AND PRE-ALLOCATE OUTPUT ARRAYS
4629 : ! =========================================================================
4630 50 : CALL setup_square_topology(mat_Z_lP, dist_aux_aux, blk_aux, dist_row_aux)
4631 :
4632 50 : CALL resolve_grid_panels(bs_env, mat_phi_mu_l, pan_first, pan_last)
4633 :
4634 : ! Pre-allocate local DBCSR matrices to act as targets for final output
4635 50 : NULLIFY (mat_Sigma_neg_tau, mat_Sigma_pos_tau)
4636 1052 : ALLOCATE (mat_Sigma_neg_tau(bs_env%num_time_freq_points, bs_env%n_spin))
4637 1052 : ALLOCATE (mat_Sigma_pos_tau(bs_env%num_time_freq_points, bs_env%n_spin))
4638 :
4639 700 : DO i_t = 1, bs_env%num_time_freq_points
4640 1490 : DO ispin = 1, bs_env%n_spin
4641 790 : ALLOCATE (mat_Sigma_neg_tau(i_t, ispin)%matrix)
4642 790 : ALLOCATE (mat_Sigma_pos_tau(i_t, ispin)%matrix)
4643 790 : CALL dbcsr_create(mat_Sigma_neg_tau(i_t, ispin)%matrix, template=bs_env%mat_ao_ao%matrix)
4644 1440 : CALL dbcsr_create(mat_Sigma_pos_tau(i_t, ispin)%matrix, template=bs_env%mat_ao_ao%matrix)
4645 : END DO
4646 : END DO
4647 :
4648 : ! =========================================================================
4649 : ! 2. IMAGINARY TIME LOOP
4650 : ! Σ^c_neg_λσ(iτ) = -φ^T ( (φ G^occ φ^T) ∘ (Z W^MIC Z^T) ) φ
4651 : ! Σ^c_pos_λσ(iτ) = φ^T ( (φ G^vir φ^T) ∘ (Z W^MIC Z^T) ) φ
4652 : ! =========================================================================
4653 700 : DO i_t = 1, bs_env%num_time_freq_points
4654 650 : tau = bs_env%imag_time_points(i_t)
4655 :
4656 : CALL dbcsr_create(matrix_W_aux, "W_aux", dist_aux_aux, dbcsr_type_no_symmetry, &
4657 650 : blk_aux, blk_aux)
4658 650 : IF (bs_env%ri_rs%cutoff_radius_g_w > 0.0_dp .AND. &
4659 : ALLOCATED(bs_env%ri_rs%atom_centers)) THEN
4660 : CALL reserve_blocks_within_radius(matrix_W_aux, bs_env%ri_rs%atom_centers, &
4661 0 : bs_env%ri_rs%cutoff_radius_g_w)
4662 0 : CALL copy_fm_to_dbcsr(fm_W_time(i_t), matrix_W_aux, keep_sparsity=.TRUE.)
4663 : ELSE
4664 650 : CALL copy_fm_to_dbcsr(fm_W_time(i_t), matrix_W_aux, keep_sparsity=.FALSE.)
4665 : END IF
4666 650 : CALL dbcsr_filter(matrix_W_aux, bs_env%eps_filter)
4667 :
4668 1440 : DO ispin = 1, bs_env%n_spin
4669 790 : t1 = m_walltime()
4670 :
4671 : ! AO-space Green's functions G^occ_µν, G^vir_µν (dense AO x AO, small)
4672 790 : CALL build_G_ao(bs_env, tau, ispin, .TRUE., .FALSE., mat_phi_mu_l, matrix_G_occ_ao)
4673 790 : CALL build_G_ao(bs_env, tau, ispin, .FALSE., .TRUE., mat_phi_mu_l, matrix_G_vir_ao)
4674 :
4675 : ! Σ^c_neg and Σ^c_pos in a single panel loop: W_pan = Z_panel × W × Z^T built once
4676 : CALL contract_grid_panels_sigma_c(mat_phi=mat_phi_mu_l, mat_Z=mat_Z_lP, &
4677 : mat_G_occ_ao=matrix_G_occ_ao, &
4678 : mat_G_vir_ao=matrix_G_vir_ao, &
4679 : mat_W_aux=matrix_W_aux, &
4680 : mat_Sigma_neg=mat_Sigma_neg_tau(i_t, ispin)%matrix, &
4681 : mat_Sigma_pos=mat_Sigma_pos_tau(i_t, ispin)%matrix, &
4682 : eps=bs_env%eps_filter, &
4683 : para_env=bs_env%para_env, &
4684 : pan_first=pan_first, pan_last=pan_last, &
4685 : keep_sparsity=bs_env%ri_rs%keep_sparsity_rirs, &
4686 : centroids=bs_env%ri_rs%chunk_centroids, &
4687 790 : cutoff=bs_env%ri_rs%cutoff_radius_v_w)
4688 790 : CALL dbcsr_scale(mat_Sigma_neg_tau(i_t, ispin)%matrix, -1.0_dp)
4689 :
4690 790 : CALL dbcsr_release(matrix_G_occ_ao)
4691 790 : CALL dbcsr_release(matrix_G_vir_ao)
4692 :
4693 1440 : IF (bs_env%unit_nr > 0) THEN
4694 : WRITE (bs_env%unit_nr, '(T2,A,I15,A,I3,A,F7.1,A)') &
4695 395 : 'Computed Σ^c(iτ) for time point', i_t, ' /', bs_env%num_time_freq_points, &
4696 790 : ', Execution time', m_walltime() - t1, ' s'
4697 : END IF
4698 :
4699 : END DO ! ispin
4700 :
4701 700 : CALL dbcsr_release(matrix_W_aux)
4702 :
4703 : END DO ! i_t
4704 :
4705 50 : IF (bs_env%unit_nr > 0) WRITE (bs_env%unit_nr, '(A)') ' '
4706 :
4707 : ! -------------------------------------------------------------------------
4708 : ! 3. FINALIZE AND CLEANUP
4709 : ! -------------------------------------------------------------------------
4710 : CALL fill_fm_Sigma_c_Gamma_time(fm_Sigma_c_Gamma_time, bs_env, &
4711 50 : mat_Sigma_pos_tau, mat_Sigma_neg_tau)
4712 :
4713 : ! fm_W_time and the scratch files are released by the caller: in an evGW0 cycle this
4714 : ! routine is entered once per iteration and both have to survive until it is done.
4715 50 : CALL dbcsr_deallocate_matrix_set(mat_Sigma_neg_tau)
4716 50 : CALL dbcsr_deallocate_matrix_set(mat_Sigma_pos_tau)
4717 :
4718 50 : CALL release_square_topology(dist=dist_aux_aux, mapped_dist=dist_row_aux)
4719 :
4720 50 : CALL timestop(handle)
4721 :
4722 100 : END SUBROUTINE compute_Sigma_c
4723 :
4724 : ! **************************************************************************************************
4725 : !> \brief Builds the DBCSR distribution.
4726 : !> \param matrix_template ...
4727 : !> \param square_dist ...
4728 : !> \param blk_sizes ...
4729 : !> \param mapped_dist ...
4730 : ! **************************************************************************************************
4731 2888 : SUBROUTINE setup_square_topology(matrix_template, square_dist, blk_sizes, mapped_dist)
4732 :
4733 : TYPE(dbcsr_type), INTENT(IN) :: matrix_template
4734 : TYPE(dbcsr_distribution_type), INTENT(OUT) :: square_dist
4735 : INTEGER, DIMENSION(:), INTENT(OUT), POINTER :: blk_sizes, mapped_dist
4736 :
4737 : CHARACTER(LEN=*), PARAMETER :: routineN = 'setup_square_topology'
4738 :
4739 : INTEGER :: handle, i, nprows
4740 2888 : INTEGER, DIMENSION(:), POINTER :: col_blk, col_dist
4741 : TYPE(dbcsr_distribution_type) :: dist_template
4742 :
4743 2888 : CALL timeset(routineN, handle)
4744 :
4745 2888 : CALL dbcsr_get_info(matrix_template, distribution=dist_template, col_blk_size=col_blk)
4746 2888 : CALL dbcsr_distribution_get(dist_template, col_dist=col_dist, nprows=nprows)
4747 :
4748 2888 : blk_sizes => col_blk
4749 8664 : ALLOCATE (mapped_dist(SIZE(blk_sizes)))
4750 9646 : DO i = 1, SIZE(blk_sizes)
4751 9646 : mapped_dist(i) = MOD(i - 1, nprows)
4752 : END DO
4753 : CALL dbcsr_distribution_new(square_dist, template=dist_template, &
4754 2888 : row_dist=mapped_dist, col_dist=col_dist)
4755 :
4756 2888 : CALL timestop(handle)
4757 :
4758 5776 : END SUBROUTINE setup_square_topology
4759 :
4760 : ! **************************************************************************************************
4761 : !> \brief Releases a distribution created by setup_square_topology.
4762 : !> \param dist ...
4763 : !> \param mapped_dist ...
4764 : ! **************************************************************************************************
4765 2888 : SUBROUTINE release_square_topology(dist, mapped_dist)
4766 :
4767 : TYPE(dbcsr_distribution_type), INTENT(INOUT) :: dist
4768 : INTEGER, DIMENSION(:), INTENT(INOUT), POINTER :: mapped_dist
4769 :
4770 2888 : CALL dbcsr_distribution_release(dist)
4771 2888 : IF (ASSOCIATED(mapped_dist)) THEN
4772 2888 : DEALLOCATE (mapped_dist)
4773 : NULLIFY (mapped_dist)
4774 : END IF
4775 :
4776 2888 : END SUBROUTINE release_square_topology
4777 :
4778 : ! **************************************************************************************************
4779 : !> \brief Σ^c_λσ(iτ) -> Σ^c_nn(ϵ) and the quasi-particle levels of the non-periodic RI-RS path,
4780 : !> ϵ_n^GW = ϵ_n^DFT + Σ^c_nn(ϵ_n^GW) + Σ^x_nn - v^xc_nn.
4781 : !> \param bs_env ...
4782 : !> \param fm_Sigma_x_Gamma ...
4783 : !> \param fm_Sigma_c_Gamma_time ...
4784 : ! **************************************************************************************************
4785 50 : SUBROUTINE compute_QP_energies(bs_env, fm_Sigma_x_Gamma, fm_Sigma_c_Gamma_time)
4786 :
4787 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
4788 : TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:) :: fm_Sigma_x_Gamma
4789 : TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:, :, :) :: fm_Sigma_c_Gamma_time
4790 :
4791 : CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_QP_energies'
4792 :
4793 : INTEGER :: handle, ispin, j_t
4794 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:) :: Sigma_x_n, V_xc_n
4795 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :) :: Sigma_c_n_freq, Sigma_c_n_time
4796 : TYPE(cp_fm_type) :: fm_ks, fm_mos, fm_s, fm_work
4797 :
4798 50 : CALL timeset(routineN, handle)
4799 :
4800 50 : CALL cp_fm_create(fm_ks, bs_env%fm_s_Gamma%matrix_struct)
4801 50 : CALL cp_fm_create(fm_s, bs_env%fm_s_Gamma%matrix_struct)
4802 50 : CALL cp_fm_create(fm_mos, bs_env%fm_s_Gamma%matrix_struct)
4803 50 : CALL cp_fm_create(fm_work, bs_env%fm_s_Gamma%matrix_struct)
4804 :
4805 200 : ALLOCATE (V_xc_n(bs_env%n_ao), Sigma_x_n(bs_env%n_ao))
4806 250 : ALLOCATE (Sigma_c_n_time(bs_env%n_ao, bs_env%num_time_freq_points, 2))
4807 150 : ALLOCATE (Sigma_c_n_freq(bs_env%n_ao, bs_env%num_time_freq_points, 2))
4808 :
4809 112 : DO ispin = 1, bs_env%n_spin
4810 :
4811 : ! 1. Roothaan-Hall H^KS_µν C_νn = S_µν C_νn ϵ_n
4812 62 : CALL cp_fm_to_fm(bs_env%fm_ks_Gamma(ispin), fm_ks)
4813 62 : CALL cp_fm_to_fm(bs_env%fm_s_Gamma, fm_s)
4814 62 : CALL cp_fm_geeig(fm_ks, fm_s, fm_mos, bs_env%eigenval_scf(:, 1, ispin), fm_work)
4815 :
4816 : ! 2. v^xc_µν -> v^xc_nn and Σ^x_µν -> Σ^x_nn
4817 62 : CALL to_Gamma_and_mo_real(V_xc_n, bs_env%fm_V_xc_Gamma(ispin), bs_env, fm_mos)
4818 62 : CALL to_Gamma_and_mo_real(Sigma_x_n, fm_Sigma_x_Gamma(ispin), bs_env, fm_mos)
4819 :
4820 : ! 3. Σ^c_µν(+/-i|τ_j|) -> Σ^c_nn(+/-i|τ_j|)
4821 852 : DO j_t = 1, bs_env%num_time_freq_points
4822 : CALL to_Gamma_and_mo_real(Sigma_c_n_time(:, j_t, 1), &
4823 790 : fm_Sigma_c_Gamma_time(j_t, 1, ispin), bs_env, fm_mos)
4824 : CALL to_Gamma_and_mo_real(Sigma_c_n_time(:, j_t, 2), &
4825 852 : fm_Sigma_c_Gamma_time(j_t, 2, ispin), bs_env, fm_mos)
4826 : END DO
4827 :
4828 : ! 4. Σ^c_nn(iτ) -> Σ^c_nn(iω)
4829 62 : CALL time_to_freq(bs_env, Sigma_c_n_time, Sigma_c_n_freq, ispin)
4830 :
4831 : ! 5. Analytic continuation Σ^c_nn(iω) -> Σ^c_nn(ϵ) and the QP levels
4832 : CALL analyt_conti_and_print(bs_env, Sigma_c_n_freq, Sigma_x_n, V_xc_n, &
4833 112 : bs_env%eigenval_scf(:, 1, ispin), 1, ispin)
4834 :
4835 : END DO ! ispin
4836 :
4837 50 : CALL get_all_VBM_CBM_bandgaps(bs_env)
4838 :
4839 50 : IF (bs_env%gw_flavour == G0W0) CALL cp_fm_release(fm_Sigma_x_Gamma)
4840 50 : CALL cp_fm_release(fm_Sigma_c_Gamma_time)
4841 :
4842 50 : CALL cp_fm_release(fm_ks)
4843 50 : CALL cp_fm_release(fm_s)
4844 50 : CALL cp_fm_release(fm_mos)
4845 50 : CALL cp_fm_release(fm_work)
4846 :
4847 50 : CALL timestop(handle)
4848 :
4849 100 : END SUBROUTINE compute_QP_energies
4850 :
4851 : ! **************************************************************************************************
4852 : !> \brief AO -> MO transform of a Γ-point matrix
4853 : !> \param array_n ...
4854 : !> \param fm_Gamma ...
4855 : !> \param bs_env ...
4856 : !> \param fm_mos ...
4857 : ! **************************************************************************************************
4858 1704 : SUBROUTINE to_Gamma_and_mo_real(array_n, fm_Gamma, bs_env, fm_mos)
4859 :
4860 : REAL(KIND=dp), DIMENSION(:) :: array_n
4861 : TYPE(cp_fm_type) :: fm_Gamma
4862 : TYPE(post_scf_bandstructure_type), POINTER :: bs_env
4863 : TYPE(cp_fm_type) :: fm_mos
4864 :
4865 : CHARACTER(LEN=*), PARAMETER :: routineN = 'to_Gamma_and_mo_real'
4866 :
4867 : INTEGER :: handle, nmo
4868 : TYPE(cp_fm_type) :: fm_mo, fm_tmp
4869 :
4870 1704 : CALL timeset(routineN, handle)
4871 :
4872 1704 : CALL cp_fm_create(fm_tmp, fm_Gamma%matrix_struct)
4873 1704 : CALL cp_fm_create(fm_mo, fm_Gamma%matrix_struct)
4874 :
4875 1704 : nmo = bs_env%n_ao
4876 1704 : CALL parallel_gemm('N', 'N', nmo, nmo, nmo, 1.0_dp, fm_Gamma, fm_mos, 0.0_dp, fm_tmp)
4877 1704 : CALL parallel_gemm('T', 'N', nmo, nmo, nmo, 1.0_dp, fm_mos, fm_tmp, 0.0_dp, fm_mo)
4878 :
4879 1704 : CALL cp_fm_get_diag(fm_mo, array_n)
4880 :
4881 1704 : CALL cp_fm_release(fm_mo)
4882 1704 : CALL cp_fm_release(fm_tmp)
4883 :
4884 1704 : CALL timestop(handle)
4885 :
4886 1704 : END SUBROUTINE to_Gamma_and_mo_real
4887 :
4888 : END MODULE gw_non_periodic_ri_rs
|