LCOV - code coverage report
Current view: top level - src - gw_ri_rs_non_periodic.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:92574dc) Lines: 96.2 % 1027 988
Test Date: 2026-09-24 01:27:39 Functions: 100.0 % 35 35

            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_ri_rs_non_periodic
      15              :    USE atomic_kind_types,               ONLY: atomic_kind_type
      16              :    USE basis_set_types,                 ONLY: gto_basis_set_type
      17              :    USE cell_types,                      ONLY: cell_type
      18              :    USE constants_operator,              ONLY: operator_coulomb
      19              :    USE cp_dbcsr_api,                    ONLY: &
      20              :         dbcsr_add, dbcsr_copy, dbcsr_create, dbcsr_deallocate_matrix, dbcsr_distribution_get, &
      21              :         dbcsr_distribution_new, dbcsr_distribution_release, dbcsr_distribution_type, dbcsr_filter, &
      22              :         dbcsr_finalize, dbcsr_get_data_size, dbcsr_get_info, dbcsr_get_occupation, &
      23              :         dbcsr_iterator_blocks_left, dbcsr_iterator_next_block, dbcsr_iterator_start, &
      24              :         dbcsr_iterator_stop, dbcsr_iterator_type, dbcsr_multiply, dbcsr_p_type, dbcsr_put_block, &
      25              :         dbcsr_release, dbcsr_scale, dbcsr_set, dbcsr_type, dbcsr_type_no_symmetry
      26              :    USE cp_dbcsr_contrib,                ONLY: dbcsr_reserve_all_blocks
      27              :    USE cp_dbcsr_operations,             ONLY: copy_dbcsr_to_fm,&
      28              :                                               copy_fm_to_dbcsr,&
      29              :                                               dbcsr_deallocate_matrix_set,&
      30              :                                               max_elements_per_block
      31              :    USE cp_fm_basic_linalg,              ONLY: cp_fm_scale_and_add,&
      32              :                                               cp_fm_uplo_to_full
      33              :    USE cp_fm_diag,                      ONLY: cp_fm_geeig
      34              :    USE cp_fm_types,                     ONLY: cp_fm_create,&
      35              :                                               cp_fm_get_diag,&
      36              :                                               cp_fm_get_info,&
      37              :                                               cp_fm_release,&
      38              :                                               cp_fm_set_all,&
      39              :                                               cp_fm_to_fm,&
      40              :                                               cp_fm_type
      41              :    USE gw_ri_rs_compute_Z_lP,           ONLY: compute_Z_lP
      42              :    USE gw_ri_rs_grid_setup_main,        ONLY: setup_ri_rs_grid
      43              :    USE gw_ri_rs_utils,                  ONLY: evaluate_ao_basis_on_points,&
      44              :                                               precompute_ri_rs_radii
      45              :    USE gw_tensor_large_cell_Gamma,      ONLY: Fourier_transform_w_to_t,&
      46              :                                               G_occ_vir,&
      47              :                                               compute_fm_chi_Gamma_freq,&
      48              :                                               create_fm_W_MIC_time,&
      49              :                                               delete_unnecessary_files,&
      50              :                                               fill_fm_Sigma_c_Gamma_time,&
      51              :                                               fm_write
      52              :    USE gw_utils,                        ONLY: analyt_conti_and_print,&
      53              :                                               de_init_bs_env,&
      54              :                                               time_to_freq
      55              :    USE gw_utils_dbcsr,                  ONLY: hadamard_product
      56              :    USE gw_utils_fm,                     ONLY: fm_contract_ABA,&
      57              :                                               fm_invert,&
      58              :                                               fm_sqrt
      59              :    USE input_constants,                 ONLY: G0W0,&
      60              :                                               evGW0,&
      61              :                                               rtp_method_bse
      62              :    USE kinds,                           ONLY: dp,&
      63              :                                               int_8,&
      64              :                                               max_line_length
      65              :    USE kpoint_coulomb_2c,               ONLY: build_2c_coulomb_matrix_kp
      66              :    USE machine,                         ONLY: m_flush,&
      67              :                                               m_walltime
      68              :    USE message_passing,                 ONLY: mp_mem_avail_per_rank_GB,&
      69              :                                               mp_para_env_type,&
      70              :                                               mp_print_mem_per_rank
      71              :    USE mp2_ri_2c,                       ONLY: RI_2c_integral_mat
      72              :    USE parallel_gemm_api,               ONLY: parallel_gemm
      73              :    USE particle_types,                  ONLY: particle_type
      74              :    USE physcon,                         ONLY: evolt
      75              :    USE post_scf_bandstructure_types,    ONLY: post_scf_bandstructure_type
      76              :    USE post_scf_bandstructure_utils,    ONLY: get_all_VBM_CBM_bandgaps
      77              :    USE qs_environment_types,            ONLY: get_qs_env,&
      78              :                                               qs_environment_type
      79              :    USE qs_kind_types,                   ONLY: qs_kind_type
      80              : #include "./base/base_uses.f90"
      81              : 
      82              :    IMPLICIT NONE
      83              : 
      84              :    PRIVATE
      85              : 
      86              :    CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'gw_ri_rs_non_periodic'
      87              : 
      88              :    PUBLIC :: gw_calc_ri_rs_non_periodic, atomic_basis_at_grid_point, &
      89              :              reserve_blocks_within_radius
      90              : 
      91              : CONTAINS
      92              : 
      93              : ! **************************************************************************************************
      94              : !> \brief GW calculation using RI-RS formalism for molecules
      95              : !> \param qs_env ...
      96              : !> \param bs_env Band-structure environment containing GW parameters.
      97              : ! **************************************************************************************************
      98           40 :    SUBROUTINE gw_calc_ri_rs_non_periodic(qs_env, bs_env)
      99              : 
     100              :       TYPE(qs_environment_type), POINTER                 :: qs_env
     101              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     102              : 
     103              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'gw_calc_ri_rs_non_periodic'
     104              : 
     105              :       INTEGER                                            :: handle
     106           40 :       TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:)        :: fm_Sigma_x_Gamma, fm_W_time
     107              : 
     108           40 :       CALL timeset(routineN, handle)
     109              : 
     110              :       ! ==============================================================================
     111              :       ! 0. Precompute AO and RI radii
     112              :       !    Per-atom cutoff radii from the most diffuse Gaussian primitives of
     113              :       !    the AO ("ORB") and RI auxiliary ("RI_AUX") basis sets:
     114              :       !      α_min,ao = min { ζ_ao | ζ_ao > 10⁻³ },  α_min,ri analogous
     115              :       !      r_ao = sqrt( -ln(ε) / α_min,ao )   (radius_ao_per_atom)
     116              :       !      r_ri = sqrt( -ln(ε) / α_min,ri )   (radius_ri_per_atom)
     117              :       ! ==============================================================================
     118           40 :       CALL precompute_ri_rs_radii(bs_env)
     119              : 
     120              :       ! ==============================================================================
     121              :       ! 1. Grid generation for RI-RS
     122              :       !    Modified Lebedev atomic grids (Duchemin & Blase), one per atom,
     123              :       !    concatenated into a flat global list:  r_l = R_A + r_l^(A)
     124              :       ! ==============================================================================
     125           40 :       CALL setup_ri_rs_grid(bs_env, bs_env%ri_rs%grid_points)
     126              : 
     127              :       ! ==============================================================================
     128              :       ! 2a. Atomic basis evaluation on the grid (grid x AO matrix)
     129              :       !     ϕ_μl = ϕ_μ(r_l)   (mat_phi_mu_l)
     130              :       ! ==============================================================================
     131              :       CALL atomic_basis_at_grid_point(bs_env, bs_env%ri_rs%grid_points, &
     132           40 :                                       bs_env%ri_rs%mat_phi_mu_l)
     133              : 
     134              :       ! ==============================================================================
     135              :       ! 2b. Print the memory estimate for the RI-RS calculation
     136              :       ! ==============================================================================
     137           40 :       CALL print_ri_rs_memory_estimate(bs_env)
     138              : 
     139              :       ! ==============================================================================
     140              :       ! 3. RI-RS fitting coefficients Z_lP (grid x RI matrix)
     141              :       !    Per-atom regularized solve, restricted to grid points r_l within a
     142              :       !    cutoff distance of atom P:
     143              :       !      a. D_ll' = [ Σ_μ ϕ_μ(r_l) ϕ_μ(r_l') ]²
     144              :       !      b. D_lP  = Σ_μν ϕ_μ(r_l) ϕ_ν(r_l) (μν|P)
     145              :       !      c. Jacobi conditioning with d_l = 1/sqrt(D_ll):
     146              :       !         D'_ll' = d_l D_ll' d_l' + λδ_ll' ,  D'_lP = d_l D_lP
     147              :       !      d. Solve Σ_l' D'_ll' Z'_l'P = D'_lP
     148              :       !      e. Rescale Z_lP = d_l Z'_lP                        (mat_Z_lP)
     149              :       ! ==============================================================================
     150              :       CALL compute_Z_lP(qs_env, bs_env, bs_env%ri_rs%grid_points, &
     151           40 :                         bs_env%ri_rs%mat_phi_mu_l, bs_env%ri_rs%mat_Z_lP)
     152              : 
     153              :       ! flag the RI-RS grid as built so a subsequent RT-BSE run reuses Z_lP
     154              :       ! instead of rebuilding it
     155           40 :       bs_env%ri_rs%grid_built = .TRUE.
     156              : 
     157              :       CALL mp_print_mem_per_rank(bs_env%para_env, bs_env%unit_nr, &
     158           40 :                                  label='Memory per MPI process after computing Z_lP:')
     159              : 
     160              :       ! ==============================================================================
     161              :       ! 4. Polarizability matrix χ on the imaginary-time grid
     162              :       !      G^occ_µλ(i|τ|)  = Σ_n^occ C_µn e^(-|(ϵ_n-ϵ_F)τ|) C_λn
     163              :       !      G^vir_µλ(i|τ|)  = Σ_n^vir C_µn e^(-|(ϵ_n-ϵ_F)τ|) C_λn
     164              :       !      G^occ_ll'(i|τ|) = Σ_µν ϕ_µ(r_l) G^occ_µν ϕ_ν(r_l')  (G^vir analogous)
     165              :       !      χ_ll'(iτ)       = G^occ_ll'(i|τ|) ∘ G^vir_ll'(i|τ|) (element-wise)
     166              :       !      χ_PQ(iτ)        = Σ_ll' Z_lP χ_ll'(iτ) Z_l'Q
     167              :       ! ==============================================================================
     168              :       CALL get_mat_chi_Gamma_tau(bs_env, bs_env%mat_chi_Gamma_tau, &
     169           40 :                                  bs_env%ri_rs%mat_phi_mu_l, bs_env%ri_rs%mat_Z_lP)
     170              : 
     171              :       ! ==============================================================================
     172              :       ! 5. Screened Coulomb interaction W (RI basis)
     173              :       !      χ_PQ(iτ) -> χ_PQ(iω) -> ε_PQ(iω) -> W_PQ(iω) -> W_PQ(iτ)
     174              :       ! ==============================================================================
     175           40 :       CALL compute_W(bs_env, qs_env, bs_env%mat_chi_Gamma_tau, fm_W_time)
     176              : 
     177              :       ! ==============================================================================
     178              :       ! 6. Exact-exchange self-energy Σ^x
     179              :       !      D_µν          = Σ_n^occ C_µn C_νn                (density matrix)
     180              :       !      D_ll'         = Σ_µν ϕ_µ(r_l) D_µν ϕ_ν(r_l')
     181              :       !      V^tr_ll'      = Σ_PQ Z_lP V^tr_PQ Z_l'Q          (truncated Coulomb)
     182              :       !      Σ^x_ll'       = D_ll' ∘ V^tr_ll'
     183              :       !      Σ^x_λσ(k=0)   = -Σ_ll' ϕ_λ(r_l) Σ^x_ll' ϕ_σ(r_l')
     184              :       ! ==============================================================================
     185              :       CALL compute_Sigma_x(bs_env, qs_env, bs_env%ri_rs%mat_phi_mu_l, &
     186           40 :                            bs_env%ri_rs%mat_Z_lP, fm_Sigma_x_Gamma)
     187              : 
     188              :       ! ==============================================================================
     189              :       ! 7. Correlation self-energy Σ^c and quasiparticle energies, iterated
     190              :       !    until eigenvalue self-consistency if the &EVGW0 section is given and
     191              :       !    done in a single pass for G0W0.
     192              :       !
     193              :       !    (a) W_ll'(iτ)   =  Σ_PQ Z_lP W^MIC_PQ(iτ) Z_l'Q
     194              :       !        Σ^c_ll'(iτ) = -G^occ_ll'(i|τ|) ∘ W_ll'(iτ),  τ < 0
     195              :       !        Σ^c_ll'(iτ) =  G^vir_ll'(i|τ|) ∘ W_ll'(iτ),  τ > 0
     196              :       !        Σ^c_λσ(iτ)  =  Σ_ll' ϕ_λ(r_l) Σ^c_ll'(iτ) ϕ_σ(r_l')
     197              :       !    (b) Σ^c_λσ(iτ) -> Σ^c_nn(ϵ)
     198              :       !        ϵ_n^GW,(i) = ϵ_n^DFT + Σ^c_nn[G^(i-1),W](ϵ_n^GW,(i)) + Σ^x_nn - v^xc_nn
     199              :       ! ==============================================================================
     200           40 :       CALL compute_Sigma_c_and_QP_energies(bs_env, fm_W_time, fm_Sigma_x_Gamma)
     201              : 
     202           40 :       CALL de_init_bs_env(qs_env, bs_env)
     203              : 
     204           40 :       CALL timestop(handle)
     205              : 
     206           40 :    END SUBROUTINE gw_calc_ri_rs_non_periodic
     207              : 
     208              : ! **************************************************************************************************
     209              : !> \brief Correlation self-energy and quasiparticle energies, iterated to eigenvalue
     210              : !>        self-consistency in G (evGW0).
     211              : !>
     212              : !>        For G0W0 this runs once with G^(0) built from the DFT eigenvalues. For evGW0 the
     213              : !>        Green's function is rebuilt from the quasiparticle energies of all states until the
     214              : !>        quasiparticle HOMO, LUMO and HOMO-LUMO gap change by less than EPS_ITER between two
     215              : !>        cycles, or MAX_ITER cycles are spent. W, Σ^x and everything computed before this
     216              : !>        routine stay frozen: they either do not depend on the eigenvalues at all (Σ^x is G
     217              : !>        at τ = 0, a pure density matrix) or are held fixed by construction in GW0. The
     218              : !>        imaginary-time grid is fixed as well, since W(iτ) lives on it.
     219              : !>
     220              : !> \param bs_env ...
     221              : !> \param fm_W_time ...
     222              : !> \param fm_Sigma_x_Gamma ...
     223              : ! **************************************************************************************************
     224           40 :    SUBROUTINE compute_Sigma_c_and_QP_energies(bs_env, fm_W_time, fm_Sigma_x_Gamma)
     225              : 
     226              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     227              :       TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:)        :: fm_W_time, fm_Sigma_x_Gamma
     228              : 
     229              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_Sigma_c_and_QP_energies'
     230              : 
     231              :       INTEGER                                            :: handle, i_iter, n_iter
     232              :       LOGICAL                                            :: converged
     233           40 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: eigenval_scf_Gamma_dft
     234              :       REAL(KIND=dp), DIMENSION(2)                        :: e_fermi_dft
     235              :       REAL(KIND=dp), DIMENSION(3, 2)                     :: band_prev
     236           40 :       TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:, :, :)  :: fm_Sigma_c_Gamma_time
     237              : 
     238           40 :       CALL timeset(routineN, handle)
     239              : 
     240              :       converged = .FALSE.
     241              : 
     242           40 :       CALL init_evGW0(bs_env, n_iter, band_prev, eigenval_scf_Gamma_dft, e_fermi_dft)
     243              : 
     244              :       ! evGW0 self-consistency loop; for G0W0, loop is terminated after one iteration
     245           54 :       DO i_iter = 1, n_iter
     246              : 
     247           54 :          bs_env%ri_rs%evgw0_i_iter = i_iter
     248              : 
     249              :          ! W_ll'(iτ)   =  Σ_PQ Z_lP W^MIC_PQ(iτ) Z_l'Q
     250              :          ! Σ^c_ll'(iτ) = -G^occ_ll'(i|τ|) ∘ W_ll'(iτ),  τ < 0
     251              :          ! Σ^c_ll'(iτ) =  G^vir_ll'(i|τ|) ∘ W_ll'(iτ),  τ > 0
     252              :          ! Σ^c_λσ(iτ)  =  Σ_ll' ϕ_λ(r_l) Σ^c_ll'(iτ) ϕ_σ(r_l')
     253              :          CALL compute_Sigma_c(bs_env, fm_W_time, bs_env%ri_rs%mat_phi_mu_l, &
     254           54 :                               bs_env%ri_rs%mat_Z_lP, fm_Sigma_c_Gamma_time)
     255              : 
     256              :          ! Σ^c_λσ(iτ) -> Σ^c_nn(ϵ)
     257              :          ! ϵ_n^GW,(i) = ϵ_n^DFT + Σ^c_nn[G^(i-1),W](ϵ_n^GW,(i)) + Σ^x_nn - v^xc_nn
     258           54 :          CALL compute_QP_energies(bs_env, fm_Sigma_x_Gamma, fm_Sigma_c_Gamma_time)
     259              : 
     260           54 :          IF (bs_env%gw_flavour == G0W0) EXIT
     261              : 
     262           18 :          CALL print_evGW0_band_edges(bs_env, band_prev, i_iter, n_iter, converged)
     263              : 
     264           18 :          IF (i_iter == n_iter .OR. converged) EXIT
     265              : 
     266              :          ! eigenvalues ϵ_n to be updated in G:
     267              :          ! G^occ_µλ(i|τ|) = Σ_n^occ C_µn e^(-|(ϵ_n-ϵ_F)τ|) C_λn
     268              :          ! G^vir_µλ(i|τ|) = Σ_n^vir C_µn e^(-|(ϵ_n-ϵ_F)τ|) C_λn
     269           72 :          CALL update_eigenvalues_G(bs_env)
     270              : 
     271              :       END DO
     272              : 
     273           40 :       CALL cp_fm_release(fm_W_time)
     274              : 
     275           40 :       CALL reset_and_clean_bs_env(bs_env, eigenval_scf_Gamma_dft, e_fermi_dft, fm_Sigma_x_Gamma)
     276              : 
     277           40 :       CALL delete_unnecessary_files(bs_env)
     278              : 
     279           40 :       CALL timestop(handle)
     280              : 
     281           40 :    END SUBROUTINE compute_Sigma_c_and_QP_energies
     282              : 
     283              : ! **************************************************************************************************
     284              : !> \brief Sets up the evGW0 eigenvalue self-consistency loop: the cycle count, the eigenvalues
     285              : !>        the first Green's function is built from, and the DFT reference that the loop
     286              : !>        overwrites. A G0W0 run reduces to a single cycle and needs none of it.
     287              : !> \param bs_env ...
     288              : !> \param n_iter ...
     289              : !> \param band_prev ...
     290              : !> \param eigenval_scf_Gamma_dft ...
     291              : !> \param e_fermi_dft ...
     292              : ! **************************************************************************************************
     293           40 :    SUBROUTINE init_evGW0(bs_env, n_iter, band_prev, eigenval_scf_Gamma_dft, e_fermi_dft)
     294              : 
     295              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     296              :       INTEGER, INTENT(OUT)                               :: n_iter
     297              :       REAL(KIND=dp), DIMENSION(3, 2), INTENT(OUT)        :: band_prev
     298              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
     299              :          INTENT(OUT)                                     :: eigenval_scf_Gamma_dft
     300              :       REAL(KIND=dp), DIMENSION(2), INTENT(OUT)           :: e_fermi_dft
     301              : 
     302              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'init_evGW0'
     303              : 
     304              :       INTEGER                                            :: handle
     305              : 
     306           40 :       CALL timeset(routineN, handle)
     307              : 
     308           40 :       n_iter = 1
     309           40 :       band_prev(:, :) = 0.0_dp
     310           40 :       e_fermi_dft(:) = 0.0_dp
     311              : 
     312           40 :       IF (bs_env%gw_flavour == evGW0) THEN
     313            4 :          n_iter = bs_env%ri_rs%evgw0_iter
     314              : 
     315              :          ! eigenvalues currently in G; the first cycle starts is a plain G0W0 step
     316           70 :          bs_env%eigenval_evGW0(:, :, :) = bs_env%eigenval_scf(:, :, :)
     317              :          ! the loop overwrites these; post-GW printing expects the DFT values back
     318           76 :          ALLOCATE (eigenval_scf_Gamma_dft, SOURCE=bs_env%eigenval_scf_Gamma)
     319           12 :          e_fermi_dft(:) = bs_env%e_fermi(:)
     320              :       END IF
     321              : 
     322           40 :       CALL timestop(handle)
     323              : 
     324           40 :    END SUBROUTINE init_evGW0
     325              : 
     326              : ! **************************************************************************************************
     327              : !> \brief Feeds the quasiparticle energies of the current evGW0 cycle back into the Green's
     328              : !>        function used by the next one, and re-centres the Fermi level between the new
     329              : !>        quasiparticle HOMO and LUMO.
     330              : !> \param bs_env ...
     331              : ! **************************************************************************************************
     332           14 :    SUBROUTINE update_eigenvalues_G(bs_env)
     333              : 
     334              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     335              : 
     336              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'update_eigenvalues_G'
     337              : 
     338              :       INTEGER                                            :: handle, i_mo, ispin
     339              : 
     340           14 :       CALL timeset(routineN, handle)
     341              : 
     342              :       ! record this cycle's evGW0 result; it is also what the next Green's function is built from
     343          230 :       bs_env%eigenval_evGW0(:, :, :) = bs_env%eigenval_GW(:, :, :)
     344              : 
     345           34 :       DO ispin = 1, bs_env%n_spin
     346              :          ! Update all physical states; exclude linear-dependency placeholders.
     347          196 :          DO i_mo = 1, bs_env%n_mo_retained
     348          196 :             bs_env%eigenval_scf_Gamma(i_mo, ispin) = bs_env%eigenval_GW(i_mo, 1, ispin)
     349              :          END DO
     350              :          bs_env%e_fermi(ispin) = &
     351              :             0.5_dp*(bs_env%eigenval_GW(bs_env%n_occ(ispin), 1, ispin) + &
     352           34 :                     bs_env%eigenval_GW(bs_env%n_occ(ispin) + 1, 1, ispin))
     353              :       END DO
     354              : 
     355           14 :       CALL timestop(handle)
     356              : 
     357           14 :    END SUBROUTINE update_eigenvalues_G
     358              : 
     359              : ! **************************************************************************************************
     360              : !> \brief Restores the DFT reference that the evGW0 loop overwrote and cleanup
     361              : !> \param bs_env ...
     362              : !> \param eigenval_scf_Gamma_dft ...
     363              : !> \param e_fermi_dft ...
     364              : !> \param fm_Sigma_x_Gamma ..
     365              : ! **************************************************************************************************
     366           40 :    SUBROUTINE reset_and_clean_bs_env(bs_env, eigenval_scf_Gamma_dft, e_fermi_dft, fm_Sigma_x_Gamma)
     367              : 
     368              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     369              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
     370              :          INTENT(INOUT)                                   :: eigenval_scf_Gamma_dft
     371              :       REAL(KIND=dp), DIMENSION(2), INTENT(IN)            :: e_fermi_dft
     372              :       TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:)        :: fm_Sigma_x_Gamma
     373              : 
     374              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'reset_and_clean_bs_env'
     375              : 
     376              :       INTEGER                                            :: handle
     377              : 
     378           40 :       CALL timeset(routineN, handle)
     379              : 
     380           40 :       IF (bs_env%gw_flavour == evGW0) THEN
     381              :          ! Update array with final evGW0 result
     382           70 :          bs_env%eigenval_evGW0(:, :, :) = bs_env%eigenval_GW(:, :, :)
     383              :          ! put the DFT reference back for the post-GW DOS/band-edge printing
     384           64 :          bs_env%eigenval_scf_Gamma(:, :) = eigenval_scf_Gamma_dft(:, :)
     385           12 :          bs_env%e_fermi(:) = e_fermi_dft(:)
     386            4 :          DEALLOCATE (eigenval_scf_Gamma_dft)
     387            4 :          CALL cp_fm_release(fm_Sigma_x_Gamma)
     388              :       END IF
     389              : 
     390           40 :       CALL timestop(handle)
     391              : 
     392           40 :    END SUBROUTINE reset_and_clean_bs_env
     393              : 
     394              : ! **************************************************************************************************
     395              : !> \brief Print the quasiparticle HOMO, LUMO and HOMO-LUMO gap of the current evGW0 cycle
     396              : !> \param bs_env ...
     397              : !> \param band_prev ...
     398              : !> \param i_iter ...
     399              : !> \param n_iter ...
     400              : !> \param converged ...
     401              : ! **************************************************************************************************
     402           18 :    SUBROUTINE print_evGW0_band_edges(bs_env, band_prev, i_iter, n_iter, converged)
     403              : 
     404              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     405              :       REAL(KIND=dp), DIMENSION(3, 2), INTENT(INOUT)      :: band_prev
     406              :       INTEGER, INTENT(IN)                                :: i_iter, n_iter
     407              :       LOGICAL, INTENT(OUT)                               :: converged
     408              : 
     409              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'print_evGW0_band_edges'
     410              : 
     411              :       INTEGER                                            :: handle, homo, ispin, u
     412              :       REAL(KIND=dp)                                      :: max_delta
     413              :       REAL(KIND=dp), DIMENSION(3)                        :: band
     414              : 
     415           18 :       CALL timeset(routineN, handle)
     416              : 
     417           18 :       u = bs_env%unit_nr
     418              : 
     419           18 :       converged = (i_iter > 1)
     420           18 :       max_delta = 0.0_dp
     421              : 
     422           18 :       IF (u > 0) THEN
     423            9 :          WRITE (u, '(A)') ' '
     424            9 :          WRITE (u, '(T2,A)') REPEAT('-', 79)
     425            9 :          WRITE (u, '(T2,A,I4,A,I4)') 'evGW0 cycle', i_iter, ' /', n_iter
     426            9 :          WRITE (u, '(T2,A)') REPEAT('-', 79)
     427              :       END IF
     428              : 
     429           44 :       DO ispin = 1, bs_env%n_spin
     430              : 
     431           26 :          homo = bs_env%n_occ(ispin)
     432           26 :          band(1) = bs_env%eigenval_GW(homo, 1, ispin)
     433           26 :          band(2) = bs_env%eigenval_GW(homo + 1, 1, ispin)
     434           26 :          band(3) = band(2) - band(1)
     435              : 
     436           26 :          IF (i_iter > 1) THEN
     437           80 :             max_delta = MAX(max_delta, MAXVAL(ABS(band(:) - band_prev(:, ispin))))
     438              :          END IF
     439              : 
     440           26 :          IF (u > 0) THEN
     441           13 :             IF (bs_env%n_spin == 2) WRITE (u, '(T2,A,I0)') 'Spin ', ispin
     442           13 :             WRITE (u, '(T2,A,T61,F20.3)') 'evGW0 HOMO (eV)', band(1)*evolt
     443           13 :             WRITE (u, '(T2,A,T61,F20.3)') 'evGW0 LUMO (eV)', band(2)*evolt
     444           13 :             WRITE (u, '(T2,A,T61,F20.3)') 'evGW0 HOMO-LUMO gap (eV)', band(3)*evolt
     445              :          END IF
     446              : 
     447          122 :          band_prev(:, ispin) = band(:)
     448              : 
     449              :       END DO
     450              : 
     451           18 :       IF (i_iter > 1) THEN
     452           14 :          converged = (max_delta < bs_env%ri_rs%evgw0_eps_iter)
     453           21 :          IF (u > 0) WRITE (u, '(T2,A,T61,F20.6)') 'Max. change to previous cycle (eV)', &
     454           14 :             max_delta*evolt
     455              :       END IF
     456              : 
     457           11 :       IF (u > 0) WRITE (u, '(T2,A)') REPEAT('-', 79)
     458              : 
     459           18 :       IF (converged) THEN
     460            4 :          IF (u > 0) THEN
     461            2 :             WRITE (u, '(A)') ' '
     462              :             WRITE (u, '(T2,A,I4,A)') &
     463            2 :                'evGW0 eigenvalue self-consistency reached in', i_iter, ' cycles.'
     464            2 :             WRITE (u, '(A)') ' '
     465              :          END IF
     466           14 :       ELSE IF (i_iter == n_iter) THEN
     467              :          CALL cp_warn(__LOCATION__, &
     468              :                       "The evGW0 eigenvalue self-consistency cycle did not converge "// &
     469              :                       "within MAX_ITER cycles. The reported quasiparticle energies are "// &
     470            0 :                       "those of the last cycle.")
     471              :       END IF
     472              : 
     473           18 :       CALL timestop(handle)
     474              : 
     475           18 :    END SUBROUTINE print_evGW0_band_edges
     476              : 
     477              : ! **************************************************************************************************
     478              : !> \brief Evaluates the AO basis on the RI-RS grid and stores it as the sparse DBCSR matrix
     479              : !>        ϕ_μl = ϕ_μ(r_l)  (rows = grid points in atom-aligned blocks of at most
     480              : !>        max_elements_per_block points, columns = one block per atom's full AO set).
     481              : !>        Grid points outside the reach of an atom's most
     482              : !>        diffuse Gaussian (or the CUTOFF_RADIUS_RL_AO) are skipped, and only blocks
     483              : !>        with at least one element > eps_filter are stored. This locality is the source of
     484              : !>        ALL grid-dimension sparsity used downstream. Also caches the atom centers and the
     485              : !>        per-chunk centroids needed by the optional CUTOFF_RADIUS_G_W / CUTOFF_RADIUS_RL_W
     486              : !>        operator truncations.
     487              : !> \param bs_env ...
     488              : !> \param ri_rs_grid_points ...
     489              : !> \param mat_phi_mu_l ...
     490              : ! **************************************************************************************************
     491           48 :    SUBROUTINE atomic_basis_at_grid_point(bs_env, ri_rs_grid_points, mat_phi_mu_l)
     492              : 
     493              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     494              :       REAL(KIND=dp), ALLOCATABLE, INTENT(INOUT)          :: ri_rs_grid_points(:, :)
     495              :       TYPE(dbcsr_type), INTENT(OUT)                      :: mat_phi_mu_l
     496              : 
     497              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'atomic_basis_at_grid_point'
     498              : 
     499              :       INTEGER :: bs_eff, c_size, handle, i, i_blk, ia, iatom, ikind, natom, npcol, nprow, &
     500              :          num_grid_chunks, r_end, r_start, remaining, run, safe_max
     501           48 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: blk_row_start
     502           48 :       INTEGER, DIMENSION(:), POINTER                     :: col_dist, r_blk_sizes, row_dist, sizes_AO
     503              :       REAL(KIND=dp)                                      :: r2_threshold
     504           48 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: atom_col_buffer
     505              :       TYPE(cell_type), POINTER                           :: cell
     506              :       TYPE(dbcsr_distribution_type)                      :: dbcsr_dist_ks, dist
     507              :       TYPE(gto_basis_set_type), POINTER                  :: ao_basis
     508              :       TYPE(mp_para_env_type), POINTER                    :: para_env
     509           48 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
     510              : 
     511           48 :       CALL timeset(routineN, handle)
     512              : 
     513           48 :       natom = bs_env%n_atom
     514           48 :       cell => bs_env%ri_rs%cell
     515           48 :       para_env => bs_env%para_env
     516           48 :       particle_set => bs_env%ri_rs%particle_set
     517           48 :       sizes_AO => bs_env%sizes_AO
     518           48 :       CPASSERT(ASSOCIATED(cell))
     519           48 :       CPASSERT(ASSOCIATED(para_env))
     520           48 :       CPASSERT(ASSOCIATED(particle_set))
     521           48 :       CPASSERT(SIZE(sizes_AO) == natom)
     522           48 :       CPASSERT(SIZE(ri_rs_grid_points, 2) == bs_env%ri_rs%n_grid_points)
     523              : 
     524              :       ! =========================================================================
     525              :       ! 1. SETUP DBCSR MATRIX TOPOLOGY
     526              :       ! =========================================================================
     527              : 
     528              :       ! B. Define Row Block Sizes: atom-aligned blocks (a block never spans two atoms' grid
     529              :       ! runs), each atom's run subdivided into blocks of at most bs_eff points.
     530              : 
     531              :       ! Fetch CP2K's default process grid configuration
     532           48 :       CALL dbcsr_get_info(bs_env%mat_ao_ao%matrix, distribution=dbcsr_dist_ks)
     533           48 :       CALL dbcsr_distribution_get(dbcsr_dist_ks, nprows=nprow, npcols=npcol)
     534              : 
     535              :       ! Overflow-safe upper bound on the block size (see bs_env%dbcsr_msg_elem_limit).
     536              :       safe_max = INT(0.5_dp*REAL(bs_env%dbcsr_msg_elem_limit, dp)* &
     537              :                      REAL(MAX(MIN(nprow, npcol), 1), dp)/ &
     538           48 :                      REAL(bs_env%ri_rs%n_grid_points, dp))
     539           48 :       safe_max = MAX(1, safe_max)
     540              :       ! Block size = CP2K's global max_elements_per_block (GLOBAL/DBCSR input; default 32),
     541              :       ! overflow-capped.
     542           48 :       bs_eff = MAX(1, MIN(max_elements_per_block, safe_max))
     543              : 
     544              :       ! Count the atom-aligned blocks, then fill r_blk_sizes and each block's starting grid row.
     545           48 :       num_grid_chunks = 0
     546          166 :       DO ia = 1, natom
     547          118 :          run = bs_env%ri_rs%grid_atom_boundaries(ia + 1) - bs_env%ri_rs%grid_atom_boundaries(ia)
     548          166 :          IF (run > 0) num_grid_chunks = num_grid_chunks + (run + bs_eff - 1)/bs_eff
     549              :       END DO
     550          192 :       ALLOCATE (r_blk_sizes(num_grid_chunks), blk_row_start(num_grid_chunks))
     551           48 :       i_blk = 0
     552           48 :       r_start = 1
     553          166 :       DO ia = 1, natom
     554          118 :          remaining = bs_env%ri_rs%grid_atom_boundaries(ia + 1) - bs_env%ri_rs%grid_atom_boundaries(ia)
     555          780 :          DO WHILE (remaining > 0)
     556          614 :             i_blk = i_blk + 1
     557          614 :             r_blk_sizes(i_blk) = MIN(bs_eff, remaining)
     558          614 :             blk_row_start(i_blk) = r_start
     559          614 :             r_start = r_start + r_blk_sizes(i_blk)
     560          732 :             remaining = remaining - r_blk_sizes(i_blk)
     561              :          END DO
     562              :       END DO
     563              : 
     564           48 :       IF (bs_env%unit_nr > 0) THEN
     565              :          ! T71 compensates for the two two-byte Greek characters in the label.
     566              :          WRITE (bs_env%unit_nr, '(T2,A,T71,I12)') &
     567           24 :             'RI-RS grid row-blocks of ϕ_μ(r_l)', num_grid_chunks
     568           24 :          WRITE (bs_env%unit_nr, '(T2,A,T69,I12)') 'RI-RS grid points per block (max)', bs_eff
     569              :       END IF
     570              : 
     571              :       ! Cache atomic positions: AO and RI blocks are one-block-per-atom, so these are the
     572              :       ! block centers used by the optional CUTOFF_RADIUS_G_W atom-pair truncation.
     573           48 :       IF (ALLOCATED(bs_env%ri_rs%atom_centers)) DEALLOCATE (bs_env%ri_rs%atom_centers)
     574          144 :       ALLOCATE (bs_env%ri_rs%atom_centers(3, natom))
     575          166 :       DO iatom = 1, natom
     576          992 :          bs_env%ri_rs%atom_centers(1:3, iatom) = particle_set(iatom)%r(1:3)
     577              :       END DO
     578              : 
     579              :       ! Cache per-chunk centroids for the optional CUTOFF_RADIUS_RL_W / CUTOFF_RADIUS_W0 block
     580              :       ! truncations. Left unallocated otherwise, so PRESENT(centroids) stays .FALSE. at the
     581              :       ! contract_grid_panels* calls.
     582           48 :       IF (bs_env%ri_rs%cutoff_radius_v_w > 0.0_dp .OR. &
     583              :           bs_env%ri_rs%cutoff_radius_w0 > 0.0_dp) THEN
     584            8 :          IF (ALLOCATED(bs_env%ri_rs%chunk_centroids)) DEALLOCATE (bs_env%ri_rs%chunk_centroids)
     585           24 :          ALLOCATE (bs_env%ri_rs%chunk_centroids(3, num_grid_chunks))
     586          110 :          DO i_blk = 1, num_grid_chunks
     587          102 :             r_start = blk_row_start(i_blk)
     588          102 :             r_end = r_start + r_blk_sizes(i_blk) - 1
     589              :             bs_env%ri_rs%chunk_centroids(1, i_blk) = &
     590         2950 :                SUM(ri_rs_grid_points(1, r_start:r_end))/REAL(r_blk_sizes(i_blk), dp)
     591              :             bs_env%ri_rs%chunk_centroids(2, i_blk) = &
     592         2950 :                SUM(ri_rs_grid_points(2, r_start:r_end))/REAL(r_blk_sizes(i_blk), dp)
     593              :             bs_env%ri_rs%chunk_centroids(3, i_blk) = &
     594         2998 :                SUM(ri_rs_grid_points(3, r_start:r_end))/REAL(r_blk_sizes(i_blk), dp)
     595              :          END DO
     596              :       END IF
     597              : 
     598              :       ! C. Build Custom Mappings using Round-Robin across the 2D process grid
     599              : 
     600           96 :       ALLOCATE (row_dist(num_grid_chunks))
     601          662 :       DO i = 1, num_grid_chunks
     602          662 :          row_dist(i) = MOD(i - 1, nprow)
     603              :       END DO
     604              : 
     605          144 :       ALLOCATE (col_dist(natom))
     606          166 :       DO i = 1, natom
     607          166 :          col_dist(i) = MOD(i - 1, npcol)
     608              :       END DO
     609              : 
     610              :       ! E. Create the DBCSR Distribution and Initialize the Matrix
     611              :       CALL dbcsr_distribution_new(dist, template=dbcsr_dist_ks, &
     612           48 :                                   row_dist=row_dist, col_dist=col_dist)
     613              : 
     614              :       CALL dbcsr_create(mat_phi_mu_l, name="phi_val_sparse", dist=dist, &
     615              :                         matrix_type=dbcsr_type_no_symmetry, &
     616           48 :                         row_blk_size=r_blk_sizes, col_blk_size=sizes_AO)
     617              : 
     618              :       ! =========================================================================
     619              :       ! 2. STREAM DATA DIRECTLY INTO SPARSE MATRIX
     620              :       ! =========================================================================
     621              :       ! Iterate over the atoms assigned to this specific MPI rank
     622           48 :       DO iatom = para_env%mepos + 1, natom, para_env%num_pe
     623              : 
     624           59 :          c_size = sizes_AO(iatom)
     625              : 
     626              :          ! Allocate a temporary dense buffer just for this specific atom
     627          236 :          ALLOCATE (atom_col_buffer(bs_env%ri_rs%n_grid_points, c_size))
     628           59 :          atom_col_buffer = 0.0_dp
     629              : 
     630              :          ! Evaluate the basis functions on the grid. Skip grid points outside
     631              :          ! the spatial extent of the most diffuse AO Gaussian on iatom; beyond
     632              :          ! that radius the contribution is guaranteed below eps_filter. A positive
     633              :          ! CUTOFF_RADIUS_RL_AO overrides this with a user-defined hard cutoff.
     634           59 :          IF (bs_env%ri_rs%cutoff_radius_ri_ao > 0.0_dp) THEN
     635            3 :             r2_threshold = bs_env%ri_rs%cutoff_radius_ri_ao**2
     636              :          ELSE
     637           56 :             r2_threshold = bs_env%ri_rs%radius_ao_per_atom(iatom)**2
     638              :          END IF
     639           59 :          ikind = particle_set(iatom)%atomic_kind%kind_number
     640           59 :          ao_basis => bs_env%basis_set_AO(ikind)%gto_basis_set
     641              :          CALL evaluate_ao_basis_on_points(atom_col_buffer, ri_rs_grid_points, ao_basis, &
     642           59 :                                           particle_set(iatom)%r, cell, cutoff_squared=r2_threshold)
     643              : 
     644              :          ! Slice the dense column into the atom-aligned grid row-blocks and insert into DBCSR
     645          822 :          DO i_blk = 1, num_grid_chunks
     646          763 :             r_start = blk_row_start(i_blk)
     647          763 :             r_end = r_start + r_blk_sizes(i_blk) - 1
     648              : 
     649              :             ! Apply dynamic sparsity filtering: Only store blocks with physical significance
     650        82708 :             IF (MAXVAL(ABS(atom_col_buffer(r_start:r_end, 1:c_size))) > bs_env%eps_filter) THEN
     651              :                CALL dbcsr_put_block(mat_phi_mu_l, row=i_blk, col=iatom, &
     652          751 :                                     block=atom_col_buffer(r_start:r_end, 1:c_size))
     653              :             END IF
     654              :          END DO
     655              : 
     656           59 :          DEALLOCATE (atom_col_buffer)
     657              : 
     658              :       END DO
     659              : 
     660           48 :       CALL dbcsr_finalize(mat_phi_mu_l)
     661              : 
     662           48 :       IF (bs_env%unit_nr > 0) WRITE (bs_env%unit_nr, '(A)') ' '
     663           48 :       CALL print_matrix_occupation(mat_phi_mu_l, 'ϕ_μ(r_l)', bs_env)
     664              : 
     665              :       ! -------------------------------------------------------------------------
     666              :       ! CLEANUP
     667              :       ! -------------------------------------------------------------------------
     668           48 :       DEALLOCATE (r_blk_sizes, row_dist, col_dist, blk_row_start)
     669           48 :       CALL dbcsr_distribution_release(dist)
     670              : 
     671           48 :       CALL timestop(handle)
     672              : 
     673          192 :    END SUBROUTINE atomic_basis_at_grid_point
     674              : 
     675              : ! **************************************************************************************************
     676              : !> \brief Computes χ_PQ(iτ) from the occupied and virtual Green's functions.
     677              : !> \param bs_env ...
     678              : !> \param mat_chi_Gamma_tau Response matrices χ_PQ(iτ) to be computed
     679              : !> \param mat_phi_mu_l AO values ϕ_μ(r_l) on the RI-RS grid
     680              : !> \param mat_Z_lP RI-RS fitting matrix Z_lP
     681              : ! **************************************************************************************************
     682           40 :    SUBROUTINE get_mat_chi_Gamma_tau(bs_env, mat_chi_Gamma_tau, mat_phi_mu_l, mat_Z_lP)
     683              : 
     684              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     685              :       TYPE(dbcsr_p_type), DIMENSION(:), POINTER          :: mat_chi_Gamma_tau
     686              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_phi_mu_l, mat_Z_lP
     687              : 
     688              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'get_mat_chi_Gamma_tau'
     689              : 
     690              :       INTEGER                                            :: handle, i_t, ispin, n_panels
     691           40 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: pan_first, pan_last
     692              :       REAL(KIND=dp)                                      :: grid_occ, t1, tau
     693              :       TYPE(dbcsr_type)                                   :: matrix_G_occ_ao, matrix_G_vir_ao
     694              : 
     695           40 :       CALL timeset(routineN, handle)
     696              : 
     697              :       ! Panel boundaries for the grid-streaming contraction.
     698              :       ! The panels are identical for χ, Σ^x and Σ^c, so the count is reported once here for all three stages.
     699              : 
     700           40 :       CALL resolve_grid_panels(bs_env, mat_phi_mu_l, pan_first, pan_last)
     701              : 
     702           40 :       n_panels = SIZE(pan_first)
     703           40 :       IF (bs_env%unit_nr > 0) THEN
     704              :          WRITE (bs_env%unit_nr, '(T2,A,T74,I9)') &
     705           20 :             'Number of batches for χ, Σ matrices', n_panels
     706           20 :          WRITE (bs_env%unit_nr, '(A)') ' '
     707           20 :          CALL m_flush(bs_env%unit_nr)
     708              :       END IF
     709              : 
     710              :       ! =========================================================================
     711              :       ! IMAGINARY TIME LOOP
     712              :       ! χ_PQ(iτ) = Σ_s g_s · Z^T ( (ϕ G^occ_s ϕ^T) ∘ (ϕ G^vir_s ϕ^T) ) Z
     713              :       ! (g_s = spin degeneracy)
     714              :       ! =========================================================================
     715          590 :       DO i_t = 1, bs_env%num_time_freq_points
     716          550 :          t1 = m_walltime()
     717          550 :          tau = bs_env%time_frequency_grid%imaginary_time(i_t)
     718              : 
     719         1180 :          DO ispin = 1, bs_env%n_spin
     720              : 
     721              :             ! AO-space Green's functions G^occ_µν, G^vir_µν (dense AO x AO, small)
     722          630 :             CALL build_G_ao(bs_env, tau, ispin, .TRUE., .FALSE., mat_phi_mu_l, matrix_G_occ_ao)
     723          630 :             CALL build_G_ao(bs_env, tau, ispin, .FALSE., .TRUE., mat_phi_mu_l, matrix_G_vir_ao)
     724              : 
     725              :             ! χ_PQ += g_s · Z^T ( (ϕ G^occ ϕ^T) ∘ (ϕ G^vir ϕ^T) ) Z
     726              :             CALL contract_grid_panels(L_A=mat_phi_mu_l, M_A=matrix_G_occ_ao, &
     727              :                                       L_B=mat_phi_mu_l, M_B=matrix_G_vir_ao, &
     728              :                                       L_out=mat_Z_lP, mat_out=mat_chi_Gamma_tau(i_t)%matrix, &
     729              :                                       scale=bs_env%spin_degeneracy, eps=bs_env%eps_filter, &
     730              :                                       para_env=bs_env%para_env, &
     731              :                                       pan_first=pan_first, pan_last=pan_last, &
     732              :                                       lb_eq_la=.TRUE., lout_eq_la=.FALSE., &
     733              :                                       zero_out=(ispin == 1), &
     734              :                                       keep_sparsity=bs_env%ri_rs%keep_sparsity_rirs, &
     735              :                                       centroids=bs_env%ri_rs%chunk_centroids, &
     736              :                                       cutoff=bs_env%ri_rs%cutoff_radius_v_w, &
     737          630 :                                       grid_occupation=grid_occ)
     738              : 
     739          630 :             CALL dbcsr_release(matrix_G_occ_ao)
     740         1180 :             CALL dbcsr_release(matrix_G_vir_ao)
     741              : 
     742              :          END DO ! ispin
     743              : 
     744              :          ! Sparsity reports
     745          550 :          IF (i_t == 1) THEN
     746           40 :             CALL print_matrix_occupation(mat_Z_lP, 'Z_lP', bs_env)
     747           40 :             IF (bs_env%unit_nr > 0) THEN
     748              :                WRITE (bs_env%unit_nr, '(T2,A,T73,F7.2,A)') &
     749           20 :                   'Percentage of non-zero matrix elements in G_ll'', χ_ll'', W_ll''', &
     750           40 :                   grid_occ*100.0_dp, ' %'
     751           20 :                CALL m_flush(bs_env%unit_nr)
     752              :             END IF
     753              :             CALL print_matrix_occupation(mat_chi_Gamma_tau(i_t)%matrix, 'χ_PQ', bs_env, &
     754           40 :                                          suffix=' for time point 1')
     755           40 :             IF (bs_env%unit_nr > 0) WRITE (bs_env%unit_nr, '(A)') ' '
     756              :          END IF
     757              : 
     758          590 :          IF (bs_env%unit_nr > 0) THEN
     759              :             WRITE (bs_env%unit_nr, '(T2,A,I13,A,I3,A,F7.1,A)') &
     760          275 :                'Computed χ(iτ,k=0) for time point', i_t, ' /', bs_env%num_time_freq_points, &
     761          550 :                ',    Execution time', m_walltime() - t1, ' s'
     762              :          END IF
     763              : 
     764              :       END DO ! i_t
     765              : 
     766           40 :       IF (bs_env%unit_nr > 0) WRITE (bs_env%unit_nr, '(A)') ' '
     767              : 
     768           40 :       CALL timestop(handle)
     769              : 
     770           80 :    END SUBROUTINE get_mat_chi_Gamma_tau
     771              : 
     772              : ! **************************************************************************************************
     773              : !> \brief Marks the grid blocks whose centroid lies within cutoff of the bounding box of the
     774              : !>        panel [blk0, blk1]'s chunk centroids.
     775              : !> \param centroids ...
     776              : !> \param blk0 ...
     777              : !> \param blk1 ...
     778              : !> \param cutoff ...
     779              : !> \param used ...
     780              : ! **************************************************************************************************
     781           92 :    SUBROUTINE mask_grid_blocks_near_panel(centroids, blk0, blk1, cutoff, used)
     782              : 
     783              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: centroids
     784              :       INTEGER, INTENT(IN)                                :: blk0, blk1
     785              :       REAL(KIND=dp), INTENT(IN)                          :: cutoff
     786              :       LOGICAL, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: used
     787              : 
     788              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'mask_grid_blocks_near_panel'
     789              : 
     790              :       INTEGER                                            :: c, handle, k
     791              :       REAL(KIND=dp)                                      :: cutoff2, d2, dx
     792              :       REAL(KIND=dp), DIMENSION(3)                        :: hi, lo
     793              : 
     794           92 :       CALL timeset(routineN, handle)
     795              : 
     796           92 :       cutoff2 = cutoff**2
     797           92 :       lo(:) = MINVAL(centroids(:, blk0:blk1), DIM=2)
     798           92 :       hi(:) = MAXVAL(centroids(:, blk0:blk1), DIM=2)
     799              : 
     800          276 :       ALLOCATE (used(SIZE(centroids, 2)))
     801         2024 :       DO c = 1, SIZE(centroids, 2)
     802              :          d2 = 0.0_dp
     803         7728 :          DO k = 1, 3
     804         5796 :             dx = MAX(0.0_dp, lo(k) - centroids(k, c), centroids(k, c) - hi(k))
     805         7728 :             d2 = d2 + dx*dx
     806              :          END DO
     807         2024 :          used(c) = (d2 <= cutoff2)
     808              :       END DO
     809              : 
     810           92 :       CALL timestop(handle)
     811              : 
     812           92 :    END SUBROUTINE mask_grid_blocks_near_panel
     813              : 
     814              : ! **************************************************************************************************
     815              : !> \brief Exact allocated-element count of the geo template of panel [blk0, blk1]: the very same
     816              : !>        per-block-pair centroid test as build_geo_template_panel, so this is the true DBCSR
     817              : !>        data size of A_pan/B_pan/C_pan (DBCSR stores whole blocks).
     818              : !> \param r_blk_sizes ...
     819              : !> \param centroids ...
     820              : !> \param used ...
     821              : !> \param blk0 ...
     822              : !> \param blk1 ...
     823              : !> \param cutoff ...
     824              : !> \param nze_tmpl ...
     825              : ! **************************************************************************************************
     826            8 :    SUBROUTINE panel_template_elems(r_blk_sizes, centroids, used, blk0, blk1, cutoff, nze_tmpl)
     827              : 
     828              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: r_blk_sizes
     829              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: centroids
     830              :       LOGICAL, DIMENSION(:), INTENT(IN)                  :: used
     831              :       INTEGER, INTENT(IN)                                :: blk0, blk1
     832              :       REAL(KIND=dp), INTENT(IN)                          :: cutoff
     833              :       INTEGER(KIND=int_8), INTENT(OUT)                   :: nze_tmpl
     834              : 
     835              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'panel_template_elems'
     836              : 
     837              :       INTEGER                                            :: c, handle, ib, n_used
     838            8 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: used_idx
     839              :       REAL(KIND=dp)                                      :: cutoff2
     840              : 
     841            8 :       CALL timeset(routineN, handle)
     842              : 
     843              :       ! Compress the near mask once so the pair loop only visits candidate columns.
     844          176 :       n_used = COUNT(used)
     845           24 :       ALLOCATE (used_idx(n_used))
     846            8 :       n_used = 0
     847          176 :       DO c = 1, SIZE(used)
     848          176 :          IF (used(c)) THEN
     849          168 :             n_used = n_used + 1
     850          168 :             used_idx(n_used) = c
     851              :          END IF
     852              :       END DO
     853              : 
     854            8 :       cutoff2 = cutoff**2
     855            8 :       nze_tmpl = 0_int_8
     856              :       !$OMP PARALLEL DO DEFAULT(NONE) SHARED(blk0, blk1, n_used, used_idx, centroids, cutoff2, &
     857            8 :       !$OMP r_blk_sizes) PRIVATE(ib, c) REDUCTION(+:nze_tmpl)
     858              :       DO ib = blk0, blk1
     859              :          DO c = 1, n_used
     860              :             IF (SUM((centroids(:, ib) - centroids(:, used_idx(c)))**2) <= cutoff2) THEN
     861              :                nze_tmpl = nze_tmpl + INT(r_blk_sizes(ib), int_8)*INT(r_blk_sizes(used_idx(c)), int_8)
     862              :             END IF
     863              :          END DO
     864              :       END DO
     865              :       !$OMP END PARALLEL DO
     866              : 
     867            8 :       CALL timestop(handle)
     868              : 
     869           16 :    END SUBROUTINE panel_template_elems
     870              : 
     871              : ! **************************************************************************************************
     872              : !> \brief Per-rank peak memory (GB) of one panel step of the neighborhood-restricted
     873              : !>        contractions: three grid x grid panels of the template size (A_pan, B_pan, C_pan)
     874              : !>        plus the grid x RI intermediates (tmp2 and the accumulation operand) and the
     875              : !>        grid x AO intermediate (tmpA), whose column support is the panel's geometric
     876              : !>        neighborhood fraction f_near = width/n_grid. Shared by the panel planner and
     877              : !> \param nze_tmpl ...
     878              : !> \param pan_rows ...
     879              : !> \param width ...
     880              : !> \param n_grid_total ...
     881              : !> \param n_RI ...
     882              : !> \param n_ao ...
     883              : !> \param n_procs ...
     884              : !> \param mem_GB ...
     885              : ! **************************************************************************************************
     886            8 :    SUBROUTINE panel_mem_estimate_GB(nze_tmpl, pan_rows, width, n_grid_total, n_RI, n_ao, &
     887              :                                     n_procs, mem_GB)
     888              : 
     889              :       INTEGER(KIND=int_8), INTENT(IN)                    :: nze_tmpl
     890              :       INTEGER, INTENT(IN)                                :: pan_rows, width, n_grid_total, n_RI, &
     891              :                                                             n_ao, n_procs
     892              :       REAL(KIND=dp), INTENT(OUT)                         :: mem_GB
     893              : 
     894              :       REAL(KIND=dp)                                      :: f_near
     895              : 
     896            8 :       f_near = REAL(width, dp)/REAL(MAX(n_grid_total, 1), dp)
     897              :       mem_GB = (3.0_dp*REAL(nze_tmpl, dp) + &
     898              :                 REAL(pan_rows, dp)*f_near*(2.0_dp*REAL(n_RI, dp) + REAL(n_ao, dp)))* &
     899            8 :                8.0_dp/REAL(MAX(n_procs, 1), dp)*1.0E-9_dp
     900              : 
     901            8 :    END SUBROUTINE panel_mem_estimate_GB
     902              : 
     903              : ! **************************************************************************************************
     904              : !> \brief Plans the panel boundaries for the streaming contractions. Panels grow by whole grid
     905              : !>        row-blocks towards ~panel_size rows. When the neighborhood restriction is active
     906              : !>        (centroids+cutoff), each candidate panel is additionally checked against
     907              : !>          (a) the 32-bit message bound with the panel's TRUE occupancy
     908              : !>          (b) the per-rank memory budget: panel_mem_estimate_GB <= mem_budget_GB.
     909              : !> \param bs_env ...
     910              : !> \param r_blk_sizes ...
     911              : !> \param panel_size ...
     912              : !> \param min_dim ...
     913              : !> \param pan_first ...
     914              : !> \param pan_last ...
     915              : !> \param centroids ...
     916              : !> \param cutoff ...
     917              : !> \param n_RI ...
     918              : !> \param n_ao ...
     919              : !> \param n_procs ...
     920              : !> \param mem_budget_GB ...
     921              : !> \param honor_exact ...
     922              : !> \param unsafe ...
     923              : ! **************************************************************************************************
     924           40 :    SUBROUTINE plan_grid_panels(bs_env, r_blk_sizes, panel_size, min_dim, pan_first, pan_last, &
     925           40 :                                centroids, cutoff, n_RI, n_ao, n_procs, mem_budget_GB, &
     926              :                                honor_exact, unsafe)
     927              : 
     928              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     929              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: r_blk_sizes
     930              :       INTEGER, INTENT(IN)                                :: panel_size, min_dim
     931              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: pan_first, pan_last
     932              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN), &
     933              :          OPTIONAL                                        :: centroids
     934              :       REAL(KIND=dp), INTENT(IN), OPTIONAL                :: cutoff
     935              :       INTEGER, INTENT(IN), OPTIONAL                      :: n_RI, n_ao, n_procs
     936              :       REAL(KIND=dp), INTENT(IN), OPTIONAL                :: mem_budget_GB
     937              :       LOGICAL, INTENT(IN), OPTIONAL                      :: honor_exact
     938              :       LOGICAL, INTENT(OUT), OPTIONAL                     :: unsafe
     939              : 
     940              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'plan_grid_panels'
     941              : 
     942              :       INTEGER                                            :: blk0, blk1, handle, ib, n_grid_blocks, &
     943              :                                                             n_grid_total, n_panels, rows_acc, &
     944              :                                                             TARGET, width
     945              :       INTEGER(KIND=int_8)                                :: msg, nze_tmpl, side
     946           40 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: tmp_first, tmp_last
     947              :       LOGICAL                                            :: fits, my_honor_exact, my_unsafe, &
     948              :                                                             use_cutoff
     949           40 :       LOGICAL, ALLOCATABLE, DIMENSION(:)                 :: used
     950              :       REAL(KIND=dp)                                      :: f_near, mem_GB
     951              : 
     952           40 :       CALL timeset(routineN, handle)
     953              : 
     954           40 :       use_cutoff = PRESENT(centroids) .AND. PRESENT(cutoff)
     955           40 :       IF (use_cutoff) use_cutoff = cutoff > 0.0_dp
     956            2 :       IF (use_cutoff) THEN
     957            2 :          CPASSERT(PRESENT(n_RI) .AND. PRESENT(n_ao) .AND. PRESENT(n_procs))
     958              :       END IF
     959              : 
     960              :       ! honor_exact: use N_PANELS as requested -- do NOT split a panel further even if it trips
     961              :       ! the message-overflow / memory-budget check; instead flag `unsafe` so the caller can warn.
     962           40 :       my_honor_exact = .FALSE.
     963           40 :       IF (PRESENT(honor_exact)) my_honor_exact = honor_exact
     964           40 :       my_unsafe = .FALSE.
     965              : 
     966           40 :       n_grid_blocks = SIZE(r_blk_sizes)
     967          570 :       n_grid_total = SUM(r_blk_sizes)
     968          160 :       ALLOCATE (tmp_first(n_grid_blocks), tmp_last(n_grid_blocks))
     969              : 
     970           40 :       n_panels = 0
     971           40 :       blk0 = 1
     972           82 :       DO WHILE (blk0 <= n_grid_blocks)
     973           42 :          TARGET = panel_size
     974            0 :          DO
     975           42 :             rows_acc = 0
     976           42 :             blk1 = blk0
     977          532 :             DO ib = blk0, n_grid_blocks
     978          530 :                rows_acc = rows_acc + r_blk_sizes(ib)
     979          530 :                blk1 = ib
     980          532 :                IF (rows_acc >= TARGET) EXIT
     981              :             END DO
     982           42 :             IF (.NOT. use_cutoff .OR. blk1 == blk0) EXIT
     983            4 :             CALL mask_grid_blocks_near_panel(centroids, blk0, blk1, cutoff, used)
     984           88 :             width = SUM(r_blk_sizes, MASK=used)
     985            4 :             CALL panel_template_elems(r_blk_sizes, centroids, used, blk0, blk1, cutoff, nze_tmpl)
     986            4 :             f_near = REAL(width, dp)/REAL(MAX(n_grid_total, 1), dp)
     987            4 :             side = INT(REAL(rows_acc, dp)*f_near*REAL(MAX(n_RI, n_ao), dp), int_8)
     988            4 :             msg = MAX(nze_tmpl, side)/INT(MAX(min_dim, 1), int_8)
     989            4 :             fits = (msg <= bs_env%dbcsr_msg_elem_limit/4)
     990            4 :             IF (fits .AND. PRESENT(mem_budget_GB)) THEN
     991            4 :                IF (mem_budget_GB > 0.0_dp) THEN
     992              :                   CALL panel_mem_estimate_GB(nze_tmpl, rows_acc, width, n_grid_total, &
     993            4 :                                              n_RI, n_ao, n_procs, mem_GB)
     994            4 :                   fits = (mem_GB <= mem_budget_GB)
     995              :                END IF
     996              :             END IF
     997              :             ! Panel size is bounded only by the message-overflow and memory checks above; there is
     998              :             ! no neighborhood-width (f_near) cap. mp_waitall is dominated by the NUMBER of panel
     999              :             ! multiplies, so fewer/larger panels are cheaper here -- panel count is driven DOWN by
    1000              :             ! the N_PANELS keyword (panel_size), not split up by a width heuristic.
    1001            4 :             IF (my_honor_exact) THEN
    1002              :                ! Keep exactly the requested grouping; just record if it exceeds a safety limit.
    1003            4 :                IF (.NOT. fits) my_unsafe = .TRUE.
    1004              :                EXIT
    1005              :             END IF
    1006            0 :             IF (fits) EXIT
    1007           38 :             TARGET = MAX(1, MIN(TARGET, rows_acc)/2)
    1008              :          END DO
    1009           42 :          n_panels = n_panels + 1
    1010           42 :          tmp_first(n_panels) = blk0
    1011           42 :          tmp_last(n_panels) = blk1
    1012           42 :          blk0 = blk1 + 1
    1013              :       END DO
    1014              : 
    1015          160 :       ALLOCATE (pan_first(n_panels), pan_last(n_panels))
    1016           82 :       pan_first(:) = tmp_first(1:n_panels)
    1017           82 :       pan_last(:) = tmp_last(1:n_panels)
    1018           40 :       DEALLOCATE (tmp_first, tmp_last)
    1019              : 
    1020           40 :       IF (PRESENT(unsafe)) unsafe = my_unsafe
    1021              : 
    1022           40 :       CALL timestop(handle)
    1023              : 
    1024           80 :    END SUBROUTINE plan_grid_panels
    1025              : 
    1026              : ! **************************************************************************************************
    1027              : !> \brief Resolves the panel boundaries for the streaming contractions from the bs_env settings:
    1028              : !> \param bs_env ...
    1029              : !> \param mat_phi_mu_l ...
    1030              : !> \param pan_first ...
    1031              : !> \param pan_last ...
    1032              : ! **************************************************************************************************
    1033          174 :    SUBROUTINE resolve_grid_panels(bs_env, mat_phi_mu_l, pan_first, pan_last)
    1034              : 
    1035              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1036              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_phi_mu_l
    1037              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: pan_first, pan_last
    1038              : 
    1039              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'resolve_grid_panels'
    1040              : 
    1041              :       CHARACTER(LEN=max_line_length)                     :: msg
    1042              :       INTEGER                                            :: handle, min_dim, n_grid_total, &
    1043              :                                                             n_panels_req, npcols, nprows, &
    1044              :                                                             panel_size, safe_max
    1045          174 :       INTEGER, DIMENSION(:), POINTER                     :: r_blk_sizes
    1046              :       LOGICAL                                            :: honor_exact, panels_unsafe, use_cutoff
    1047              :       REAL(KIND=dp)                                      :: mem_avail_GB, mem_budget_GB
    1048              :       TYPE(dbcsr_distribution_type)                      :: dist
    1049              : 
    1050          174 :       CALL timeset(routineN, handle)
    1051              : 
    1052          174 :       IF (ALLOCATED(bs_env%ri_rs%pan_first)) THEN
    1053          542 :          ALLOCATE (pan_first, SOURCE=bs_env%ri_rs%pan_first)
    1054          542 :          ALLOCATE (pan_last, SOURCE=bs_env%ri_rs%pan_last)
    1055          134 :          CALL timestop(handle)
    1056              :          RETURN
    1057              :       END IF
    1058              : 
    1059              :       use_cutoff = bs_env%ri_rs%cutoff_radius_v_w > 0.0_dp .AND. &
    1060           40 :                    ALLOCATED(bs_env%ri_rs%chunk_centroids)
    1061              : 
    1062              :       ! MIN(nprows, npcols) is the divisor that bounds the worst-rank Cannon message: a
    1063              :       ! P x n_grid panel is replicated into block row strips (P/nprows x n_grid) or column
    1064              :       ! strips (P x n_grid/npcols) during multiply_cannon, so the largest single-rank
    1065              :       ! message is ~ P*n_grid / MIN(nprows,npcols) elements.
    1066              :       CALL dbcsr_get_info(mat_phi_mu_l, nfullrows_total=n_grid_total, row_blk_size=r_blk_sizes, &
    1067           40 :                           distribution=dist)
    1068           40 :       CALL dbcsr_distribution_get(dist, nprows=nprows, npcols=npcols)
    1069           40 :       min_dim = MAX(MIN(nprows, npcols), 1)
    1070              : 
    1071              :       ! Panel height such that NO per-rank DBCSR message can overflow the 32-bit length field
    1072              :       ! (see bs_env%dbcsr_msg_elem_limit): requiring the worst-rank message to stay under
    1073              :       ! 0.5 * HUGE(int_4) gives the safe height P_safe = 0.5 * HUGE(int_4) * min_dim / n_grid.
    1074           40 :       IF (use_cutoff) THEN
    1075            2 :          safe_max = n_grid_total
    1076              :       ELSE
    1077              :          safe_max = INT(0.5_dp*REAL(bs_env%dbcsr_msg_elem_limit, dp)*REAL(min_dim, dp)/ &
    1078           38 :                         REAL(n_grid_total, dp))
    1079           38 :          safe_max = MAX(1, MIN(safe_max, n_grid_total))
    1080              :       END IF
    1081              : 
    1082              :       ! A user-set N_PANELS ( > 1 ) is honored EXACTLY: the planner produces that many panels
    1083              :       ! (up to grid-block granularity) and never force-splits them for the message/memory safety
    1084              :       ! limits -- if a limit is tripped it warns instead of silently changing the count.
    1085           40 :       n_panels_req = bs_env%ri_rs%n_panels
    1086           40 :       honor_exact = (n_panels_req > 1)
    1087           40 :       panels_unsafe = .FALSE.
    1088           40 :       IF (n_panels_req > 1) THEN
    1089              :          ! ceil(n_grid/n_panels_req) rows per panel => exactly n_panels_req panels. With the
    1090              :          ! cutoff active safe_max = n_grid_total (no clamp, honored exactly); without it, safe_max
    1091              :          ! is the int32-overflow ceiling and MUST still bound the panel (the non-cutoff planner
    1092              :          ! loop has no in-loop message-size check).
    1093            2 :          panel_size = MIN((n_grid_total + n_panels_req - 1)/n_panels_req, safe_max)
    1094              :       ELSE
    1095              :          ! Default (<= 1): a single whole-grid panel, clamped to the overflow-safe ceiling.
    1096           38 :          panel_size = safe_max
    1097              :       END IF
    1098           40 :       panel_size = MAX(1, panel_size)
    1099              : 
    1100           40 :       IF (use_cutoff) THEN
    1101              :          ! Half of the measured free memory as panel budget.
    1102            2 :          CALL mp_mem_avail_per_rank_GB(bs_env%para_env, mem_avail_GB)
    1103            2 :          mem_budget_GB = 0.5_dp*mem_avail_GB
    1104              :          CALL plan_grid_panels(bs_env, r_blk_sizes, panel_size, min_dim, pan_first, pan_last, &
    1105              :                                centroids=bs_env%ri_rs%chunk_centroids, &
    1106              :                                cutoff=bs_env%ri_rs%cutoff_radius_v_w, &
    1107              :                                n_RI=bs_env%n_RI, n_ao=bs_env%n_ao, &
    1108              :                                n_procs=bs_env%para_env%num_pe, mem_budget_GB=mem_budget_GB, &
    1109            2 :                                honor_exact=honor_exact, unsafe=panels_unsafe)
    1110              :       ELSE
    1111           38 :          CALL plan_grid_panels(bs_env, r_blk_sizes, panel_size, min_dim, pan_first, pan_last)
    1112              :       END IF
    1113              : 
    1114           40 :       IF (honor_exact .AND. panels_unsafe) THEN
    1115              :          WRITE (msg, '(A,I0,A)') &
    1116            0 :             "N_PANELS = ", n_panels_req, " is used as requested, but one or more panels "// &
    1117              :             "exceed the DBCSR 32-bit message length or the memory budget. The run may abort "// &
    1118            0 :             "or swap; increase N_PANELS if it does."
    1119            0 :          CPWARN(TRIM(msg))
    1120              :       END IF
    1121              : 
    1122          162 :       ALLOCATE (bs_env%ri_rs%pan_first, SOURCE=pan_first)
    1123          162 :       ALLOCATE (bs_env%ri_rs%pan_last, SOURCE=pan_last)
    1124              : 
    1125           40 :       CALL timestop(handle)
    1126              : 
    1127          214 :    END SUBROUTINE resolve_grid_panels
    1128              : 
    1129              : ! **************************************************************************************************
    1130              : !> \brief Estimates and prints per-process memory requirements for the RI-RS GW calculation.
    1131              : !> \param bs_env ...
    1132              : ! **************************************************************************************************
    1133           40 :    SUBROUTINE print_ri_rs_memory_estimate(bs_env)
    1134              : 
    1135              : !$    USE OMP_LIB, ONLY: omp_get_max_threads
    1136              : 
    1137              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1138              : 
    1139              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'print_ri_rs_memory_estimate'
    1140              : 
    1141              :       CHARACTER(LEN=max_line_length)                     :: msg
    1142              :       INTEGER                                            :: handle, iatom, ipan, l, &
    1143              :                                                             max_n_ao_used, max_n_local_grid, &
    1144              :                                                             n_ao_used_atom, n_grid_total, &
    1145              :                                                             n_local_grid, n_loc_ri_max, n_procs, &
    1146              :                                                             n_procs_per_atom, n_RI, n_threads, &
    1147              :                                                             natom, pan_rows, pan_width
    1148              :       INTEGER(KIND=int_8)                                :: nze_tmpl
    1149           40 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: pan_first, pan_last
    1150           40 :       INTEGER, DIMENSION(:), POINTER                     :: r_blk_sizes
    1151              :       LOGICAL                                            :: use_cutoff
    1152           40 :       LOGICAL, ALLOCATABLE, DIMENSION(:)                 :: grid_used
    1153              :       REAL(KIND=dp)                                      :: cutoff_ri, mem_avail_GB, mem_D_local_GB, &
    1154              :                                                             mem_dlp_GB, mem_pan_GB, mem_panels_GB, &
    1155              :                                                             mem_phi_local_GB, mem_Z_lP_GB, &
    1156              :                                                             mem_Zlp_peak_GB, pos_P(3)
    1157           40 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
    1158              : 
    1159           40 :       CALL timeset(routineN, handle)
    1160              : 
    1161           40 :       n_grid_total = bs_env%ri_rs%n_grid_points
    1162           40 :       CALL dbcsr_get_info(bs_env%ri_rs%mat_phi_mu_l, row_blk_size=r_blk_sizes)
    1163          570 :       CPASSERT(SUM(r_blk_sizes) == n_grid_total)
    1164           40 :       n_RI = bs_env%n_RI
    1165           40 :       n_procs = bs_env%para_env%num_pe
    1166              : 
    1167              :       ! Z_lP upper bound: dense n_grid × n_RI, distributed evenly across all ranks.
    1168              :       ! The actual sparse Z_lP is smaller due to the per-atom locality cutoff.
    1169              :       mem_Z_lP_GB = REAL(n_grid_total, dp)*REAL(n_RI, dp)*8.0_dp/ &
    1170           40 :                     REAL(n_procs, dp)*1.0E-9_dp
    1171              : 
    1172              :       ! Peak panel memory during Σ^c: two G panels (A_occ, A_vir) + one W panel plus the
    1173              :       ! grid × RI / grid × AO intermediates. With the CUTOFF_RADIUS_RL_W restriction the panel
    1174              :       ! matrices only allocate the geo-template blocks, so use the same nze-aware model as the
    1175              :       ! panel planner (panel_mem_estimate_GB); without the cutoff, dense panel_rows × n_grid.
    1176              :       ! Plus the n_RI × n_RI W_aux matrix. Distributed over n_procs ranks.
    1177              :       use_cutoff = bs_env%ri_rs%cutoff_radius_v_w > 0.0_dp .AND. &
    1178           40 :                    ALLOCATED(bs_env%ri_rs%chunk_centroids)
    1179           40 :       CALL resolve_grid_panels(bs_env, bs_env%ri_rs%mat_phi_mu_l, pan_first, pan_last)
    1180           40 :       mem_panels_GB = 0.0_dp
    1181           82 :       DO ipan = 1, SIZE(pan_first)
    1182          572 :          pan_rows = SUM(r_blk_sizes(pan_first(ipan):pan_last(ipan)))
    1183           42 :          IF (use_cutoff) THEN
    1184              :             CALL mask_grid_blocks_near_panel(bs_env%ri_rs%chunk_centroids, pan_first(ipan), &
    1185              :                                              pan_last(ipan), bs_env%ri_rs%cutoff_radius_v_w, &
    1186            4 :                                              grid_used)
    1187           88 :             pan_width = SUM(r_blk_sizes, MASK=grid_used)
    1188              :             CALL panel_template_elems(r_blk_sizes, bs_env%ri_rs%chunk_centroids, &
    1189              :                                       grid_used, pan_first(ipan), pan_last(ipan), &
    1190            4 :                                       bs_env%ri_rs%cutoff_radius_v_w, nze_tmpl)
    1191              :             CALL panel_mem_estimate_GB(nze_tmpl, pan_rows, pan_width, n_grid_total, &
    1192            4 :                                        n_RI, bs_env%n_ao, n_procs, mem_pan_GB)
    1193              :          ELSE
    1194           38 :             pan_width = n_grid_total
    1195              :             mem_pan_GB = (3.0_dp*REAL(pan_rows, dp)*REAL(pan_width, dp) + &
    1196              :                           2.0_dp*REAL(pan_rows, dp)*REAL(n_RI, dp))* &
    1197           38 :                          8.0_dp/REAL(n_procs, dp)*1.0E-9_dp
    1198              :          END IF
    1199           82 :          mem_panels_GB = MAX(mem_panels_GB, mem_pan_GB)
    1200              :       END DO
    1201              :       mem_panels_GB = mem_panels_GB + &
    1202           40 :                       REAL(n_RI, dp)*REAL(n_RI, dp)*8.0_dp/REAL(n_procs, dp)*1.0E-9_dp
    1203              : 
    1204              :       ! Z_lP SOLVE peak (compute_Z_lP). For the atom P with the largest integration
    1205              :       ! sphere, one rank holds simultaneously:
    1206              :       !   D_local  : n_local_grid x n_local_grid  (dense D'_ll', BLAS path only; O(n_local_grid^2))
    1207              :       !   phi_local: n_local_grid x n_ao_used  (AOs reaching into the sphere only)
    1208              :       !   d_lp     : n_local_grid x n_loc_ri, replicated once + one private copy per OMP thread
    1209              :       ! n_local_grid = # grid points within cutoff_ri(P) = CUTOFF_RADIUS_RL_RI (if > 0) else
    1210              :       ! r_c(RI metric) + r_RI(P). This is NOT evenly distributed: n_local_grid depends on the
    1211              :       ! local density of atoms/grid, so the rank owning the densest atom peaks well above the
    1212              :       ! average. We report the worst-case (max over atoms) as a per-rank upper bound.
    1213           40 :       particle_set => bs_env%ri_rs%particle_set
    1214           40 :       CPASSERT(ASSOCIATED(particle_set))
    1215           40 :       natom = bs_env%n_atom
    1216              : 
    1217           40 :       max_n_local_grid = 0
    1218           40 :       n_loc_ri_max = 0
    1219           40 :       max_n_ao_used = 0
    1220          142 :       DO iatom = 1, natom
    1221          102 :          IF (bs_env%ri_rs%cutoff_radius_ri_rs > 0.0_dp) THEN
    1222            6 :             cutoff_ri = bs_env%ri_rs%cutoff_radius_ri_rs
    1223              :          ELSE
    1224           96 :             cutoff_ri = bs_env%ri_metric%cutoff_radius + bs_env%ri_rs%radius_ri_per_atom(iatom)
    1225              :          END IF
    1226          408 :          pos_P(:) = particle_set(iatom)%r(:)
    1227              :          n_local_grid = 0
    1228        37516 :          DO l = 1, n_grid_total
    1229       149758 :             IF (SUM((bs_env%ri_rs%grid_points(1:3, l) - pos_P(1:3))**2) <= cutoff_ri**2) THEN
    1230        35890 :                n_local_grid = n_local_grid + 1
    1231              :             END IF
    1232              :          END DO
    1233          102 :          max_n_local_grid = MAX(max_n_local_grid, n_local_grid)
    1234          102 :          CALL get_n_ao_in_sphere(bs_env, iatom, cutoff_ri, n_ao_used_atom)
    1235          102 :          max_n_ao_used = MAX(max_n_ao_used, n_ao_used_atom)
    1236              :          n_loc_ri_max = MAX(n_loc_ri_max, &
    1237          142 :                             bs_env%i_RI_end_from_atom(iatom) - bs_env%i_RI_start_from_atom(iatom) + 1)
    1238              :       END DO
    1239              : 
    1240           40 :       n_procs_per_atom = MIN(MAX(bs_env%ri_rs%n_procs_per_atom_z_lp, 1), n_procs)
    1241           40 :       n_threads = 1
    1242           40 : !$    n_threads = omp_get_max_threads()
    1243              : 
    1244              :       ! D_local: dense on one rank for the BLAS path; block-cyclic over the subgroup (=> /G) for
    1245              :       ! the ScaLAPACK path (N_PROCS_PER_ATOM_Z_LP = G > 1). phi_local/d_lp stay per-rank either way.
    1246           40 :       IF (n_procs_per_atom > 1) THEN
    1247            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
    1248              :       ELSE
    1249           40 :          mem_D_local_GB = REAL(max_n_local_grid, dp)**2*8.0_dp*1.0E-9_dp
    1250              :       END IF
    1251           40 :       mem_phi_local_GB = REAL(max_n_local_grid, dp)*REAL(max_n_ao_used, dp)*8.0_dp*1.0E-9_dp
    1252              :       mem_dlp_GB = REAL(max_n_local_grid, dp)*REAL(n_loc_ri_max, dp)*8.0_dp* &
    1253           40 :                    REAL(1 + n_threads, dp)*1.0E-9_dp
    1254           40 :       mem_Zlp_peak_GB = mem_D_local_GB + mem_phi_local_GB + mem_dlp_GB
    1255              : 
    1256              :       ! Available memory per process = node MemLikelyFree / ranks-per-node, min across ranks
    1257              :       ! (0 on non-Linux => warnings suppressed below).
    1258           40 :       CALL mp_mem_avail_per_rank_GB(bs_env%para_env, mem_avail_GB)
    1259              : 
    1260           40 :       IF (bs_env%unit_nr > 0) THEN
    1261           20 :          WRITE (bs_env%unit_nr, '(A)') ' '
    1262           20 :          WRITE (bs_env%unit_nr, '(T2,A)') 'RI-RS memory estimate per MPI process:'
    1263              :          WRITE (bs_env%unit_nr, '(T4,A,F37.2,A)') &
    1264           20 :             'Available memory per process (system)', mem_avail_GB, ' GB'
    1265              :          WRITE (bs_env%unit_nr, '(T4,A,F18.2,A)') &
    1266           20 :             'Required for Z_lP (dense upper bound; actual is sparser)', mem_Z_lP_GB, ' GB'
    1267              :          WRITE (bs_env%unit_nr, '(T4,A,F25.2,A)') &
    1268           20 :             'Required for χ, W, Σ panels (peak per panel step)', mem_panels_GB, ' GB'
    1269              :          WRITE (bs_env%unit_nr, '(T4,A,F17.2,A)') &
    1270           20 :             'Required for Z_lP solve peak (D_local+ϕ, worst-case atom)', mem_Zlp_peak_GB, ' GB'
    1271              :          WRITE (bs_env%unit_nr, '(T4,A,T69,I12)') &
    1272           20 :             'Worst-case local-grid number of grid points:', max_n_local_grid
    1273              :          WRITE (bs_env%unit_nr, '(T4,A,T69,F9.2,A)') &
    1274           20 :             'Worst-case memory D_local:', mem_D_local_GB, ' GB'
    1275              : 
    1276              :       END IF
    1277              : 
    1278           40 :       IF (mem_avail_GB > 0.0_dp .AND. mem_Z_lP_GB > mem_avail_GB) THEN
    1279              :          WRITE (msg, '(A,F0.2,A,F0.2,A)') &
    1280            0 :             "The estimated memory for Z_lP, ", mem_Z_lP_GB, " GB per process, exceeds the "// &
    1281            0 :             "available ", mem_avail_GB, " GB. Z_lP (n_grid x n_RI) is distributed across all "// &
    1282              :             "MPI ranks, so add nodes, use fewer MPI ranks per node, or raise "// &
    1283              :             "N_PROCS_PER_ATOM_Z_LP to distribute each atom block via ScaLAPACK, which reduces "// &
    1284            0 :             "the per-rank memory roughly by the number of ranks per atom."
    1285            0 :          CPWARN(TRIM(msg))
    1286              :       END IF
    1287              : 
    1288           40 :       IF (mem_avail_GB > 0.0_dp .AND. mem_panels_GB > mem_avail_GB) THEN
    1289              :          WRITE (msg, '(A,F0.2,A,F0.2,A)') &
    1290            0 :             "The estimated peak memory of the chi/W/Sigma panels, ", mem_panels_GB, &
    1291            0 :             " GB per process, exceeds the available ", mem_avail_GB, " GB. Panel memory "// &
    1292              :             "scales roughly as 3*panel_size*n_grid/n_procs, so add nodes, use fewer MPI ranks "// &
    1293            0 :             "per node, or raise N_PANELS for more but smaller panels."
    1294            0 :          CPWARN(TRIM(msg))
    1295              :       END IF
    1296              : 
    1297           40 :       IF (mem_avail_GB > 0.0_dp .AND. mem_Zlp_peak_GB > mem_avail_GB) THEN
    1298              :          WRITE (msg, '(A,F0.2,A,F0.2,A)') &
    1299            0 :             "The estimated peak memory of the Z_lP solve, ", mem_Zlp_peak_GB, &
    1300            0 :             " GB per process, exceeds the available ", mem_avail_GB, &
    1301              :             " GB. The per-atom matrix D'_ll' dominates and "// &
    1302              :             "scales as n_local_grid^2, and it is not balanced across ranks: the rank owning "// &
    1303              :             "the atom with the largest integration sphere peaks well above the average. "// &
    1304              :             "Either raise N_PROCS_PER_ATOM_Z_LP to distribute D_local block-cyclic via "// &
    1305              :             "ScaLAPACK, which reduces that term roughly by the number of ranks per atom at no "// &
    1306              :             "loss of accuracy, or lower CUTOFF_RADIUS_RL_RI, which shrinks D_local as "// &
    1307              :             "n_local_grid^2 but trades accuracy, or use fewer MPI ranks per node so that each "// &
    1308            0 :             "rank has more memory for the peak atom."
    1309            0 :          CPWARN(TRIM(msg))
    1310              :       END IF
    1311              : 
    1312           40 :       CALL timestop(handle)
    1313              : 
    1314           80 :    END SUBROUTINE print_ri_rs_memory_estimate
    1315              : 
    1316              : ! **************************************************************************************************
    1317              : !> \brief Creates an empty (panel_chunks x neighborhood_chunks) DBCSR matrix with zero blocks
    1318              : !>        pre-allocated only where |centroid(panel_row r) - centroid(column c)| <= cutoff.
    1319              : !>        Used with retain_sparsity=.TRUE. in the subsequent dbcsr_multiply so distant blocks
    1320              : !>        of the grid-basis panels (ϕ G ϕ^T, Z W Z^T, ...) are never computed at all.
    1321              : !> \param L_pan ...
    1322              : !> \param L_full ...
    1323              : !> \param centroids ...
    1324              : !> \param cutoff ...
    1325              : !> \param blk0 ...
    1326              : !> \param A_template ...
    1327              : !> \param col_map ...
    1328              : ! **************************************************************************************************
    1329          124 :    SUBROUTINE build_geo_template_panel(L_pan, L_full, centroids, cutoff, blk0, A_template, col_map)
    1330              :       TYPE(dbcsr_type), INTENT(IN)                       :: L_pan, L_full
    1331              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: centroids
    1332              :       REAL(KIND=dp), INTENT(IN)                          :: cutoff
    1333              :       INTEGER, INTENT(IN)                                :: blk0
    1334              :       TYPE(dbcsr_type), INTENT(OUT)                      :: A_template
    1335              :       INTEGER, DIMENSION(:), INTENT(IN), OPTIONAL        :: col_map
    1336              : 
    1337              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'build_geo_template_panel'
    1338              : 
    1339              :       INTEGER                                            :: c, cg, cs, handle, my_pcol, my_prow, &
    1340              :                                                             n_grid_blks, n_pan_blks, npcols, &
    1341              :                                                             nprows, r, rs
    1342          124 :       INTEGER, DIMENSION(:), POINTER                     :: grid_blk_sizes, pan_blk_sizes
    1343              :       REAL(KIND=dp)                                      :: cutoff2
    1344          124 :       REAL(KIND=dp), ALLOCATABLE                         :: zero_blk(:, :)
    1345              :       TYPE(dbcsr_distribution_type)                      :: dist
    1346              : 
    1347          124 :       CALL timeset(routineN, handle)
    1348              : 
    1349          124 :       cutoff2 = cutoff**2
    1350          124 :       CALL dbcsr_get_info(L_pan, nblkrows_total=n_pan_blks, row_blk_size=pan_blk_sizes)
    1351          124 :       CALL dbcsr_get_info(L_full, nblkrows_total=n_grid_blks, row_blk_size=grid_blk_sizes)
    1352              : 
    1353              :       ! create_product_matrix assigns row r to process MOD(r-1,nprows) and
    1354              :       ! col c to MOD(c-1,npcols), so we can determine local ownership analytically.
    1355          124 :       CALL create_product_matrix(L_pan, L_full, 'N', 'T', A_template)
    1356          124 :       CALL dbcsr_get_info(A_template, distribution=dist)
    1357              :       CALL dbcsr_distribution_get(dist, nprows=nprows, npcols=npcols, &
    1358          124 :                                   myprow=my_prow, mypcol=my_pcol)
    1359              : 
    1360              :       ALLOCATE (zero_blk(MAXVAL(pan_blk_sizes(1:n_pan_blks)), &
    1361         4402 :                          MAXVAL(grid_blk_sizes(1:n_grid_blks))))
    1362          124 :       zero_blk(:, :) = 0.0_dp
    1363              : 
    1364         1426 :       DO r = 1, n_pan_blks
    1365         1302 :          IF (MOD(r - 1, nprows) /= my_prow) CYCLE
    1366          651 :          rs = pan_blk_sizes(r)
    1367        14446 :          DO c = 1, n_grid_blks
    1368        13671 :             IF (MOD(c - 1, npcols) /= my_pcol) CYCLE
    1369        13671 :             cg = c
    1370        13671 :             IF (PRESENT(col_map)) cg = col_map(c)
    1371        13671 :             IF ((centroids(1, blk0 + r - 1) - centroids(1, cg))**2 + &
    1372              :                 (centroids(2, blk0 + r - 1) - centroids(2, cg))**2 + &
    1373         1302 :                 (centroids(3, blk0 + r - 1) - centroids(3, cg))**2 <= cutoff2) THEN
    1374        12741 :                cs = grid_blk_sizes(c)
    1375        12741 :                CALL dbcsr_put_block(A_template, r, c, zero_blk(1:rs, 1:cs))
    1376              :             END IF
    1377              :          END DO
    1378              :       END DO
    1379          124 :       CALL dbcsr_finalize(A_template)
    1380              : 
    1381          124 :       DEALLOCATE (zero_blk)
    1382          124 :       CALL timestop(handle)
    1383              : 
    1384          124 :    END SUBROUTINE build_geo_template_panel
    1385              : 
    1386              : ! **************************************************************************************************
    1387              : !> \brief Slices a contiguous range of grid row-blocks [blk0, blk1] out of a (grid x n) DBCSR
    1388              : !>        matrix into a new (P x n) panel matrix: iterate the source's local blocks, put the
    1389              : !>        in-range ones into the panel with a remapped row-block index, then finalize. Row-block
    1390              : !>        index i of the panel corresponds to source row-block blk0+i-1.
    1391              : !> \param mat_full ...
    1392              : !> \param blk0 ...
    1393              : !> \param blk1 ...
    1394              : !> \param mat_panel ...
    1395              : ! **************************************************************************************************
    1396         3096 :    SUBROUTINE extract_grid_panel(mat_full, blk0, blk1, mat_panel)
    1397              : 
    1398              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_full
    1399              :       INTEGER, INTENT(IN)                                :: blk0, blk1
    1400              :       TYPE(dbcsr_type), INTENT(OUT)                      :: mat_panel
    1401              : 
    1402              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'extract_grid_panel'
    1403              : 
    1404              :       INTEGER                                            :: handle, ib, jb, npb
    1405         3096 :       INTEGER, DIMENSION(:), POINTER                     :: col_blk_full, col_dist_full, &
    1406         3096 :                                                             row_blk_full, row_blk_pan, &
    1407         3096 :                                                             row_dist_full, row_dist_pan
    1408         3096 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: blk
    1409              :       TYPE(dbcsr_distribution_type)                      :: dist_full, dist_pan
    1410              :       TYPE(dbcsr_iterator_type)                          :: iter
    1411              : 
    1412         3096 :       CALL timeset(routineN, handle)
    1413              : 
    1414              :       CALL dbcsr_get_info(mat_full, distribution=dist_full, &
    1415         3096 :                           row_blk_size=row_blk_full, col_blk_size=col_blk_full)
    1416         3096 :       CALL dbcsr_distribution_get(dist_full, row_dist=row_dist_full, col_dist=col_dist_full)
    1417              : 
    1418         3096 :       npb = blk1 - blk0 + 1
    1419        12384 :       ALLOCATE (row_dist_pan(npb), row_blk_pan(npb))
    1420        92520 :       row_dist_pan(:) = row_dist_full(blk0:blk1)
    1421        92520 :       row_blk_pan(:) = row_blk_full(blk0:blk1)
    1422              : 
    1423              :       CALL dbcsr_distribution_new(dist_pan, template=dist_full, &
    1424         3096 :                                   row_dist=row_dist_pan, col_dist=col_dist_full)
    1425              :       CALL dbcsr_create(mat_panel, name="grid_panel", dist=dist_pan, &
    1426              :                         matrix_type=dbcsr_type_no_symmetry, &
    1427         3096 :                         row_blk_size=row_blk_pan, col_blk_size=col_blk_full)
    1428              : 
    1429         3096 :       CALL dbcsr_iterator_start(iter, mat_full)
    1430        56256 :       DO WHILE (dbcsr_iterator_blocks_left(iter))
    1431        53160 :          CALL dbcsr_iterator_next_block(iter, ib, jb, blk)
    1432        53160 :          IF (ib < blk0 .OR. ib > blk1) CYCLE
    1433        56256 :          CALL dbcsr_put_block(mat_panel, ib - blk0 + 1, jb, blk)
    1434              :       END DO
    1435         3096 :       CALL dbcsr_iterator_stop(iter)
    1436         3096 :       CALL dbcsr_finalize(mat_panel)
    1437              : 
    1438         3096 :       CALL dbcsr_distribution_release(dist_pan)
    1439         3096 :       DEALLOCATE (row_dist_pan, row_blk_pan)
    1440              : 
    1441         3096 :       CALL timestop(handle)
    1442              : 
    1443         9288 :    END SUBROUTINE extract_grid_panel
    1444              : 
    1445              : ! **************************************************************************************************
    1446              : !> \brief Marks which column blocks of a DBCSR matrix carry at least one non-zero block anywhere
    1447              : !>        (global union). Used to restrict the inner index of the panel multiplies to the
    1448              : !>        AO/RI atoms that actually touch the panel (exact: dropped rows only meet zeros).
    1449              : !> \param matrix ...
    1450              : !> \param para_env ...
    1451              : !> \param used ...
    1452              : ! **************************************************************************************************
    1453         2446 :    SUBROUTINE collect_used_col_blocks(matrix, para_env, used)
    1454              : 
    1455              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: matrix
    1456              :       TYPE(mp_para_env_type), INTENT(IN), POINTER        :: para_env
    1457              :       LOGICAL, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: used
    1458              : 
    1459              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'collect_used_col_blocks'
    1460              : 
    1461              :       INTEGER                                            :: handle, ib, jb, nblkcols
    1462              :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: iused
    1463         2446 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: blk
    1464              :       TYPE(dbcsr_iterator_type)                          :: iter
    1465              : 
    1466         2446 :       CALL timeset(routineN, handle)
    1467              : 
    1468         2446 :       CALL dbcsr_get_info(matrix, nblkcols_total=nblkcols)
    1469         7338 :       ALLOCATE (iused(nblkcols))
    1470         2446 :       iused(:) = 0
    1471              : 
    1472         2446 :       CALL dbcsr_iterator_start(iter, matrix)
    1473        43679 :       DO WHILE (dbcsr_iterator_blocks_left(iter))
    1474        41233 :          CALL dbcsr_iterator_next_block(iter, ib, jb, blk)
    1475        41233 :          iused(jb) = 1
    1476              :       END DO
    1477         2446 :       CALL dbcsr_iterator_stop(iter)
    1478              : 
    1479         2446 :       CALL para_env%sum(iused)
    1480              : 
    1481         4892 :       ALLOCATE (used(nblkcols))
    1482         8296 :       used(:) = (iused(:) > 0)
    1483         2446 :       DEALLOCATE (iused)
    1484              : 
    1485         2446 :       CALL timestop(handle)
    1486              : 
    1487         4892 :    END SUBROUTINE collect_used_col_blocks
    1488              : 
    1489              : ! **************************************************************************************************
    1490              : !> \brief Copies the flagged block rows (compress_rows=.TRUE.) or block columns (.FALSE.) of a
    1491              : !>        DBCSR matrix into a compressed matrix. The subset keeps the parent's process assignment
    1492              : !>        along the compressed dimension, so every block stays on its owning rank: the extraction
    1493              : !>        is purely local (zero communication), like extract_grid_panel.
    1494              : !> \param mat_full ...
    1495              : !> \param used ...
    1496              : !> \param mat_out ...
    1497              : !> \param compress_rows ...
    1498              : !> \param blk_map ...
    1499              : ! **************************************************************************************************
    1500         6560 :    SUBROUTINE extract_masked_blocks(mat_full, used, mat_out, compress_rows, blk_map)
    1501              : 
    1502              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_full
    1503              :       LOGICAL, DIMENSION(:), INTENT(IN)                  :: used
    1504              :       TYPE(dbcsr_type), INTENT(OUT)                      :: mat_out
    1505              :       LOGICAL, INTENT(IN)                                :: compress_rows
    1506              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT), &
    1507              :          OPTIONAL                                        :: blk_map
    1508              : 
    1509              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'extract_masked_blocks'
    1510              : 
    1511              :       INTEGER                                            :: handle, ib, jb, n_blk, n_sub, r
    1512         6560 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: inv_map
    1513         6560 :       INTEGER, DIMENSION(:), POINTER                     :: blk_full, blk_sub, col_blk_full, &
    1514         6560 :                                                             col_dist_full, dist_full_1d, &
    1515         6560 :                                                             dist_sub_1d, row_blk_full, &
    1516         6560 :                                                             row_dist_full
    1517         6560 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: blk
    1518              :       TYPE(dbcsr_distribution_type)                      :: dist_full, dist_sub
    1519              :       TYPE(dbcsr_iterator_type)                          :: iter
    1520              : 
    1521         6560 :       CALL timeset(routineN, handle)
    1522              : 
    1523              :       CALL dbcsr_get_info(mat_full, distribution=dist_full, &
    1524         6560 :                           row_blk_size=row_blk_full, col_blk_size=col_blk_full)
    1525         6560 :       CALL dbcsr_distribution_get(dist_full, row_dist=row_dist_full, col_dist=col_dist_full)
    1526              : 
    1527         6560 :       IF (compress_rows) THEN
    1528         4114 :          blk_full => row_blk_full
    1529         4114 :          dist_full_1d => row_dist_full
    1530              :       ELSE
    1531         2446 :          blk_full => col_blk_full
    1532         2446 :          dist_full_1d => col_dist_full
    1533              :       END IF
    1534         6560 :       n_blk = SIZE(blk_full)
    1535              : 
    1536        25368 :       n_sub = COUNT(used)
    1537         6560 :       CPASSERT(n_sub > 0)
    1538        39360 :       ALLOCATE (inv_map(n_blk), blk_sub(n_sub), dist_sub_1d(n_sub))
    1539         6644 :       IF (PRESENT(blk_map)) ALLOCATE (blk_map(n_sub))
    1540         6560 :       inv_map(:) = 0
    1541         6560 :       r = 0
    1542        25368 :       DO ib = 1, n_blk
    1543        25368 :          IF (used(ib)) THEN
    1544        18808 :             r = r + 1
    1545        18808 :             inv_map(ib) = r
    1546        18808 :             blk_sub(r) = blk_full(ib)
    1547        18808 :             dist_sub_1d(r) = dist_full_1d(ib)
    1548        18808 :             IF (PRESENT(blk_map)) blk_map(r) = ib
    1549              :          END IF
    1550              :       END DO
    1551              : 
    1552         6560 :       IF (compress_rows) THEN
    1553              :          CALL dbcsr_distribution_new(dist_sub, template=dist_full, &
    1554         4114 :                                      row_dist=dist_sub_1d, col_dist=col_dist_full)
    1555              :          CALL dbcsr_create(mat_out, name="row_subset", dist=dist_sub, &
    1556              :                            matrix_type=dbcsr_type_no_symmetry, &
    1557         4114 :                            row_blk_size=blk_sub, col_blk_size=col_blk_full)
    1558              :       ELSE
    1559              :          CALL dbcsr_distribution_new(dist_sub, template=dist_full, &
    1560         2446 :                                      row_dist=row_dist_full, col_dist=dist_sub_1d)
    1561              :          CALL dbcsr_create(mat_out, name="col_subset", dist=dist_sub, &
    1562              :                            matrix_type=dbcsr_type_no_symmetry, &
    1563         2446 :                            row_blk_size=row_blk_full, col_blk_size=blk_sub)
    1564              :       END IF
    1565              : 
    1566         6560 :       CALL dbcsr_iterator_start(iter, mat_full)
    1567        63814 :       DO WHILE (dbcsr_iterator_blocks_left(iter))
    1568        57254 :          CALL dbcsr_iterator_next_block(iter, ib, jb, blk)
    1569        63814 :          IF (compress_rows) THEN
    1570        16021 :             IF (inv_map(ib) > 0) CALL dbcsr_put_block(mat_out, inv_map(ib), jb, blk)
    1571              :          ELSE
    1572        41233 :             IF (inv_map(jb) > 0) CALL dbcsr_put_block(mat_out, ib, inv_map(jb), blk)
    1573              :          END IF
    1574              :       END DO
    1575         6560 :       CALL dbcsr_iterator_stop(iter)
    1576         6560 :       CALL dbcsr_finalize(mat_out)
    1577              : 
    1578         6560 :       CALL dbcsr_distribution_release(dist_sub)
    1579         6560 :       DEALLOCATE (inv_map, blk_sub, dist_sub_1d)
    1580              : 
    1581         6560 :       CALL timestop(handle)
    1582              : 
    1583        13120 :    END SUBROUTINE extract_masked_blocks
    1584              : 
    1585              : ! **************************************************************************************************
    1586              : !> \brief Pre-seeds a square blocked DBCSR matrix with zero blocks only for block pairs whose
    1587              : !>        centers lie within radius, for use with copy_fm_to_dbcsr(keep_sparsity=T) or
    1588              : !>        dbcsr_multiply(retain_sparsity=T). Consumers: the CUTOFF_RADIUS_G_W operator truncation
    1589              : !>        (atom-blocked, centers = atom_centers) and the RT-BSE CUTOFF_RADIUS_W0 truncation of the
    1590              : !>        grid-basis W^0 (grid-blocked, centers = chunk_centroids).
    1591              : !> \param matrix ...
    1592              : !> \param centers block positions, one column per block row/column of matrix
    1593              : !> \param radius truncation radius, same units as centers (bohr)
    1594              : ! **************************************************************************************************
    1595            6 :    SUBROUTINE reserve_blocks_within_radius(matrix, centers, radius)
    1596              : 
    1597              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: matrix
    1598              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: centers
    1599              :       REAL(KIND=dp), INTENT(IN)                          :: radius
    1600              : 
    1601              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'reserve_blocks_within_radius'
    1602              : 
    1603              :       INTEGER                                            :: handle, i, j, my_pcol, my_prow, &
    1604              :                                                             nblkcols, nblkrows
    1605            6 :       INTEGER, DIMENSION(:), POINTER                     :: col_blk, col_dist, row_blk, row_dist
    1606              :       REAL(KIND=dp)                                      :: radius2
    1607            6 :       REAL(KIND=dp), ALLOCATABLE                         :: zero_blk(:, :)
    1608              :       TYPE(dbcsr_distribution_type)                      :: dist
    1609              : 
    1610            6 :       CALL timeset(routineN, handle)
    1611              : 
    1612              :       CALL dbcsr_get_info(matrix, nblkrows_total=nblkrows, nblkcols_total=nblkcols, &
    1613            6 :                           row_blk_size=row_blk, col_blk_size=col_blk, distribution=dist)
    1614              :       CALL dbcsr_distribution_get(dist, row_dist=row_dist, col_dist=col_dist, &
    1615            6 :                                   myprow=my_prow, mypcol=my_pcol)
    1616            6 :       CPASSERT(nblkrows == SIZE(centers, 2))
    1617            6 :       CPASSERT(nblkcols == SIZE(centers, 2))
    1618              : 
    1619            6 :       radius2 = radius**2
    1620          144 :       ALLOCATE (zero_blk(MAXVAL(row_blk(1:nblkrows)), MAXVAL(col_blk(1:nblkcols))))
    1621            6 :       zero_blk(:, :) = 0.0_dp
    1622              : 
    1623           66 :       DO i = 1, nblkrows
    1624           60 :          IF (row_dist(i) /= my_prow) CYCLE
    1625          336 :          DO j = 1, nblkcols
    1626          300 :             IF (col_dist(j) /= my_pcol) CYCLE
    1627          300 :             IF ((centers(1, i) - centers(1, j))**2 + (centers(2, i) - centers(2, j))**2 + &
    1628           60 :                 (centers(3, i) - centers(3, j))**2 <= radius2) THEN
    1629          168 :                CALL dbcsr_put_block(matrix, i, j, zero_blk(1:row_blk(i), 1:col_blk(j)))
    1630              :             END IF
    1631              :          END DO
    1632              :       END DO
    1633            6 :       CALL dbcsr_finalize(matrix)
    1634              : 
    1635            6 :       DEALLOCATE (zero_blk)
    1636            6 :       CALL timestop(handle)
    1637              : 
    1638            6 :    END SUBROUTINE reserve_blocks_within_radius
    1639              : 
    1640              : ! **************************************************************************************************
    1641              : !> \brief Creates the (empty) result matrix of op(mat_left) * op(mat_right) with the correct block
    1642              : !>        structure and a distribution on the shared process grid, ready to be filled by
    1643              : !>        dbcsr_multiply. Row structure comes from op(left), column structure from op(right).
    1644              : !> \param mat_left ...
    1645              : !> \param mat_right ...
    1646              : !> \param transa 'N' or 'T' applied to mat_left
    1647              : !> \param transb 'N' or 'T' applied to mat_right
    1648              : !> \param mat_out ...
    1649              : ! **************************************************************************************************
    1650         8742 :    SUBROUTINE create_product_matrix(mat_left, mat_right, transa, transb, mat_out)
    1651              : 
    1652              :       TYPE(dbcsr_type), INTENT(IN)                       :: mat_left, mat_right
    1653              :       CHARACTER(LEN=1), INTENT(IN)                       :: transa, transb
    1654              :       TYPE(dbcsr_type), INTENT(OUT)                      :: mat_out
    1655              : 
    1656              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'create_product_matrix'
    1657              : 
    1658              :       INTEGER                                            :: handle, i, npcols, nprows
    1659         8742 :       INTEGER, DIMENSION(:), POINTER                     :: col_blk_l, col_blk_r, out_col_blk, &
    1660         8742 :                                                             out_col_dist, out_row_blk, &
    1661         8742 :                                                             out_row_dist, row_blk_l, row_blk_r
    1662              :       TYPE(dbcsr_distribution_type)                      :: dist_l, dist_out
    1663              : 
    1664         8742 :       CALL timeset(routineN, handle)
    1665              : 
    1666         8742 :       CALL dbcsr_get_info(mat_left, distribution=dist_l, row_blk_size=row_blk_l, col_blk_size=col_blk_l)
    1667         8742 :       CALL dbcsr_get_info(mat_right, row_blk_size=row_blk_r, col_blk_size=col_blk_r)
    1668         8742 :       CALL dbcsr_distribution_get(dist_l, nprows=nprows, npcols=npcols)
    1669              : 
    1670              :       ! block SIZES follow op(left)/op(right); DISTRIBUTIONS are freshly round-robined onto the
    1671              :       ! shared process grid (a transposed operand's row-dist is NOT a valid col-dist on a
    1672              :       ! non-square grid). dbcsr_multiply redistributes internally, so any valid mapping works.
    1673         8742 :       IF (transa == 'N') THEN
    1674         8742 :          out_row_blk => row_blk_l
    1675              :       ELSE
    1676            0 :          out_row_blk => col_blk_l
    1677              :       END IF
    1678         8742 :       IF (transb == 'N') THEN
    1679         6344 :          out_col_blk => col_blk_r
    1680              :       ELSE
    1681         2398 :          out_col_blk => row_blk_r
    1682              :       END IF
    1683              : 
    1684        43710 :       ALLOCATE (out_row_dist(SIZE(out_row_blk)), out_col_dist(SIZE(out_col_blk)))
    1685       132960 :       DO i = 1, SIZE(out_row_blk)
    1686       132960 :          out_row_dist(i) = MOD(i - 1, nprows)
    1687              :       END DO
    1688        59416 :       DO i = 1, SIZE(out_col_blk)
    1689        59416 :          out_col_dist(i) = MOD(i - 1, npcols)
    1690              :       END DO
    1691              : 
    1692              :       CALL dbcsr_distribution_new(dist_out, template=dist_l, &
    1693         8742 :                                   row_dist=out_row_dist, col_dist=out_col_dist)
    1694              :       CALL dbcsr_create(mat_out, name="panel_product", dist=dist_out, &
    1695              :                         matrix_type=dbcsr_type_no_symmetry, &
    1696         8742 :                         row_blk_size=out_row_blk, col_blk_size=out_col_blk)
    1697         8742 :       CALL dbcsr_distribution_release(dist_out)
    1698         8742 :       DEALLOCATE (out_row_dist, out_col_dist)
    1699              : 
    1700         8742 :       CALL timestop(handle)
    1701              : 
    1702        26226 :    END SUBROUTINE create_product_matrix
    1703              : 
    1704              : ! **************************************************************************************************
    1705              : !> \brief Builds the AO-space Green's function operator G^occ/vir_µν (AO x AO DBCSR)
    1706              : !> \param bs_env ...
    1707              : !> \param tau ...
    1708              : !> \param ispin ...
    1709              : !> \param occ ...
    1710              : !> \param vir ...
    1711              : !> \param template ...
    1712              : !> \param matrix_G_ao ...
    1713              : ! **************************************************************************************************
    1714         5932 :    SUBROUTINE build_G_ao(bs_env, tau, ispin, occ, vir, template, matrix_G_ao)
    1715              : 
    1716              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1717              :       REAL(KIND=dp), INTENT(IN)                          :: tau
    1718              :       INTEGER, INTENT(IN)                                :: ispin
    1719              :       LOGICAL, INTENT(IN)                                :: occ, vir
    1720              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: template
    1721              :       TYPE(dbcsr_type), INTENT(OUT)                      :: matrix_G_ao
    1722              : 
    1723              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'build_G_ao'
    1724              : 
    1725              :       INTEGER                                            :: handle
    1726         2966 :       INTEGER, DIMENSION(:), POINTER                     :: blk_ao, dist_row_ao
    1727              :       TYPE(cp_fm_type), POINTER                          :: fm_G
    1728              :       TYPE(dbcsr_distribution_type)                      :: dist_ao_ao
    1729              : 
    1730         2966 :       CALL timeset(routineN, handle)
    1731              : 
    1732         2966 :       IF (occ) THEN
    1733         1506 :          fm_G => bs_env%fm_Gocc
    1734              :       ELSE
    1735         1460 :          fm_G => bs_env%fm_Gvir
    1736              :       END IF
    1737              : 
    1738         2966 :       CALL G_occ_vir(bs_env, tau, fm_G, ispin, occ=occ, vir=vir)
    1739              : 
    1740         2966 :       CALL setup_square_topology(template, dist_ao_ao, blk_ao, dist_row_ao)
    1741              :       CALL dbcsr_create(matrix_G_ao, name="G_ao", dist=dist_ao_ao, &
    1742              :                         matrix_type=dbcsr_type_no_symmetry, &
    1743         2966 :                         row_blk_size=blk_ao, col_blk_size=blk_ao)
    1744              : 
    1745              :       ! Optional CUTOFF_RADIUS_G_W operator truncation: only atom-pair blocks within the radius
    1746              :       ! are reserved and filled.
    1747         2966 :       IF (bs_env%ri_rs%cutoff_radius_g_w > 0.0_dp .AND. &
    1748              :           ALLOCATED(bs_env%ri_rs%atom_centers)) THEN
    1749              :          CALL reserve_blocks_within_radius(matrix_G_ao, bs_env%ri_rs%atom_centers, &
    1750            0 :                                            bs_env%ri_rs%cutoff_radius_g_w)
    1751            0 :          CALL copy_fm_to_dbcsr(fm_G, matrix_G_ao, keep_sparsity=.TRUE.)
    1752              :       ELSE
    1753         2966 :          CALL copy_fm_to_dbcsr(fm_G, matrix_G_ao, keep_sparsity=.FALSE.)
    1754              :       END IF
    1755         2966 :       CALL dbcsr_filter(matrix_G_ao, bs_env%eps_filter)
    1756              : 
    1757              :       ! release only the topology; keep matrix_G_ao for the caller
    1758         2966 :       CALL release_square_topology(dist=dist_ao_ao, mapped_dist=dist_row_ao)
    1759              : 
    1760         2966 :       CALL timestop(handle)
    1761              : 
    1762         2966 :    END SUBROUTINE build_G_ao
    1763              : 
    1764              : ! **************************************************************************************************
    1765              : !> \brief Panel-streaming evaluation of  out += scale * L_out^T (A_grid ∘ B_grid) L_out,
    1766              : !>        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
    1767              : !>        grid x grid objects. The grid (row) index is processed in panels of ~panel_size rows; for
    1768              : !>        each panel only P x grid slabs are built, Hadamard-multiplied, and contracted into the
    1769              : !>        (small) output. Algebraically identical to L_out^T (A_grid ∘ B_grid) L_out summed over
    1770              : !>        grid rows, so the result matches the non-streamed path to eps_filter.
    1771              : !>
    1772              : !>        Mapping (L in {phi (grid x AO), Z (grid x RI)}, M the AO/RI-space operator):
    1773              : !>          chi :   L_A=L_B=phi, M_A=G_occ_ao, M_B=G_vir_ao, L_out=Z   -> RI x RI
    1774              : !>          Sig :   L_A=phi (M_A=D/G), L_B=Z (M_B=V/W),       L_out=phi -> AO x AO
    1775              : !> \param L_A ...
    1776              : !> \param M_A ...
    1777              : !> \param L_B ...
    1778              : !> \param M_B ...
    1779              : !> \param L_out ...
    1780              : !> \param mat_out ...
    1781              : !> \param scale ...
    1782              : !> \param eps ...
    1783              : !> \param para_env ...
    1784              : !> \param pan_first ...
    1785              : !> \param pan_last ...
    1786              : !> \param lb_eq_la ...
    1787              : !> \param lout_eq_la ...
    1788              : !> \param zero_out ...
    1789              : !> \param keep_sparsity ...
    1790              : !> \param centroids ...
    1791              : !> \param cutoff ...
    1792              : !> \param grid_occupation ...
    1793              : ! **************************************************************************************************
    1794          676 :    SUBROUTINE contract_grid_panels(L_A, M_A, L_B, M_B, L_out, mat_out, scale, eps, para_env, &
    1795          676 :                                    pan_first, pan_last, lb_eq_la, lout_eq_la, zero_out, &
    1796          676 :                                    keep_sparsity, centroids, cutoff, grid_occupation)
    1797              : 
    1798              :       TYPE(dbcsr_type), INTENT(INOUT), TARGET            :: L_A
    1799              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: M_A
    1800              :       TYPE(dbcsr_type), INTENT(INOUT), TARGET            :: L_B
    1801              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: M_B
    1802              :       TYPE(dbcsr_type), INTENT(INOUT), TARGET            :: L_out
    1803              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_out
    1804              :       REAL(KIND=dp), INTENT(IN)                          :: scale, eps
    1805              :       TYPE(mp_para_env_type), INTENT(IN), POINTER        :: para_env
    1806              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: pan_first, pan_last
    1807              :       LOGICAL, INTENT(IN)                                :: lb_eq_la, lout_eq_la, zero_out
    1808              :       LOGICAL, INTENT(IN), OPTIONAL                      :: keep_sparsity
    1809              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN), &
    1810              :          OPTIONAL                                        :: centroids
    1811              :       REAL(KIND=dp), INTENT(IN), OPTIONAL                :: cutoff
    1812              :       REAL(KIND=dp), INTENT(OUT), OPTIONAL               :: grid_occupation
    1813              : 
    1814              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'contract_grid_panels'
    1815              : 
    1816              :       INTEGER                                            :: blk0, blk1, handle, ipan, n_grid_total, &
    1817              :                                                             ncols_pan, nrows_pan
    1818          676 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: gmap
    1819              :       LOGICAL                                            :: my_keep_sparsity, use_cutoff
    1820          676 :       LOGICAL, ALLOCATABLE, DIMENSION(:)                 :: grid_used, usedA, usedB
    1821              :       TYPE(dbcsr_type)                                   :: A_pan, B_pan, C_pan, LA_pan, LA_panC, &
    1822              :                                                             LB_pan, LB_panC, Lout_pan, MA_sub, &
    1823              :                                                             MB_sub, tmp2, tmpA, tmpB
    1824              :       TYPE(dbcsr_type), POINTER                          :: RB_A, RB_B, RB_out
    1825              :       TYPE(dbcsr_type), TARGET                           :: LA_near, LB_near, Lout_near
    1826              : 
    1827          676 :       CALL timeset(routineN, handle)
    1828              : 
    1829          676 :       my_keep_sparsity = .FALSE.
    1830          676 :       IF (PRESENT(keep_sparsity)) my_keep_sparsity = keep_sparsity
    1831          676 :       use_cutoff = PRESENT(centroids) .AND. PRESENT(cutoff)
    1832          676 :       IF (use_cutoff) use_cutoff = cutoff > 0.0_dp
    1833          676 :       IF (PRESENT(grid_occupation)) grid_occupation = 0.0_dp
    1834              : 
    1835          676 :       CALL dbcsr_get_info(L_A, nfullrows_total=n_grid_total)
    1836              : 
    1837          676 :       IF (zero_out) CALL dbcsr_set(mat_out, 0.0_dp)
    1838              : 
    1839         1374 :       DO ipan = 1, SIZE(pan_first)
    1840          698 :          blk0 = pan_first(ipan)
    1841          698 :          blk1 = pan_last(ipan)
    1842              : 
    1843              :          ! phi/Z panel slices (P x n)
    1844          698 :          CALL extract_grid_panel(L_A, blk0, blk1, LA_pan)
    1845          698 :          IF (.NOT. lb_eq_la) CALL extract_grid_panel(L_B, blk0, blk1, LB_pan)
    1846          698 :          IF (.NOT. lout_eq_la) CALL extract_grid_panel(L_out, blk0, blk1, Lout_pan)
    1847              : 
    1848              :          ! Which AO/RI atoms (column blocks) actually touch this panel: the inner index of
    1849              :          ! every multiply below is restricted to them, so only the matching rows of the
    1850              :          ! system-wide operators M_A/M_B ever enter Cannon (exact: dropped rows meet zeros).
    1851          698 :          CALL collect_used_col_blocks(LA_pan, para_env, usedA)
    1852          698 :          IF (.NOT. lb_eq_la) THEN
    1853           48 :             CALL collect_used_col_blocks(LB_pan, para_env, usedB)
    1854              :          ELSE
    1855          650 :             IF (ALLOCATED(usedB)) DEALLOCATE (usedB)
    1856         3500 :             ALLOCATE (usedB, SOURCE=usedA)
    1857              :          END IF
    1858          698 :          IF (.NOT. (ANY(usedA) .AND. ANY(usedB))) THEN
    1859              :             ! empty panel slice: its Hadamard contribution is exactly zero on all ranks
    1860            0 :             CALL dbcsr_release(LA_pan)
    1861            0 :             IF (.NOT. lb_eq_la) CALL dbcsr_release(LB_pan)
    1862            0 :             IF (.NOT. lout_eq_la) CALL dbcsr_release(Lout_pan)
    1863              :             CYCLE
    1864              :          END IF
    1865              : 
    1866              :          ! Grid rows within reach of the panel: with the CUTOFF_RADIUS_RL_W truncation only
    1867              :          ! they can appear as columns of the panel products / inner rows of the L_out multiply,
    1868              :          ! so the system-wide phi/Z right operands are cut down to this neighborhood slice
    1869              :          ! (local extraction, zero communication; exact w.r.t. the geo template).
    1870          698 :          IF (use_cutoff) THEN
    1871           44 :             CALL mask_grid_blocks_near_panel(centroids, blk0, blk1, cutoff, grid_used)
    1872           44 :             CALL extract_masked_blocks(L_A, grid_used, LA_near, compress_rows=.TRUE., blk_map=gmap)
    1873           44 :             IF (.NOT. lb_eq_la) CALL extract_masked_blocks(L_B, grid_used, LB_near, compress_rows=.TRUE.)
    1874           44 :             IF (.NOT. lout_eq_la) CALL extract_masked_blocks(L_out, grid_used, Lout_near, compress_rows=.TRUE.)
    1875              :             RB_A => LA_near
    1876              :          ELSE
    1877              :             RB_A => L_A
    1878              :          END IF
    1879          698 :          IF (lb_eq_la) THEN
    1880              :             RB_B => RB_A
    1881           48 :          ELSE IF (use_cutoff) THEN
    1882              :             RB_B => LB_near
    1883              :          ELSE
    1884           44 :             RB_B => L_B
    1885              :          END IF
    1886          698 :          IF (lout_eq_la) THEN
    1887              :             RB_out => RB_A
    1888          650 :          ELSE IF (use_cutoff) THEN
    1889              :             RB_out => Lout_near
    1890              :          ELSE
    1891          610 :             RB_out => L_out
    1892              :          END IF
    1893              : 
    1894              :          ! A_pan = LA_pan * M_A * L_A^T   (P x grid_near).
    1895              :          ! When cutoff is active, A_pan is pre-seeded with only nearby blocks via
    1896              :          ! build_geo_template_panel, and the multiply uses retain_sparsity to skip
    1897              :          ! computing distant blocks entirely (exact: they are zero by locality).
    1898          698 :          CALL extract_masked_blocks(LA_pan, usedA, LA_panC, compress_rows=.FALSE.)
    1899          698 :          CALL extract_masked_blocks(M_A, usedA, MA_sub, compress_rows=.TRUE.)
    1900          698 :          CALL create_product_matrix(LA_panC, MA_sub, 'N', 'N', tmpA)
    1901          698 :          CALL dbcsr_multiply('N', 'N', 1.0_dp, LA_panC, MA_sub, 0.0_dp, tmpA, filter_eps=eps)
    1902          698 :          CALL dbcsr_release(MA_sub)
    1903          698 :          IF (use_cutoff) THEN
    1904              :             CALL build_geo_template_panel(LA_pan, LA_near, centroids, cutoff, blk0, A_pan, &
    1905           44 :                                           col_map=gmap)
    1906              :          ELSE
    1907          654 :             CALL create_product_matrix(tmpA, RB_A, 'N', 'T', A_pan)
    1908              :          END IF
    1909              :          CALL dbcsr_multiply('N', 'T', 1.0_dp, tmpA, RB_A, 0.0_dp, A_pan, &
    1910          698 :                              filter_eps=eps, retain_sparsity=use_cutoff)
    1911          698 :          CALL dbcsr_release(tmpA)
    1912              : 
    1913              :          ! Grid-basis occupation of A_pan = ϕ G ϕ^T, accumulated over ALL panels into the
    1914              :          ! occupation of the (never formed) full grid x grid object:
    1915              :          !   sum_panels nnz(A_pan) / n_grid^2,  with nnz = occ * pan_rows * pan_cols.
    1916              :          ! Panel-independent by construction -- a single-panel sample would instead report the
    1917              :          ! local neighbor count of whichever region happens to land in that panel.
    1918          698 :          IF (PRESENT(grid_occupation)) THEN
    1919          650 :             CALL dbcsr_get_info(A_pan, nfullrows_total=nrows_pan, nfullcols_total=ncols_pan)
    1920              :             grid_occupation = grid_occupation + dbcsr_get_occupation(A_pan)* &
    1921              :                               REAL(ncols_pan, dp)*REAL(nrows_pan, dp)/ &
    1922          650 :                               (REAL(n_grid_total, dp)*REAL(n_grid_total, dp))
    1923              :          END IF
    1924              : 
    1925              :          ! B_pan = LB_pan * M_B * L_B^T   (P x grid_near); reuse the L_A slices when L_B == L_A.
    1926              :          ! With keep_sparsity, B_pan is pre-populated with A_pan's block structure so that
    1927              :          ! retain_sparsity forces the final multiply to fill only those blocks (exact for ∘).
    1928          698 :          IF (lb_eq_la) THEN
    1929          650 :             CALL extract_masked_blocks(M_B, usedA, MB_sub, compress_rows=.TRUE.)
    1930          650 :             CALL create_product_matrix(LA_panC, MB_sub, 'N', 'N', tmpB)
    1931          650 :             CALL dbcsr_multiply('N', 'N', 1.0_dp, LA_panC, MB_sub, 0.0_dp, tmpB, filter_eps=eps)
    1932              :          ELSE
    1933           48 :             CALL extract_masked_blocks(LB_pan, usedB, LB_panC, compress_rows=.FALSE.)
    1934           48 :             CALL extract_masked_blocks(M_B, usedB, MB_sub, compress_rows=.TRUE.)
    1935           48 :             CALL create_product_matrix(LB_panC, MB_sub, 'N', 'N', tmpB)
    1936           48 :             CALL dbcsr_multiply('N', 'N', 1.0_dp, LB_panC, MB_sub, 0.0_dp, tmpB, filter_eps=eps)
    1937           48 :             CALL dbcsr_release(LB_panC)
    1938              :          END IF
    1939          698 :          CALL dbcsr_release(MB_sub)
    1940          698 :          IF (my_keep_sparsity) THEN
    1941          698 :             CALL dbcsr_create(B_pan, template=A_pan)
    1942          698 :             CALL dbcsr_copy(B_pan, A_pan)
    1943          698 :             CALL dbcsr_set(B_pan, 0.0_dp)
    1944              :             ! The output pattern is already fixed. Omitting redundant on-the-fly filtering also
    1945              :             ! avoids overflowing DBCSR's single-precision screening norms for conditioned Z fits.
    1946              :             CALL dbcsr_multiply('N', 'T', 1.0_dp, tmpB, RB_B, 0.0_dp, B_pan, &
    1947          698 :                                 retain_sparsity=.TRUE.)
    1948              :          ELSE
    1949            0 :             CALL create_product_matrix(tmpB, RB_B, 'N', 'T', B_pan)
    1950            0 :             CALL dbcsr_multiply('N', 'T', 1.0_dp, tmpB, RB_B, 0.0_dp, B_pan, filter_eps=eps)
    1951              :          END IF
    1952          698 :          CALL dbcsr_release(tmpB)
    1953          698 :          CALL dbcsr_release(LA_panC)
    1954              : 
    1955              :          ! C_pan = scale * (A_pan ∘ B_pan)   (P x grid_near)
    1956          698 :          CALL dbcsr_create(C_pan, template=A_pan)
    1957          698 :          CALL hadamard_product(A_pan, B_pan, C_pan, scale)
    1958          698 :          CALL dbcsr_release(A_pan)
    1959          698 :          CALL dbcsr_release(B_pan)
    1960              : 
    1961              :          ! tmp2 = C_pan * L_out   (P x n_out; inner index restricted to the neighborhood)
    1962          698 :          CALL create_product_matrix(C_pan, RB_out, 'N', 'N', tmp2)
    1963          698 :          CALL dbcsr_multiply('N', 'N', 1.0_dp, C_pan, RB_out, 0.0_dp, tmp2, filter_eps=eps)
    1964          698 :          CALL dbcsr_release(C_pan)
    1965              : 
    1966              :          ! mat_out += L_out_pan^T * tmp2   (accumulate: beta = 1)
    1967          698 :          IF (lout_eq_la) THEN
    1968           48 :             CALL dbcsr_multiply('T', 'N', 1.0_dp, LA_pan, tmp2, 1.0_dp, mat_out, filter_eps=eps)
    1969              :          ELSE
    1970          650 :             CALL dbcsr_multiply('T', 'N', 1.0_dp, Lout_pan, tmp2, 1.0_dp, mat_out, filter_eps=eps)
    1971          650 :             CALL dbcsr_release(Lout_pan)
    1972              :          END IF
    1973          698 :          CALL dbcsr_release(tmp2)
    1974          698 :          IF (.NOT. lb_eq_la) CALL dbcsr_release(LB_pan)
    1975          698 :          CALL dbcsr_release(LA_pan)
    1976         3468 :          IF (use_cutoff) THEN
    1977           44 :             CALL dbcsr_release(LA_near)
    1978           44 :             IF (.NOT. lb_eq_la) CALL dbcsr_release(LB_near)
    1979           44 :             IF (.NOT. lout_eq_la) CALL dbcsr_release(Lout_near)
    1980              :          END IF
    1981              : 
    1982              :       END DO
    1983              : 
    1984          676 :       CALL timestop(handle)
    1985              : 
    1986         1352 :    END SUBROUTINE contract_grid_panels
    1987              : 
    1988              : ! **************************************************************************************************
    1989              : !> \brief Σ^c-specific panel loop: computes both the occupied (neg) and virtual (pos) contributions
    1990              : !>        in a single pass over grid panels, forming W_pan = Z_panel × W_aux × Z^T only ONCE per
    1991              : !>        panel and reusing it for both the G^occ and G^vir Hadamard contractions.
    1992              : !>
    1993              : !>        Computes:
    1994              : !>          mat_Sigma_neg = ϕ^T ( (ϕ G^occ ϕ^T) ∘ (Z W^MIC Z^T) ) ϕ
    1995              : !>          mat_Sigma_pos = ϕ^T ( (ϕ G^vir ϕ^T) ∘ (Z W^MIC Z^T) ) ϕ
    1996              : !>
    1997              : !> \param mat_phi ...
    1998              : !> \param mat_Z ...
    1999              : !> \param mat_G_occ_ao ...
    2000              : !> \param mat_G_vir_ao ...
    2001              : !> \param mat_W_aux ...
    2002              : !> \param mat_Sigma_neg ...
    2003              : !> \param mat_Sigma_pos ...
    2004              : !> \param eps ...
    2005              : !> \param para_env ...
    2006              : !> \param pan_first ...
    2007              : !> \param pan_last ...
    2008              : !> \param keep_sparsity ...
    2009              : !> \param centroids ...
    2010              : !> \param cutoff ...
    2011              : ! **************************************************************************************************
    2012          830 :    SUBROUTINE contract_grid_panels_sigma_c(mat_phi, mat_Z, mat_G_occ_ao, mat_G_vir_ao, &
    2013              :                                            mat_W_aux, mat_Sigma_neg, mat_Sigma_pos, eps, para_env, &
    2014          830 :                                            pan_first, pan_last, keep_sparsity, centroids, cutoff)
    2015              : 
    2016              :       TYPE(dbcsr_type), INTENT(INOUT), TARGET            :: mat_phi, mat_Z
    2017              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_G_occ_ao, mat_G_vir_ao, mat_W_aux, &
    2018              :                                                             mat_Sigma_neg, mat_Sigma_pos
    2019              :       REAL(KIND=dp), INTENT(IN)                          :: eps
    2020              :       TYPE(mp_para_env_type), INTENT(IN), POINTER        :: para_env
    2021              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: pan_first, pan_last
    2022              :       LOGICAL, INTENT(IN), OPTIONAL                      :: keep_sparsity
    2023              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN), &
    2024              :          OPTIONAL                                        :: centroids
    2025              :       REAL(KIND=dp), INTENT(IN), OPTIONAL                :: cutoff
    2026              : 
    2027              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'contract_grid_panels_sigma_c'
    2028              : 
    2029              :       INTEGER                                            :: blk0, blk1, handle, ipan
    2030          830 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: gmap
    2031              :       LOGICAL                                            :: my_keep_sparsity, use_cutoff
    2032          830 :       LOGICAL, ALLOCATABLE, DIMENSION(:)                 :: grid_used, used_ao, used_ri
    2033              :       TYPE(dbcsr_type)                                   :: A_occ, A_vir, C_pan, G_occ_sub, &
    2034              :                                                             G_vir_sub, phi_pan, phi_panC, tmp2, &
    2035              :                                                             tmpA, tmpB, W_pan, W_sub, Z_pan, Z_panC
    2036              :       TYPE(dbcsr_type), POINTER                          :: RB_phi, RB_Z
    2037              :       TYPE(dbcsr_type), TARGET                           :: phi_near, Z_near
    2038              : 
    2039          830 :       CALL timeset(routineN, handle)
    2040              : 
    2041          830 :       my_keep_sparsity = .FALSE.
    2042          830 :       IF (PRESENT(keep_sparsity)) my_keep_sparsity = keep_sparsity
    2043          830 :       use_cutoff = PRESENT(centroids) .AND. PRESENT(cutoff)
    2044          830 :       IF (use_cutoff) use_cutoff = cutoff > 0.0_dp
    2045              : 
    2046          830 :       CALL dbcsr_set(mat_Sigma_neg, 0.0_dp)
    2047          830 :       CALL dbcsr_set(mat_Sigma_pos, 0.0_dp)
    2048              : 
    2049         1680 :       DO ipan = 1, SIZE(pan_first)
    2050          850 :          blk0 = pan_first(ipan)
    2051          850 :          blk1 = pan_last(ipan)
    2052              : 
    2053          850 :          CALL extract_grid_panel(mat_phi, blk0, blk1, phi_pan)
    2054          850 :          CALL extract_grid_panel(mat_Z, blk0, blk1, Z_pan)
    2055              : 
    2056              :          ! AO/RI atoms touching this panel: only the matching rows of G_occ/G_vir/W ever
    2057              :          ! enter the multiplies below (exact: dropped rows meet zero columns of the panel).
    2058          850 :          CALL collect_used_col_blocks(phi_pan, para_env, used_ao)
    2059          850 :          CALL collect_used_col_blocks(Z_pan, para_env, used_ri)
    2060          850 :          IF (.NOT. (ANY(used_ao) .AND. ANY(used_ri))) THEN
    2061            0 :             CALL dbcsr_release(phi_pan)
    2062            0 :             CALL dbcsr_release(Z_pan)
    2063            0 :             CYCLE
    2064              :          END IF
    2065              : 
    2066              :          ! Neighborhood slices of phi/Z (grid rows within cutoff of the panel): they replace
    2067              :          ! the system-wide right operands in every multiply (local extraction, zero comm).
    2068          850 :          IF (use_cutoff) THEN
    2069           40 :             CALL mask_grid_blocks_near_panel(centroids, blk0, blk1, cutoff, grid_used)
    2070           40 :             CALL extract_masked_blocks(mat_phi, grid_used, phi_near, compress_rows=.TRUE., blk_map=gmap)
    2071           40 :             CALL extract_masked_blocks(mat_Z, grid_used, Z_near, compress_rows=.TRUE.)
    2072           40 :             RB_phi => phi_near
    2073           40 :             RB_Z => Z_near
    2074              :          ELSE
    2075              :             RB_phi => mat_phi
    2076              :             RB_Z => mat_Z
    2077              :          END IF
    2078              : 
    2079          850 :          CALL extract_masked_blocks(phi_pan, used_ao, phi_panC, compress_rows=.FALSE.)
    2080          850 :          CALL extract_masked_blocks(Z_pan, used_ri, Z_panC, compress_rows=.FALSE.)
    2081          850 :          CALL extract_masked_blocks(mat_G_occ_ao, used_ao, G_occ_sub, compress_rows=.TRUE.)
    2082          850 :          CALL extract_masked_blocks(mat_G_vir_ao, used_ao, G_vir_sub, compress_rows=.TRUE.)
    2083          850 :          CALL extract_masked_blocks(mat_W_aux, used_ri, W_sub, compress_rows=.TRUE.)
    2084              : 
    2085              :          ! A_occ = phi_pan × G_occ × phi^T  (built first so W_pan can inherit its pattern).
    2086              :          ! With cutoff active, A_occ is pre-seeded with geo-local blocks so that the
    2087              :          ! phi^T multiply uses retain_sparsity and never computes distant blocks.
    2088          850 :          CALL create_product_matrix(phi_panC, G_occ_sub, 'N', 'N', tmpA)
    2089          850 :          CALL dbcsr_multiply('N', 'N', 1.0_dp, phi_panC, G_occ_sub, 0.0_dp, tmpA, filter_eps=eps)
    2090          850 :          IF (use_cutoff) THEN
    2091              :             CALL build_geo_template_panel(phi_pan, phi_near, centroids, cutoff, blk0, A_occ, &
    2092           40 :                                           col_map=gmap)
    2093              :          ELSE
    2094          810 :             CALL create_product_matrix(tmpA, RB_phi, 'N', 'T', A_occ)
    2095              :          END IF
    2096              :          CALL dbcsr_multiply('N', 'T', 1.0_dp, tmpA, RB_phi, 0.0_dp, A_occ, &
    2097          850 :                              filter_eps=eps, retain_sparsity=use_cutoff)
    2098          850 :          CALL dbcsr_release(tmpA)
    2099          850 :          CALL dbcsr_release(G_occ_sub)
    2100              : 
    2101              :          ! A_vir = phi_pan × G_vir × phi^T  (same pre-screen as A_occ)
    2102          850 :          CALL create_product_matrix(phi_panC, G_vir_sub, 'N', 'N', tmpA)
    2103          850 :          CALL dbcsr_multiply('N', 'N', 1.0_dp, phi_panC, G_vir_sub, 0.0_dp, tmpA, filter_eps=eps)
    2104          850 :          IF (use_cutoff) THEN
    2105              :             CALL build_geo_template_panel(phi_pan, phi_near, centroids, cutoff, blk0, A_vir, &
    2106           40 :                                           col_map=gmap)
    2107              :          ELSE
    2108          810 :             CALL create_product_matrix(tmpA, RB_phi, 'N', 'T', A_vir)
    2109              :          END IF
    2110              :          CALL dbcsr_multiply('N', 'T', 1.0_dp, tmpA, RB_phi, 0.0_dp, A_vir, &
    2111          850 :                              filter_eps=eps, retain_sparsity=use_cutoff)
    2112          850 :          CALL dbcsr_release(tmpA)
    2113          850 :          CALL dbcsr_release(G_vir_sub)
    2114              : 
    2115              :          ! W_pan = Z_pan × W_aux × Z^T  (computed once, reused for both Σ^c terms).
    2116              :          ! With keep_sparsity, W_pan is pre-seeded with the union of A_occ and A_vir block
    2117              :          ! patterns so that retain_sparsity forces the multiply to fill only those blocks:
    2118              :          ! exact since W outside G_occ∪G_vir is multiplied by zero in the Hadamard.
    2119          850 :          CALL create_product_matrix(Z_panC, W_sub, 'N', 'N', tmpB)
    2120          850 :          CALL dbcsr_multiply('N', 'N', 1.0_dp, Z_panC, W_sub, 0.0_dp, tmpB, filter_eps=eps)
    2121          850 :          IF (my_keep_sparsity) THEN
    2122          850 :             CALL dbcsr_create(W_pan, template=A_occ)
    2123          850 :             CALL dbcsr_copy(W_pan, A_occ)
    2124          850 :             CALL dbcsr_add(W_pan, A_vir, 1.0_dp, 1.0_dp)
    2125          850 :             CALL dbcsr_set(W_pan, 0.0_dp)
    2126              :             ! The retained pattern is exact for the following Hadamard products; screening it
    2127              :             ! again is redundant and may overflow DBCSR's single-precision block-norm product.
    2128              :             CALL dbcsr_multiply('N', 'T', 1.0_dp, tmpB, RB_Z, 0.0_dp, W_pan, &
    2129          850 :                                 retain_sparsity=.TRUE.)
    2130              :          ELSE
    2131            0 :             CALL create_product_matrix(tmpB, RB_Z, 'N', 'T', W_pan)
    2132            0 :             CALL dbcsr_multiply('N', 'T', 1.0_dp, tmpB, RB_Z, 0.0_dp, W_pan, filter_eps=eps)
    2133              :          END IF
    2134          850 :          CALL dbcsr_release(tmpB)
    2135          850 :          CALL dbcsr_release(W_sub)
    2136          850 :          CALL dbcsr_release(phi_panC)
    2137          850 :          CALL dbcsr_release(Z_panC)
    2138              : 
    2139              :          ! Σ^c_neg: ϕ^T ( A_occ ∘ W_pan ) ϕ
    2140          850 :          CALL dbcsr_create(C_pan, template=A_occ)
    2141          850 :          CALL hadamard_product(A_occ, W_pan, C_pan, 1.0_dp)
    2142          850 :          CALL dbcsr_release(A_occ)
    2143          850 :          CALL create_product_matrix(C_pan, RB_phi, 'N', 'N', tmp2)
    2144          850 :          CALL dbcsr_multiply('N', 'N', 1.0_dp, C_pan, RB_phi, 0.0_dp, tmp2, filter_eps=eps)
    2145          850 :          CALL dbcsr_release(C_pan)
    2146          850 :          CALL dbcsr_multiply('T', 'N', 1.0_dp, phi_pan, tmp2, 1.0_dp, mat_Sigma_neg, filter_eps=eps)
    2147          850 :          CALL dbcsr_release(tmp2)
    2148              : 
    2149              :          ! Σ^c_pos: ϕ^T ( A_vir ∘ W_pan ) ϕ  — W_pan reused
    2150          850 :          CALL dbcsr_create(C_pan, template=A_vir)
    2151          850 :          CALL hadamard_product(A_vir, W_pan, C_pan, 1.0_dp)
    2152          850 :          CALL dbcsr_release(A_vir)
    2153          850 :          CALL create_product_matrix(C_pan, RB_phi, 'N', 'N', tmp2)
    2154          850 :          CALL dbcsr_multiply('N', 'N', 1.0_dp, C_pan, RB_phi, 0.0_dp, tmp2, filter_eps=eps)
    2155          850 :          CALL dbcsr_release(C_pan)
    2156          850 :          CALL dbcsr_multiply('T', 'N', 1.0_dp, phi_pan, tmp2, 1.0_dp, mat_Sigma_pos, filter_eps=eps)
    2157          850 :          CALL dbcsr_release(tmp2)
    2158              : 
    2159          850 :          CALL dbcsr_release(W_pan)
    2160          850 :          CALL dbcsr_release(Z_pan)
    2161          850 :          CALL dbcsr_release(phi_pan)
    2162         6780 :          IF (use_cutoff) THEN
    2163           40 :             CALL dbcsr_release(phi_near)
    2164           40 :             CALL dbcsr_release(Z_near)
    2165              :          END IF
    2166              : 
    2167              :       END DO
    2168              : 
    2169          830 :       CALL timestop(handle)
    2170              : 
    2171         1660 :    END SUBROUTINE contract_grid_panels_sigma_c
    2172              : 
    2173              : ! **************************************************************************************************
    2174              : !> \brief Computes the screened Coulomb interaction on the imaginary-time grid, entirely in the
    2175              : !>        RI auxiliary (PQ) basis:
    2176              : !>          χ_PQ(iω) = Σ_τ w(ω,τ) cos(ωτ) χ_PQ(iτ)                  (cosine transform)
    2177              : !>          ε(iω)    = Id - V^0.5 M^-1 χ(iω) M^-1 V^0.5             (dielectric function)
    2178              : !>          W(iω)    = V^0.5 ( ε^-1(iω) - Id ) V^0.5                (correlation part only)
    2179              : !>          W(iτ)    = Σ_ω w̃(τ,ω) cos(ωτ) W(iω)                    (back transform)
    2180              : !>          W(iτ)   <- M^-1 W(iτ) M^-1                              (fold in the RI metric)
    2181              : !>        where V is the bare Coulomb matrix and M the RI metric.
    2182              : !> \param bs_env ...
    2183              : !> \param qs_env ...
    2184              : !> \param mat_chi_Gamma_tau ...
    2185              : !> \param fm_W_time ...
    2186              : ! **************************************************************************************************
    2187          160 :    SUBROUTINE compute_W(bs_env, qs_env, mat_chi_Gamma_tau, fm_W_time)
    2188              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2189              :       TYPE(qs_environment_type), POINTER                 :: qs_env
    2190              :       TYPE(dbcsr_p_type), DIMENSION(:), POINTER          :: mat_chi_Gamma_tau
    2191              :       TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:)        :: fm_W_time
    2192              : 
    2193              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'compute_W'
    2194              : 
    2195              :       INTEGER                                            :: handle, i_t, j_w
    2196              :       REAL(KIND=dp)                                      :: t1
    2197              :       TYPE(cp_fm_type)                                   :: fm_M_inv_V_sqrt, fm_V, fm_V_sqrt
    2198              : 
    2199           40 :       CALL timeset(routineN, handle)
    2200              : 
    2201           40 :       t1 = m_walltime()
    2202              : 
    2203           40 :       CALL create_fm_W_MIC_time(bs_env, fm_W_time)
    2204              : 
    2205              :       ! 1. Allocate V and M matrices
    2206           40 :       CALL cp_fm_create(fm_V, bs_env%fm_RI_RI%matrix_struct)
    2207           40 :       CALL cp_fm_create(fm_V_sqrt, bs_env%fm_RI_RI%matrix_struct)
    2208           40 :       CALL cp_fm_create(fm_M_inv_V_sqrt, bs_env%fm_RI_RI%matrix_struct)
    2209              : 
    2210              :       ! Compute V and M^-1 * V^0.5
    2211           40 :       CALL compute_V_MinvVsqrt(bs_env, qs_env, fm_V, fm_V_sqrt, fm_M_inv_V_sqrt)
    2212              : 
    2213              :       ! 2. Loop over frequencies
    2214          590 :       DO j_w = 1, bs_env%num_time_freq_points
    2215              :          ! Fourier transformation of χ_PQ(iτ) to χ_PQ(iω_j)
    2216          550 :          CALL compute_fm_chi_Gamma_freq(bs_env, bs_env%fm_chi_Gamma_freq, j_w, mat_chi_Gamma_tau)
    2217              : 
    2218              :          ! ε(iω_j) = Id - V^0.5*M^-1*χ(iω_j)*M^-1*V^0.5
    2219              :          ! W(iω_j) = V^0.5*(ε^-1(iω_j)-Id)*V^0.5
    2220              :          CALL compute_fm_W_freq(bs_env, bs_env%fm_chi_Gamma_freq, fm_V_sqrt, &
    2221          550 :                                 fm_M_inv_V_sqrt, bs_env%fm_W_MIC_freq)
    2222              : 
    2223              :          ! Fourier transform from W_PQ^MIC(iω_j) to W_PQ^MIC(iτ)
    2224          590 :          CALL Fourier_transform_w_to_t(bs_env, fm_W_time, bs_env%fm_W_MIC_freq, j_w)
    2225              :       END DO
    2226              : 
    2227              :       ! M^-1(k=0) W^MIC(iτ) M^-1(k=0) -> fm_W_time
    2228           40 :       CALL fm_contract_ABA(bs_env%fm_Minv_Gamma, fm_W_time)
    2229              : 
    2230           40 :       IF (bs_env%unit_nr > 0) THEN
    2231              :          WRITE (bs_env%unit_nr, '(T2,A,T58,A,F7.1,A)') &
    2232           20 :             'Computed W(iτ),', ' Execution time', m_walltime() - t1, ' s'
    2233              :       END IF
    2234              : 
    2235           40 :       CALL dbcsr_deallocate_matrix_set(mat_chi_Gamma_tau)
    2236              : 
    2237              :       ! Cleanup
    2238           40 :       CALL cp_fm_release(fm_V)
    2239           40 :       CALL cp_fm_release(fm_V_sqrt)
    2240           40 :       CALL cp_fm_release(fm_M_inv_V_sqrt)
    2241              : 
    2242              :       ! Marek : Fourier transform W^MIC(itau) back to get it at a specific im.frequency point - iomega = 0
    2243           40 :       IF (bs_env%rtp_method == rtp_method_bse) THEN
    2244           14 :          t1 = m_walltime()
    2245           14 :          CALL cp_fm_create(bs_env%fm_W_MIC_freq_zero, bs_env%fm_W_MIC_freq%matrix_struct)
    2246              :          ! Set to zero
    2247           14 :          CALL cp_fm_set_all(bs_env%fm_W_MIC_freq_zero, 0.0_dp)
    2248              :          ! Sum over all times
    2249          294 :          DO i_t = 1, bs_env%num_time_freq_points
    2250              :             ! Add the relevant structure with correct weight
    2251              :             CALL cp_fm_scale_and_add(1.0_dp, bs_env%fm_W_MIC_freq_zero, &
    2252          294 :                                      bs_env%time_frequency_grid%time_weights_at_zero_frequency(i_t), fm_W_time(i_t))
    2253              :          END DO
    2254              :          ! Done, save to file
    2255           14 :          CALL fm_write(bs_env%fm_W_MIC_freq_zero, 0, "W_freq_rtp", qs_env)
    2256              :          ! Report calculation
    2257           14 :          IF (bs_env%unit_nr > 0) THEN
    2258              :             WRITE (bs_env%unit_nr, '(T2,A,T57,A,F7.1,A)') &
    2259            7 :                'Computed W(0),', ' Execution time', m_walltime() - t1, ' s'
    2260              :          END IF
    2261              :       END IF
    2262              : 
    2263           40 :       IF (bs_env%unit_nr > 0) WRITE (bs_env%unit_nr, '(A)') ' '
    2264              : 
    2265           40 :       CALL timestop(handle)
    2266              : 
    2267           40 :    END SUBROUTINE compute_W
    2268              : 
    2269              : ! **************************************************************************************************
    2270              : !> \brief Computes V, V^0.5, and M^-1 V^0.5 for the RI-RS dielectric function.
    2271              : !>        The Coulomb matrix V is constructed by the RI-RS k-point path. The inverse metric
    2272              : !>        M^-1(k=0) is precomputed once in gw_utils and stored in bs_env.
    2273              : !> \param bs_env ...
    2274              : !> \param qs_env ...
    2275              : !> \param fm_V Coulomb matrix V(k=0)
    2276              : !> \param fm_V_sqrt symmetric factor V^0.5
    2277              : !> \param fm_Minv_Vsqrt product M^-1(k=0) V^0.5
    2278              : ! **************************************************************************************************
    2279           40 :    SUBROUTINE compute_V_MinvVsqrt(bs_env, qs_env, fm_V, fm_V_sqrt, fm_Minv_Vsqrt)
    2280              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2281              :       TYPE(qs_environment_type), POINTER                 :: qs_env
    2282              :       TYPE(cp_fm_type), INTENT(INOUT)                    :: fm_V, fm_V_sqrt, fm_Minv_Vsqrt
    2283              : 
    2284              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_V_MinvVsqrt'
    2285              : 
    2286              :       INTEGER                                            :: handle
    2287           40 :       TYPE(atomic_kind_type), DIMENSION(:), POINTER      :: atomic_kind_set
    2288              :       TYPE(cell_type), POINTER                           :: cell
    2289           40 :       TYPE(dbcsr_p_type), DIMENSION(:, :), POINTER       :: mat_V_kp
    2290           40 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
    2291           40 :       TYPE(qs_kind_type), DIMENSION(:), POINTER          :: qs_kind_set
    2292              : 
    2293           40 :       CALL timeset(routineN, handle)
    2294              : 
    2295           40 :       IF (bs_env%auto_ri%enabled) THEN
    2296              :          ! -------------------------------------------------------------------
    2297              :          ! 1a. The optimized AB functions span two atoms, so their previously transformed
    2298              :          !     Coulomb matrix cannot be rebuilt by the atom-local k-point integral routine.
    2299              :          ! -------------------------------------------------------------------
    2300            4 :          CALL cp_fm_to_fm(bs_env%auto_ri%V_pq, fm_V)
    2301              :       ELSE
    2302              :          ! -------------------------------------------------------------------
    2303              :          ! 1b. Build Coulomb Matrix V(k=0) using the kp-routine but only for ikp=1.
    2304              :          ! -------------------------------------------------------------------
    2305              :          CALL get_qs_env(qs_env=qs_env, particle_set=particle_set, cell=cell, &
    2306           36 :                          qs_kind_set=qs_kind_set)
    2307           36 :          atomic_kind_set => bs_env%ri_rs%atomic_kind_set
    2308              : 
    2309          180 :          ALLOCATE (mat_V_kp(1:1, 1:2))
    2310           36 :          NULLIFY (mat_V_kp(1, 1)%matrix, mat_V_kp(1, 2)%matrix)
    2311           36 :          ALLOCATE (mat_V_kp(1, 1)%matrix, mat_V_kp(1, 2)%matrix)
    2312           36 :          CALL dbcsr_create(mat_V_kp(1, 1)%matrix, template=bs_env%mat_RI_RI%matrix)
    2313           36 :          CALL dbcsr_reserve_all_blocks(mat_V_kp(1, 1)%matrix)
    2314           36 :          CALL dbcsr_set(mat_V_kp(1, 1)%matrix, 0.0_dp)
    2315           36 :          CALL dbcsr_create(mat_V_kp(1, 2)%matrix, template=bs_env%mat_RI_RI%matrix)
    2316           36 :          CALL dbcsr_reserve_all_blocks(mat_V_kp(1, 2)%matrix)
    2317              :          ! The dummy imaginary part is required only by the k-point routine interface.
    2318           36 :          CALL dbcsr_set(mat_V_kp(1, 2)%matrix, 0.0_dp)
    2319              : 
    2320          144 :          bs_env%kpoints_chi_eps_W%nkp_grid = bs_env%nkp_grid_chi_eps_W_orig
    2321              :          CALL build_2c_coulomb_matrix_kp(mat_V_kp, bs_env%kpoints_chi_eps_W, "RI_AUX", cell, &
    2322              :                                          particle_set, qs_kind_set, atomic_kind_set, &
    2323           36 :                                          bs_env%size_lattice_sum_V, operator_coulomb, 1, 1)
    2324           36 :          CALL copy_dbcsr_to_fm(mat_V_kp(1, 1)%matrix, fm_V)
    2325              : 
    2326           36 :          CALL dbcsr_deallocate_matrix(mat_V_kp(1, 1)%matrix)
    2327           36 :          CALL dbcsr_deallocate_matrix(mat_V_kp(1, 2)%matrix)
    2328           36 :          DEALLOCATE (mat_V_kp)
    2329              :       END IF
    2330              : 
    2331              :       ! -----------------------------------------------------------------------
    2332              :       ! 2. V -> V^0.5.
    2333              :       ! -----------------------------------------------------------------------
    2334           40 :       CALL fm_sqrt(fm_V, fm_V_sqrt, bs_env%eps_eigval_mat_RI, bs_env%unit_nr)
    2335              : 
    2336              :       ! -----------------------------------------------------------------------
    2337              :       ! 3. M^-1(k=0) V^0.5.
    2338              :       ! -----------------------------------------------------------------------
    2339              :       CALL parallel_gemm("N", "T", bs_env%n_RI, bs_env%n_RI, bs_env%n_RI, 1.0_dp, &
    2340           40 :                          bs_env%fm_Minv_Gamma, fm_V_sqrt, 0.0_dp, fm_Minv_Vsqrt)
    2341              : 
    2342           40 :       CALL timestop(handle)
    2343              : 
    2344           40 :    END SUBROUTINE compute_V_MinvVsqrt
    2345              : 
    2346              : ! **************************************************************************************************
    2347              : !> \brief Computes the screened interaction at one imaginary frequency:
    2348              : !>          ε(iω_j)  = Id - (M^-1 V^0.5)^T χ(iω_j) (M^-1 V^0.5)
    2349              : !>          W(iω_j)  = V^0.5^T ( ε^-1(iω_j) - Id ) V^0.5
    2350              : !>        ε is inverted via Cholesky; if that fails due to conditioning, via
    2351              : !>        eigendecomposition (cp_fm_power) with eigenvalue filtering.
    2352              : !> \param bs_env ...
    2353              : !> \param fm_chi_freq_j ...
    2354              : !> \param fm_V_sqrt ...
    2355              : !> \param fm_Minv_Vsqrt ...
    2356              : !> \param fm_W_freq_j ...
    2357              : ! **************************************************************************************************
    2358         1650 :    SUBROUTINE compute_fm_W_freq(bs_env, fm_chi_freq_j, fm_V_sqrt, fm_Minv_Vsqrt, fm_W_freq_j)
    2359              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2360              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_chi_freq_j, fm_V_sqrt, fm_Minv_Vsqrt
    2361              :       TYPE(cp_fm_type), INTENT(INOUT)                    :: fm_W_freq_j
    2362              : 
    2363              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'compute_fm_W_freq'
    2364              : 
    2365              :       INTEGER                                            :: handle, n_RI
    2366              :       TYPE(cp_fm_type)                                   :: fm_eps_freq_j, fm_work
    2367              : 
    2368          550 :       CALL timeset(routineN, handle)
    2369              : 
    2370          550 :       n_RI = bs_env%n_RI
    2371              : 
    2372          550 :       CALL cp_fm_create(fm_eps_freq_j, fm_chi_freq_j%matrix_struct)
    2373          550 :       CALL cp_fm_create(fm_work, fm_chi_freq_j%matrix_struct)
    2374              : 
    2375              :       ! -----------------------------------------------------------------------
    2376              :       ! 1. ε(iω_j) = Id - (M^-1 * V^0.5)^T * χ(iω_j) * (M^-1 * V^0.5)
    2377              :       ! -----------------------------------------------------------------------
    2378              :       ! work = χ(iω_j) * (M^-1 * V^0.5)
    2379              :       CALL parallel_gemm('N', 'N', n_RI, n_RI, n_RI, 1.0_dp, &
    2380          550 :                          fm_chi_freq_j, fm_Minv_Vsqrt, 0.0_dp, fm_work)
    2381              : 
    2382              :       ! eps_work = (M^-1 * V^0.5)^T * work
    2383              :       CALL parallel_gemm('T', 'N', n_RI, n_RI, n_RI, 1.0_dp, &
    2384          550 :                          fm_Minv_Vsqrt, fm_work, 0.0_dp, fm_eps_freq_j)
    2385              : 
    2386              :       ! ε(iω_j) = Id - eps_work  -->  -eps_work + Id
    2387          550 :       CALL fm_add_on_diag(fm_eps_freq_j, 1.0_dp)
    2388              : 
    2389              :       ! Force perfect symmetry before Cholesky to avoid info != 0 due to GEMM noise
    2390          550 :       CALL cp_fm_uplo_to_full(fm_eps_freq_j, fm_work)
    2391              : 
    2392              :       ! -----------------------------------------------------------------------
    2393              :       ! 2. W(iω_j) = V^0.5^T * (ε^-1(iω_j) - Id) * V^0.5
    2394              :       ! -----------------------------------------------------------------------
    2395              : 
    2396              :       ! a) Invert ε by Cholesky decomposition or, if that fails, by diagonalization.
    2397          550 :       CALL fm_invert(fm_eps_freq_j, bs_env%eps_eigval_mat_RI, bs_env%unit_nr)
    2398              : 
    2399              :       ! b) ε^-1(iω_j) - Id
    2400          550 :       CALL fm_add_on_diag(fm_eps_freq_j, -1.0_dp)
    2401              : 
    2402              :       ! c) W(iω_j) = V^0.5^T * (ε^-1(iω_j) - Id) * V^0.5
    2403          550 :       CALL fm_contract_ABA(fm_V_sqrt, fm_eps_freq_j, fm_W_freq_j)
    2404              : 
    2405              :       ! Cleanup
    2406          550 :       CALL cp_fm_release(fm_work)
    2407          550 :       CALL cp_fm_release(fm_eps_freq_j)
    2408              : 
    2409          550 :       CALL timestop(handle)
    2410              : 
    2411          550 :    END SUBROUTINE compute_fm_W_freq
    2412              : 
    2413              : ! **************************************************************************************************
    2414              : !> \brief Adds a real scalar value to the diagonal of a real full matrix
    2415              : !> \param fm ...
    2416              : !> \param alpha ...
    2417              : ! **************************************************************************************************
    2418         2200 :    SUBROUTINE fm_add_on_diag(fm, alpha)
    2419              :       TYPE(cp_fm_type), INTENT(INOUT)                    :: fm
    2420              :       REAL(KIND=dp), INTENT(IN)                          :: alpha
    2421              : 
    2422              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'fm_add_on_diag'
    2423              : 
    2424              :       INTEGER                                            :: handle, i_global, i_row, j_col, &
    2425              :                                                             j_global, ncol_local, nrow_local
    2426         1100 :       INTEGER, DIMENSION(:), POINTER                     :: col_indices, row_indices
    2427              : 
    2428         1100 :       CALL timeset(routineN, handle)
    2429              : 
    2430              :       CALL cp_fm_get_info(matrix=fm, &
    2431              :                           nrow_local=nrow_local, &
    2432              :                           ncol_local=ncol_local, &
    2433              :                           row_indices=row_indices, &
    2434         1100 :                           col_indices=col_indices)
    2435              : 
    2436        37760 :       DO j_col = 1, ncol_local
    2437        36660 :          j_global = col_indices(j_col)
    2438       742290 :          DO i_row = 1, nrow_local
    2439       704530 :             i_global = row_indices(i_row)
    2440       741190 :             IF (j_global == i_global) THEN
    2441        18330 :                fm%local_data(i_row, j_col) = fm%local_data(i_row, j_col) + alpha
    2442              :             END IF
    2443              :          END DO
    2444              :       END DO
    2445              : 
    2446         1100 :       CALL timestop(handle)
    2447              : 
    2448         1100 :    END SUBROUTINE fm_add_on_diag
    2449              : 
    2450              : ! **************************************************************************************************
    2451              : !> \brief Computes the exact-exchange part of the GW self-energy:
    2452              : !>          D_μν        = Σ_n^occ C_μn C_νn                     (density matrix = G^occ at τ=0)
    2453              : !>          V^tr_PQ     = M^-1 (P|Q)_trunc M^-1                 (truncated Coulomb, RI basis)
    2454              : !>          Σ^x_λσ(k=0) = -Σ_ll' ϕ_λ(r_l) [ (ϕ D ϕ^T)_ll' ∘ (Z V^tr Z^T)_ll' ] ϕ_σ(r_l')
    2455              : !> \param bs_env ...
    2456              : !> \param qs_env ...
    2457              : !> \param mat_phi_mu_l ...
    2458              : !> \param mat_Z_lP ...
    2459              : !> \param fm_Sigma_x_Gamma ...
    2460              : ! **************************************************************************************************
    2461           40 :    SUBROUTINE compute_Sigma_x(bs_env, qs_env, mat_phi_mu_l, mat_Z_lP, fm_Sigma_x_Gamma)
    2462              : 
    2463              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2464              :       TYPE(qs_environment_type), POINTER                 :: qs_env
    2465              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_phi_mu_l, mat_Z_lP
    2466              :       TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:)        :: fm_Sigma_x_Gamma
    2467              : 
    2468              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'compute_Sigma_x'
    2469              : 
    2470              :       INTEGER                                            :: handle, ispin
    2471           40 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: pan_first, pan_last
    2472           40 :       INTEGER, DIMENSION(:), POINTER                     :: blk_aux, dist_row_aux
    2473              :       REAL(KIND=dp)                                      :: t1
    2474           40 :       TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:, :)     :: fm_Vtr_Gamma
    2475              :       TYPE(dbcsr_distribution_type)                      :: dist_aux_aux
    2476              :       TYPE(dbcsr_type)                                   :: mat_Sigma_x_Gamma, matrix_D_ao, &
    2477              :                                                             matrix_V_aux
    2478              : 
    2479           40 :       CALL timeset(routineN, handle)
    2480              : 
    2481           40 :       t1 = m_walltime()
    2482              : 
    2483          166 :       ALLOCATE (fm_Sigma_x_Gamma(bs_env%n_spin))
    2484           86 :       DO ispin = 1, bs_env%n_spin
    2485           86 :          CALL cp_fm_create(fm_Sigma_x_Gamma(ispin), bs_env%fm_s_Gamma%matrix_struct)
    2486              :       END DO
    2487              : 
    2488           40 :       CALL dbcsr_create(mat_Sigma_x_Gamma, template=bs_env%mat_ao_ao%matrix)
    2489              : 
    2490           40 :       CALL resolve_grid_panels(bs_env, mat_phi_mu_l, pan_first, pan_last)
    2491              : 
    2492              :       ! =========================================================================
    2493              :       ! 1. COMPUTE V^tr_PQ (RI x RI)
    2494              :       ! =========================================================================
    2495           40 :       CALL setup_square_topology(mat_Z_lP, dist_aux_aux, blk_aux, dist_row_aux)
    2496              : 
    2497           40 :       IF (bs_env%auto_ri%enabled) THEN
    2498           12 :          ALLOCATE (fm_Vtr_Gamma(1, 1))
    2499            4 :          CALL cp_fm_create(fm_Vtr_Gamma(1, 1), bs_env%fm_RI_RI%matrix_struct)
    2500            4 :          CALL cp_fm_to_fm(bs_env%auto_ri%V_pq, fm_Vtr_Gamma(1, 1))
    2501              :       ELSE
    2502              :          CALL RI_2c_integral_mat(qs_env, fm_Vtr_Gamma, bs_env%fm_RI_RI, bs_env%n_RI, &
    2503           36 :                                  bs_env%trunc_coulomb, do_kpoints=.FALSE.)
    2504              :       END IF
    2505              : 
    2506              :       ! M^-1(k=0) V^tr(τ) M^-1(k=0) -> fm_Vtr_Gamma
    2507           40 :       CALL fm_contract_ABA(bs_env%fm_Minv_Gamma, fm_Vtr_Gamma(:, 1))
    2508              : 
    2509           40 :       CALL dbcsr_create(matrix_V_aux, "V_aux", dist_aux_aux, dbcsr_type_no_symmetry, blk_aux, blk_aux)
    2510              :       ! Optional CUTOFF_RADIUS_G_W operator truncation + filter (see build_G_ao)
    2511           40 :       IF (bs_env%ri_rs%cutoff_radius_g_w > 0.0_dp .AND. ALLOCATED(bs_env%ri_rs%atom_centers)) THEN
    2512              :          CALL reserve_blocks_within_radius(matrix_V_aux, bs_env%ri_rs%atom_centers, &
    2513            0 :                                            bs_env%ri_rs%cutoff_radius_g_w)
    2514            0 :          CALL copy_fm_to_dbcsr(fm_Vtr_Gamma(1, 1), matrix_V_aux, keep_sparsity=.TRUE.)
    2515              :       ELSE
    2516           40 :          CALL copy_fm_to_dbcsr(fm_Vtr_Gamma(1, 1), matrix_V_aux, keep_sparsity=.FALSE.)
    2517              :       END IF
    2518           40 :       CALL dbcsr_filter(matrix_V_aux, bs_env%eps_filter)
    2519              : 
    2520              :       ! =========================================================================
    2521              :       ! 2. SPIN LOOP FOR EXACT EXCHANGE
    2522              :       ! Σ^x_λσ = -Σ_ll' ϕ_λ(r_l) ( D_ll' V^tr_ll' ) ϕ_σ(r_l')
    2523              :       !        = -ϕ^T ( (ϕ D ϕ^T) ∘ (Z V^tr Z^T) ) ϕ
    2524              :       ! =========================================================================
    2525           86 :       DO ispin = 1, bs_env%n_spin
    2526              : 
    2527              :          ! AO-space density matrix D_µν = G^occ at τ = 0
    2528           46 :          CALL build_G_ao(bs_env, 0.0_dp, ispin, .TRUE., .FALSE., mat_phi_mu_l, matrix_D_ao)
    2529              : 
    2530              :          CALL contract_grid_panels(L_A=mat_phi_mu_l, M_A=matrix_D_ao, &
    2531              :                                    L_B=mat_Z_lP, M_B=matrix_V_aux, &
    2532              :                                    L_out=mat_phi_mu_l, mat_out=mat_Sigma_x_Gamma, &
    2533              :                                    scale=1.0_dp, eps=bs_env%eps_filter, &
    2534              :                                    para_env=bs_env%para_env, &
    2535              :                                    pan_first=pan_first, pan_last=pan_last, &
    2536              :                                    lb_eq_la=.FALSE., lout_eq_la=.TRUE., zero_out=.TRUE., &
    2537              :                                    keep_sparsity=bs_env%ri_rs%keep_sparsity_rirs, &
    2538              :                                    centroids=bs_env%ri_rs%chunk_centroids, &
    2539           46 :                                    cutoff=bs_env%ri_rs%cutoff_radius_v_w)
    2540           46 :          CALL dbcsr_scale(mat_Sigma_x_Gamma, -1.0_dp)
    2541              : 
    2542           46 :          CALL dbcsr_release(matrix_D_ao)
    2543              : 
    2544              :          ! Data I/O and Export to CP2K Full Matrices
    2545           86 :          CALL copy_dbcsr_to_fm(mat_Sigma_x_Gamma, fm_Sigma_x_Gamma(ispin))
    2546              : 
    2547              :       END DO ! ispin
    2548              : 
    2549           40 :       IF (bs_env%unit_nr > 0) THEN
    2550              :          WRITE (bs_env%unit_nr, '(T2,A,T58,A,F7.1,A)') &
    2551           20 :             'Computed Σ^x(k=0),', ' Execution time', m_walltime() - t1, ' s'
    2552           20 :          WRITE (bs_env%unit_nr, '(A)') ' '
    2553              :       END IF
    2554              : 
    2555              :       ! =========================================================================
    2556              :       ! 3. CLEANUP
    2557              :       ! =========================================================================
    2558           40 :       CALL dbcsr_release(matrix_V_aux)
    2559           40 :       CALL dbcsr_release(mat_Sigma_x_Gamma)
    2560           40 :       CALL release_square_topology(dist=dist_aux_aux, mapped_dist=dist_row_aux)
    2561              : 
    2562           40 :       CALL cp_fm_release(fm_Vtr_Gamma)
    2563              : 
    2564           40 :       CALL timestop(handle)
    2565              : 
    2566          120 :    END SUBROUTINE compute_Sigma_x
    2567              : 
    2568              : ! **************************************************************************************************
    2569              : !> \brief Computes the correlation part of the GW self-energy on the imaginary-time grid:
    2570              : !>          Σ^c_λσ(iτ<0) = -Σ_ll' ϕ_λ(r_l) [ (ϕ G^occ ϕ^T)_ll' ∘ (Z W^MIC Z^T)_ll' ] ϕ_σ(r_l')
    2571              : !>          Σ^c_λσ(iτ>0) = +Σ_ll' ϕ_λ(r_l) [ (ϕ G^vir ϕ^T)_ll' ∘ (Z W^MIC Z^T)_ll' ] ϕ_σ(r_l')
    2572              : !> \param bs_env ...
    2573              : !> \param fm_W_time ...
    2574              : !> \param mat_phi_mu_l ...
    2575              : !> \param mat_Z_lP ...
    2576              : !> \param fm_Sigma_c_Gamma_time ...
    2577              : ! **************************************************************************************************
    2578           54 :    SUBROUTINE compute_Sigma_c(bs_env, fm_W_time, mat_phi_mu_l, mat_Z_lP, fm_Sigma_c_Gamma_time)
    2579              : 
    2580              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2581              :       TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:)        :: fm_W_time
    2582              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_phi_mu_l, mat_Z_lP
    2583              :       TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:, :, :)  :: fm_Sigma_c_Gamma_time
    2584              : 
    2585              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'compute_Sigma_c'
    2586              : 
    2587              :       INTEGER                                            :: handle, i_t, ispin
    2588           54 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: pan_first, pan_last
    2589           54 :       INTEGER, DIMENSION(:), POINTER                     :: blk_aux, dist_row_aux
    2590              :       REAL(KIND=dp)                                      :: t1, tau
    2591              :       TYPE(dbcsr_distribution_type)                      :: dist_aux_aux
    2592           54 :       TYPE(dbcsr_p_type), DIMENSION(:, :), POINTER       :: mat_Sigma_neg_tau, mat_Sigma_pos_tau
    2593              :       TYPE(dbcsr_type)                                   :: matrix_G_occ_ao, matrix_G_vir_ao, &
    2594              :                                                             matrix_W_aux
    2595              : 
    2596           54 :       CALL timeset(routineN, handle)
    2597              : 
    2598              :       ! =========================================================================
    2599              :       ! 1. SETUP AUXILIARY TOPOLOGY AND PRE-ALLOCATE OUTPUT ARRAYS
    2600              :       ! =========================================================================
    2601           54 :       CALL setup_square_topology(mat_Z_lP, dist_aux_aux, blk_aux, dist_row_aux)
    2602              : 
    2603           54 :       CALL resolve_grid_panels(bs_env, mat_phi_mu_l, pan_first, pan_last)
    2604              : 
    2605              :       ! Pre-allocate local DBCSR matrices to act as targets for final output
    2606           54 :       NULLIFY (mat_Sigma_neg_tau, mat_Sigma_pos_tau)
    2607         1112 :       ALLOCATE (mat_Sigma_neg_tau(bs_env%num_time_freq_points, bs_env%n_spin))
    2608         1112 :       ALLOCATE (mat_Sigma_pos_tau(bs_env%num_time_freq_points, bs_env%n_spin))
    2609              : 
    2610          744 :       DO i_t = 1, bs_env%num_time_freq_points
    2611         1574 :          DO ispin = 1, bs_env%n_spin
    2612          830 :             ALLOCATE (mat_Sigma_neg_tau(i_t, ispin)%matrix)
    2613          830 :             ALLOCATE (mat_Sigma_pos_tau(i_t, ispin)%matrix)
    2614          830 :             CALL dbcsr_create(mat_Sigma_neg_tau(i_t, ispin)%matrix, template=bs_env%mat_ao_ao%matrix)
    2615         1520 :             CALL dbcsr_create(mat_Sigma_pos_tau(i_t, ispin)%matrix, template=bs_env%mat_ao_ao%matrix)
    2616              :          END DO
    2617              :       END DO
    2618              : 
    2619              :       ! =========================================================================
    2620              :       ! 2. IMAGINARY TIME LOOP
    2621              :       ! Σ^c_neg_λσ(iτ) = -ϕ^T ( (ϕ G^occ ϕ^T) ∘ (Z W^MIC Z^T) ) ϕ
    2622              :       ! Σ^c_pos_λσ(iτ) =  ϕ^T ( (ϕ G^vir ϕ^T) ∘ (Z W^MIC Z^T) ) ϕ
    2623              :       ! =========================================================================
    2624          744 :       DO i_t = 1, bs_env%num_time_freq_points
    2625          690 :          tau = bs_env%time_frequency_grid%imaginary_time(i_t)
    2626              : 
    2627              :          CALL dbcsr_create(matrix_W_aux, "W_aux", dist_aux_aux, dbcsr_type_no_symmetry, &
    2628          690 :                            blk_aux, blk_aux)
    2629          690 :          IF (bs_env%ri_rs%cutoff_radius_g_w > 0.0_dp .AND. ALLOCATED(bs_env%ri_rs%atom_centers)) THEN
    2630              :             CALL reserve_blocks_within_radius(matrix_W_aux, bs_env%ri_rs%atom_centers, &
    2631            0 :                                               bs_env%ri_rs%cutoff_radius_g_w)
    2632            0 :             CALL copy_fm_to_dbcsr(fm_W_time(i_t), matrix_W_aux, keep_sparsity=.TRUE.)
    2633              :          ELSE
    2634          690 :             CALL copy_fm_to_dbcsr(fm_W_time(i_t), matrix_W_aux, keep_sparsity=.FALSE.)
    2635              :          END IF
    2636          690 :          CALL dbcsr_filter(matrix_W_aux, bs_env%eps_filter)
    2637              : 
    2638         1520 :          DO ispin = 1, bs_env%n_spin
    2639          830 :             t1 = m_walltime()
    2640              : 
    2641              :             ! AO-space Green's functions G^occ_µν, G^vir_µν (dense AO x AO, small)
    2642          830 :             CALL build_G_ao(bs_env, tau, ispin, .TRUE., .FALSE., mat_phi_mu_l, matrix_G_occ_ao)
    2643          830 :             CALL build_G_ao(bs_env, tau, ispin, .FALSE., .TRUE., mat_phi_mu_l, matrix_G_vir_ao)
    2644              : 
    2645              :             ! Σ^c_neg and Σ^c_pos in a single panel loop: W_pan = Z_panel × W × Z^T built once
    2646              :             CALL contract_grid_panels_sigma_c(mat_phi=mat_phi_mu_l, mat_Z=mat_Z_lP, &
    2647              :                                               mat_G_occ_ao=matrix_G_occ_ao, &
    2648              :                                               mat_G_vir_ao=matrix_G_vir_ao, &
    2649              :                                               mat_W_aux=matrix_W_aux, &
    2650              :                                               mat_Sigma_neg=mat_Sigma_neg_tau(i_t, ispin)%matrix, &
    2651              :                                               mat_Sigma_pos=mat_Sigma_pos_tau(i_t, ispin)%matrix, &
    2652              :                                               eps=bs_env%eps_filter, &
    2653              :                                               para_env=bs_env%para_env, &
    2654              :                                               pan_first=pan_first, pan_last=pan_last, &
    2655              :                                               keep_sparsity=bs_env%ri_rs%keep_sparsity_rirs, &
    2656              :                                               centroids=bs_env%ri_rs%chunk_centroids, &
    2657          830 :                                               cutoff=bs_env%ri_rs%cutoff_radius_v_w)
    2658          830 :             CALL dbcsr_scale(mat_Sigma_neg_tau(i_t, ispin)%matrix, -1.0_dp)
    2659              : 
    2660          830 :             CALL dbcsr_release(matrix_G_occ_ao)
    2661          830 :             CALL dbcsr_release(matrix_G_vir_ao)
    2662              : 
    2663         1520 :             IF (bs_env%unit_nr > 0) THEN
    2664              :                WRITE (bs_env%unit_nr, '(T2,A,I15,A,I3,A,F7.1,A)') &
    2665          415 :                   'Computed Σ^c(iτ) for time point', i_t, ' /', bs_env%num_time_freq_points, &
    2666          830 :                   ',    Execution time', m_walltime() - t1, ' s'
    2667              :             END IF
    2668              : 
    2669              :          END DO ! ispin
    2670              : 
    2671          744 :          CALL dbcsr_release(matrix_W_aux)
    2672              : 
    2673              :       END DO ! i_t
    2674              : 
    2675              :       ! -------------------------------------------------------------------------
    2676              :       ! 3. FINALIZE AND CLEANUP
    2677              :       ! -------------------------------------------------------------------------
    2678              :       CALL fill_fm_Sigma_c_Gamma_time(fm_Sigma_c_Gamma_time, bs_env, &
    2679           54 :                                       mat_Sigma_pos_tau, mat_Sigma_neg_tau)
    2680              : 
    2681              :       ! fm_W_time and the scratch files are released by the caller: in an evGW0 cycle this
    2682              :       ! routine is entered once per iteration and both have to survive until it is done.
    2683           54 :       CALL dbcsr_deallocate_matrix_set(mat_Sigma_neg_tau)
    2684           54 :       CALL dbcsr_deallocate_matrix_set(mat_Sigma_pos_tau)
    2685              : 
    2686           54 :       CALL release_square_topology(dist=dist_aux_aux, mapped_dist=dist_row_aux)
    2687              : 
    2688           54 :       CALL timestop(handle)
    2689              : 
    2690          108 :    END SUBROUTINE compute_Sigma_c
    2691              : 
    2692              : ! **************************************************************************************************
    2693              : !> \brief Builds the DBCSR distribution.
    2694              : !> \param matrix_template ...
    2695              : !> \param square_dist ...
    2696              : !> \param blk_sizes ...
    2697              : !> \param mapped_dist ...
    2698              : ! **************************************************************************************************
    2699         3060 :    SUBROUTINE setup_square_topology(matrix_template, square_dist, blk_sizes, mapped_dist)
    2700              : 
    2701              :       TYPE(dbcsr_type), INTENT(IN)                       :: matrix_template
    2702              :       TYPE(dbcsr_distribution_type), INTENT(OUT)         :: square_dist
    2703              :       INTEGER, DIMENSION(:), INTENT(OUT), POINTER        :: blk_sizes, mapped_dist
    2704              : 
    2705              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'setup_square_topology'
    2706              : 
    2707              :       INTEGER                                            :: handle, i, nprows
    2708         3060 :       INTEGER, DIMENSION(:), POINTER                     :: col_blk, col_dist
    2709              :       TYPE(dbcsr_distribution_type)                      :: dist_template
    2710              : 
    2711         3060 :       CALL timeset(routineN, handle)
    2712              : 
    2713         3060 :       CALL dbcsr_get_info(matrix_template, distribution=dist_template, col_blk_size=col_blk)
    2714         3060 :       CALL dbcsr_distribution_get(dist_template, col_dist=col_dist, nprows=nprows)
    2715              : 
    2716         3060 :       blk_sizes => col_blk
    2717         9180 :       ALLOCATE (mapped_dist(SIZE(blk_sizes)))
    2718        10334 :       DO i = 1, SIZE(blk_sizes)
    2719        10334 :          mapped_dist(i) = MOD(i - 1, nprows)
    2720              :       END DO
    2721              :       CALL dbcsr_distribution_new(square_dist, template=dist_template, &
    2722         3060 :                                   row_dist=mapped_dist, col_dist=col_dist)
    2723              : 
    2724         3060 :       CALL timestop(handle)
    2725              : 
    2726         6120 :    END SUBROUTINE setup_square_topology
    2727              : 
    2728              : ! **************************************************************************************************
    2729              : !> \brief Releases a distribution created by setup_square_topology.
    2730              : !> \param dist ...
    2731              : !> \param mapped_dist ...
    2732              : ! **************************************************************************************************
    2733         3060 :    SUBROUTINE release_square_topology(dist, mapped_dist)
    2734              : 
    2735              :       TYPE(dbcsr_distribution_type), INTENT(INOUT)       :: dist
    2736              :       INTEGER, DIMENSION(:), INTENT(INOUT), POINTER      :: mapped_dist
    2737              : 
    2738         3060 :       CALL dbcsr_distribution_release(dist)
    2739         3060 :       IF (ASSOCIATED(mapped_dist)) THEN
    2740         3060 :          DEALLOCATE (mapped_dist)
    2741              :          NULLIFY (mapped_dist)
    2742              :       END IF
    2743              : 
    2744         3060 :    END SUBROUTINE release_square_topology
    2745              : 
    2746              : ! **************************************************************************************************
    2747              : !> \brief Σ^c_λσ(iτ) -> Σ^c_nn(ϵ) and the quasi-particle levels of the non-periodic RI-RS path,
    2748              : !>        ϵ_n^GW = ϵ_n^DFT + Σ^c_nn(ϵ_n^GW) + Σ^x_nn - v^xc_nn.
    2749              : !> \param bs_env ...
    2750              : !> \param fm_Sigma_x_Gamma ...
    2751              : !> \param fm_Sigma_c_Gamma_time ...
    2752              : ! **************************************************************************************************
    2753           54 :    SUBROUTINE compute_QP_energies(bs_env, fm_Sigma_x_Gamma, fm_Sigma_c_Gamma_time)
    2754              : 
    2755              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2756              :       TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:)        :: fm_Sigma_x_Gamma
    2757              :       TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:, :, :)  :: fm_Sigma_c_Gamma_time
    2758              : 
    2759              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_QP_energies'
    2760              : 
    2761              :       INTEGER                                            :: handle, ispin, j_t
    2762              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: Sigma_x_n, V_xc_n
    2763              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: Sigma_c_n_freq, Sigma_c_n_time
    2764              :       TYPE(cp_fm_type)                                   :: fm_ks, fm_mos, fm_s, fm_work
    2765              : 
    2766           54 :       CALL timeset(routineN, handle)
    2767              : 
    2768           54 :       CALL cp_fm_create(fm_ks, bs_env%fm_s_Gamma%matrix_struct)
    2769           54 :       CALL cp_fm_create(fm_s, bs_env%fm_s_Gamma%matrix_struct)
    2770           54 :       CALL cp_fm_create(fm_mos, bs_env%fm_s_Gamma%matrix_struct)
    2771           54 :       CALL cp_fm_create(fm_work, bs_env%fm_s_Gamma%matrix_struct)
    2772              : 
    2773          216 :       ALLOCATE (V_xc_n(bs_env%n_ao), Sigma_x_n(bs_env%n_ao))
    2774          270 :       ALLOCATE (Sigma_c_n_time(bs_env%n_ao, bs_env%num_time_freq_points, 2))
    2775          162 :       ALLOCATE (Sigma_c_n_freq(bs_env%n_ao, bs_env%num_time_freq_points, 2))
    2776              : 
    2777          120 :       DO ispin = 1, bs_env%n_spin
    2778              : 
    2779              :          ! 1. Roothaan-Hall H^KS_µν C_νn = S_µν C_νn ϵ_n
    2780           66 :          CALL cp_fm_to_fm(bs_env%fm_ks_Gamma(ispin), fm_ks)
    2781           66 :          CALL cp_fm_to_fm(bs_env%fm_s_Gamma, fm_s)
    2782           66 :          CALL cp_fm_geeig(fm_ks, fm_s, fm_mos, bs_env%eigenval_scf(:, 1, ispin), fm_work)
    2783              : 
    2784              :          ! 2. v^xc_µν -> v^xc_nn  and  Σ^x_µν -> Σ^x_nn
    2785           66 :          CALL to_Gamma_and_mo_real(V_xc_n, bs_env%fm_V_xc_Gamma(ispin), fm_mos)
    2786           66 :          CALL to_Gamma_and_mo_real(Sigma_x_n, fm_Sigma_x_Gamma(ispin), fm_mos)
    2787              : 
    2788              :          ! 3. Σ^c_µν(+/-i|τ_j|) -> Σ^c_nn(+/-i|τ_j|)
    2789          896 :          DO j_t = 1, bs_env%num_time_freq_points
    2790              :             CALL to_Gamma_and_mo_real(Sigma_c_n_time(:, j_t, 1), &
    2791          830 :                                       fm_Sigma_c_Gamma_time(j_t, 1, ispin), fm_mos)
    2792              :             CALL to_Gamma_and_mo_real(Sigma_c_n_time(:, j_t, 2), &
    2793          896 :                                       fm_Sigma_c_Gamma_time(j_t, 2, ispin), fm_mos)
    2794              :          END DO
    2795              : 
    2796              :          ! 4. Σ^c_nn(iτ) -> Σ^c_nn(iω)
    2797           66 :          CALL time_to_freq(bs_env, Sigma_c_n_time, Sigma_c_n_freq, ispin)
    2798              : 
    2799              :          ! 5. Analytic continuation Σ^c_nn(iω) -> Σ^c_nn(ϵ) and the QP levels
    2800              :          CALL analyt_conti_and_print(bs_env, Sigma_c_n_freq, Sigma_x_n, V_xc_n, &
    2801          120 :                                      bs_env%eigenval_scf(:, 1, ispin), 1, ispin)
    2802              : 
    2803              :       END DO ! ispin
    2804              : 
    2805           54 :       CALL get_all_VBM_CBM_bandgaps(bs_env)
    2806              : 
    2807           54 :       IF (bs_env%gw_flavour == G0W0) CALL cp_fm_release(fm_Sigma_x_Gamma)
    2808           54 :       CALL cp_fm_release(fm_Sigma_c_Gamma_time)
    2809              : 
    2810           54 :       CALL cp_fm_release(fm_ks)
    2811           54 :       CALL cp_fm_release(fm_s)
    2812           54 :       CALL cp_fm_release(fm_mos)
    2813           54 :       CALL cp_fm_release(fm_work)
    2814              : 
    2815           54 :       CALL timestop(handle)
    2816              : 
    2817          108 :    END SUBROUTINE compute_QP_energies
    2818              : 
    2819              : ! **************************************************************************************************
    2820              : !> \brief AO -> MO transform of a Γ-point matrix
    2821              : !> \param array_n ...
    2822              : !> \param fm_Gamma ...
    2823              : !> \param fm_mos ...
    2824              : ! **************************************************************************************************
    2825         1792 :    SUBROUTINE to_Gamma_and_mo_real(array_n, fm_Gamma, fm_mos)
    2826              : 
    2827              :       REAL(KIND=dp), DIMENSION(:)                        :: array_n
    2828              :       TYPE(cp_fm_type)                                   :: fm_Gamma, fm_mos
    2829              : 
    2830              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'to_Gamma_and_mo_real'
    2831              : 
    2832              :       INTEGER                                            :: handle
    2833              :       TYPE(cp_fm_type)                                   :: fm_mo
    2834              : 
    2835         1792 :       CALL timeset(routineN, handle)
    2836              : 
    2837         1792 :       CALL cp_fm_create(fm_mo, fm_Gamma%matrix_struct)
    2838              : 
    2839              :       ! A_nn' = Σ_μν C_μn A_μν C_νn'
    2840         1792 :       CALL fm_contract_ABA(fm_mos, fm_Gamma, fm_mo)
    2841              : 
    2842         1792 :       CALL cp_fm_get_diag(fm_mo, array_n)
    2843              : 
    2844         1792 :       CALL cp_fm_release(fm_mo)
    2845              : 
    2846         1792 :       CALL timestop(handle)
    2847              : 
    2848         1792 :    END SUBROUTINE to_Gamma_and_mo_real
    2849              : 
    2850              : ! **************************************************************************************************
    2851              : !> \brief Counts the AO functions whose radial support intersects an RI fitting sphere.
    2852              : !> \param bs_env ...
    2853              : !> \param atom_P Atom at the center of the RI fitting sphere
    2854              : !> \param cutoff_ri Radius of the RI fitting sphere
    2855              : !> \param n_ao_used Number of intersecting AO functions
    2856              : ! **************************************************************************************************
    2857          102 :    SUBROUTINE get_n_ao_in_sphere(bs_env, atom_P, cutoff_ri, n_ao_used)
    2858              : 
    2859              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2860              :       INTEGER, INTENT(IN)                                :: atom_P
    2861              :       REAL(KIND=dp), INTENT(IN)                          :: cutoff_ri
    2862              :       INTEGER, INTENT(OUT)                               :: n_ao_used
    2863              : 
    2864              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'get_n_ao_in_sphere'
    2865              : 
    2866              :       INTEGER                                            :: handle, ri_atom
    2867              : 
    2868          102 :       CALL timeset(routineN, handle)
    2869              : 
    2870          102 :       n_ao_used = 0
    2871          372 :       DO ri_atom = 1, bs_env%n_atom
    2872         1080 :          IF (NORM2(bs_env%ri_rs%particle_set(ri_atom)%r(:) - &
    2873              :                    bs_env%ri_rs%particle_set(atom_P)%r(:)) > &
    2874              :              bs_env%ri_rs%radius_ao_per_atom(ri_atom) + cutoff_ri) CYCLE
    2875              :          n_ao_used = n_ao_used + bs_env%i_ao_end_from_atom(ri_atom) - &
    2876          372 :                      bs_env%i_ao_start_from_atom(ri_atom) + 1
    2877              :       END DO
    2878              : 
    2879          102 :       CALL timestop(handle)
    2880              : 
    2881          102 :    END SUBROUTINE get_n_ao_in_sphere
    2882              : 
    2883              : ! **************************************************************************************************
    2884              : !> \brief Prints the percentage of non-zero elements in a distributed RI-RS matrix.
    2885              : !> \param matrix Distributed matrix whose occupation is reported
    2886              : !> \param label Mathematical matrix label used in the output
    2887              : !> \param bs_env ...
    2888              : !> \param suffix Optional text appended to the matrix label
    2889              : ! **************************************************************************************************
    2890          128 :    SUBROUTINE print_matrix_occupation(matrix, label, bs_env, suffix)
    2891              : 
    2892              :       TYPE(dbcsr_type), INTENT(IN)                       :: matrix
    2893              :       CHARACTER(LEN=*), INTENT(IN)                       :: label
    2894              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2895              :       CHARACTER(LEN=*), INTENT(IN), OPTIONAL             :: suffix
    2896              : 
    2897              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'print_matrix_occupation'
    2898              : 
    2899              :       CHARACTER(LEN=32)                                  :: output_format
    2900              :       CHARACTER(LEN=max_line_length)                     :: msg, output_label
    2901              :       INTEGER                                            :: handle, i, unicode_shift
    2902              :       REAL(KIND=dp)                                      :: frac_2p31, max_loc, occ
    2903              : 
    2904          128 :       CALL timeset(routineN, handle)
    2905              : 
    2906          128 :       occ = dbcsr_get_occupation(matrix)
    2907          128 :       max_loc = REAL(dbcsr_get_data_size(matrix), dp)
    2908          128 :       CALL bs_env%para_env%max(max_loc)
    2909              : 
    2910          128 :       IF (bs_env%unit_nr > 0) THEN
    2911           64 :          frac_2p31 = max_loc/REAL(bs_env%dbcsr_msg_elem_limit, dp)
    2912           64 :          output_label = 'Percentage of non-zero matrix elements in '//TRIM(label)
    2913           64 :          IF (PRESENT(suffix)) output_label = TRIM(output_label)//TRIM(suffix)
    2914              :          ! Fortran counts UTF-8 bytes, whereas the terminal displays each Greek letter in one
    2915              :          ! column. Shift the absolute output tab once for every continuation byte.
    2916           64 :          unicode_shift = 0
    2917         3512 :          DO i = 1, LEN_TRIM(output_label)
    2918         3512 :             IF (IAND(IACHAR(output_label(i:i)), 192) == 128) unicode_shift = unicode_shift + 1
    2919              :          END DO
    2920           64 :          WRITE (output_format, '(A,I0,A)') '(T2,A,T', 72 + unicode_shift, ',F7.2,A)'
    2921           64 :          WRITE (bs_env%unit_nr, output_format) TRIM(output_label), occ*100.0_dp, ' %'
    2922           64 :          IF (frac_2p31 > 0.5_dp) THEN
    2923              :             WRITE (msg, '(3A,F0.2,A)') &
    2924            0 :                "The largest per-rank message of ", TRIM(label), " reaches ", frac_2p31, &
    2925              :                " of the 32-bit limit that DBCSR uses for its message length. Beyond it the "// &
    2926              :                "length overflows and multiply_cannon fails. Reduce the per-rank block size, "// &
    2927            0 :                "for instance with more MPI ranks or a larger N_PANELS."
    2928            0 :             CPWARN(TRIM(msg))
    2929              :          END IF
    2930           64 :          CALL m_flush(bs_env%unit_nr)
    2931              :       END IF
    2932              : 
    2933          128 :       CALL timestop(handle)
    2934              : 
    2935          128 :    END SUBROUTINE print_matrix_occupation
    2936              : 
    2937              : END MODULE gw_ri_rs_non_periodic
        

Generated by: LCOV version 2.0-1