LCOV - code coverage report
Current view: top level - src - gw_non_periodic_ri_rs.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:71c3ab0) Lines: 86.4 % 1528 1320
Test Date: 2026-07-25 06:35:44 Functions: 93.8 % 48 45

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

Generated by: LCOV version 2.0-1