LCOV - code coverage report
Current view: top level - src - gw_ri_rs_non_periodic.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:9ccafda) Lines: 97.5 % 972 948
Test Date: 2026-10-15 00:55:44 Functions: 100.0 % 34 34

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

Generated by: LCOV version 2.0-1