LCOV - code coverage report
Current view: top level - src - gw_ri_rs_compute_Z_lP.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:92574dc) Lines: 58.7 % 1129 663
Test Date: 2026-09-24 01:27:39 Functions: 78.6 % 28 22

            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 Computes the RI-RS fitting matrix Z_lP.
      10              : !> \par History
      11              : !>      09.2026 created Jan Wilhelm
      12              : !>      09.2026 moved code by Ritaj Tyagi from gw_ri_rs_non_periodic.F
      13              : !>      09.2026 added routine to compute Z_lP for automatic RI optimization
      14              : ! **************************************************************************************************
      15              : MODULE gw_ri_rs_compute_Z_lP
      16              :    USE ai_contraction_sphi,             ONLY: abc_contract_xsmm
      17              :    USE atomic_kind_types,               ONLY: atomic_kind_type
      18              :    USE basis_set_types,                 ONLY: get_gto_basis_set
      19              :    USE cell_types,                      ONLY: cell_type,&
      20              :                                               pbc
      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_binary_read, dbcsr_binary_write, dbcsr_create, dbcsr_distribution_get, &
      26              :         dbcsr_distribution_new, dbcsr_distribution_release, dbcsr_distribution_type, dbcsr_filter, &
      27              :         dbcsr_finalize, dbcsr_get_block_p, dbcsr_get_info, dbcsr_multiply, dbcsr_put_block, &
      28              :         dbcsr_release, dbcsr_set, dbcsr_type, dbcsr_type_no_symmetry
      29              :    USE cp_dbcsr_contrib,                ONLY: dbcsr_reserve_all_blocks
      30              :    USE cp_fm_struct,                    ONLY: cp_fm_struct_type
      31              :    USE cp_fm_types,                     ONLY: cp_fm_type
      32              :    USE cp_log_handling,                 ONLY: cp_get_default_logger,&
      33              :                                               cp_logger_type
      34              :    USE cp_output_handling,              ONLY: cp_p_file,&
      35              :                                               cp_print_key_should_output
      36              :    USE gw_auto_ri_types,                ONLY: auto_ri_type
      37              :    USE gw_ri_rs_compute_Z_lP_utils,     ONLY: build_gram_jacobi_blas,&
      38              :                                               build_jacobi_diag_from_phi,&
      39              :                                               scale_rows_by_diag,&
      40              :                                               solve_D_lp_distributed,&
      41              :                                               store_Z_lP_columns
      42              :    USE gw_ri_rs_utils,                  ONLY: evaluate_ao_on_points
      43              :    USE gw_utils_compute_integrals,      ONLY: build_3c_integral_block_ctx,&
      44              :                                               gw_3c_ctx_create,&
      45              :                                               gw_3c_ctx_release,&
      46              :                                               gw_3c_ctx_type,&
      47              :                                               gw_3c_ws_create,&
      48              :                                               gw_3c_ws_release,&
      49              :                                               gw_3c_ws_type
      50              :    USE input_section_types,             ONLY: section_vals_type
      51              :    USE kinds,                           ONLY: dp
      52              :    USE libint_2c_3c,                    ONLY: eri_3center
      53              :    USE machine,                         ONLY: m_flush,&
      54              :                                               m_walltime
      55              :    USE message_passing,                 ONLY: mp_mem_avail_per_rank_GB,&
      56              :                                               mp_para_env_type
      57              :    USE orbital_pointers,                ONLY: ncoset
      58              :    USE particle_types,                  ONLY: particle_type
      59              :    USE physcon,                         ONLY: angstrom
      60              :    USE post_scf_bandstructure_types,    ONLY: post_scf_bandstructure_type
      61              :    USE qs_environment_types,            ONLY: get_qs_env,&
      62              :                                               qs_environment_type
      63              :    USE qs_kind_types,                   ONLY: qs_kind_type
      64              :    USE util,                            ONLY: sort
      65              : #include "./base/base_uses.f90"
      66              : 
      67              :    IMPLICIT NONE
      68              :    PRIVATE
      69              : 
      70              :    CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'gw_ri_rs_compute_Z_lP'
      71              : 
      72              :    PUBLIC :: compute_Z_lP
      73              : 
      74              : CONTAINS
      75              : 
      76              : ! **************************************************************************************************
      77              : !> \brief Computes Z_lP for (1) a tabulated RI basis set or (2) an on-the-fly generated RI basis set.
      78              : !>
      79              : !>        In both cases, the Z_lP coefficients are computed by solving the linear system
      80              : !>
      81              : !>          Σ_l' D_ll' Z_l'P = d_lP,
      82              : !>
      83              : !>        with D_ll' and d_lP given by
      84              : !>
      85              : !>          D_ll' = [Σ_μ ϕ_μ(r_l) ϕ_μ(r_l')]²,
      86              : !>
      87              : !>          d_lP = Σ_μν ϕ_μ(r_l) ϕ_ν(r_l) (μν|P).
      88              : !>
      89              : !>        For an on-the-fly generated RI basis set, an RI function φ_P may be a contraction of
      90              : !>        Gaussians on neighboring atoms, which requires special computation of d_lP.
      91              : !>
      92              : !> \param qs_env ...
      93              : !> \param bs_env Band-structure environment containing GW parameters.
      94              : !> \param ri_rs_grid_points ...
      95              : !> \param mat_phi_mu_l ...
      96              : !> \param mat_Z_lP ...
      97              : ! **************************************************************************************************
      98           48 :    SUBROUTINE compute_Z_lP(qs_env, bs_env, ri_rs_grid_points, mat_phi_mu_l, mat_Z_lP)
      99              :       TYPE(qs_environment_type), POINTER                 :: qs_env
     100              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     101              :       REAL(KIND=dp), ALLOCATABLE, INTENT(INOUT)          :: ri_rs_grid_points(:, :)
     102              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_phi_mu_l
     103              :       TYPE(dbcsr_type), INTENT(OUT)                      :: mat_Z_lP
     104              : 
     105              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'compute_Z_lP'
     106              : 
     107              :       INTEGER                                            :: handle
     108              : 
     109           48 :       CALL timeset(routineN, handle)
     110              : 
     111           48 :       IF (bs_env%auto_ri%enabled) THEN
     112            4 :          CALL compute_Z_lP_auto_ri(qs_env, bs_env, ri_rs_grid_points, mat_phi_mu_l, mat_Z_lP)
     113              :       ELSE
     114           44 :          CALL compute_Z_lP_standard(qs_env, bs_env, ri_rs_grid_points, mat_phi_mu_l, mat_Z_lP)
     115              :       END IF
     116              : 
     117           48 :       CALL timestop(handle)
     118              : 
     119           48 :    END SUBROUTINE compute_Z_lP
     120              : 
     121              : ! **************************************************************************************************
     122              : !> \brief Computes the RI-RS fitting coefficients Z_lP by solving, independently for every RI
     123              : !>        atom P, a Jacobi-conditioned, Tikhonov-regularized linear system restricted to the
     124              : !>        grid points r_l inside P's integration sphere |r_l - R_P| <= cutoff_ri(P):
     125              : !>
     126              : !>        1.  D_ll'  = [ Σ_μ ϕ_μ(r_l) ϕ_μ(r_l') ]²
     127              : !>        2.  d_l    = 1 / sqrt(D_ll)                   (Jacobi conditioning vector)
     128              : !>        3.  D'_ll' = d_l D_ll' d_l' + λ δ_ll'         (λ = TIKHONOV regularization)
     129              : !>        4.  d_lP   = Σ_μν ϕ_μ(r_l) ϕ_ν(r_l) (μν|P)
     130              : !>        5.  Σ_l' D'_ll' Z'_l'P = d_l d_lP             (solve linear system for Z'_lP)
     131              : !>        6.  Z_lP   = d_l Z'_l'P                       (undo the conditioning)
     132              : !>
     133              : !>        Work is distributed over atoms in two phases (planned by classify_z_lp_atoms and
     134              : !>        lpt_assign_atoms): Phase A solves "small" atoms with single-rank LAPACK
     135              : !>        (dpotrf/dpotrs); Phase B solves "big" atoms, whose dense matrix D'_ll' would exceed one
     136              : !>        rank's memory, with ScaLAPACK (pdpotrf/pdpotrs) over rank subgroups of size G.
     137              : !>        The solved Z columns are scattered into the sparse global mat_Z_lP.
     138              : !>        If a Z_lP restart file exists, it is read instead and the solve is skipped entirely.
     139              : !> \param qs_env ...
     140              : !> \param bs_env ...
     141              : !> \param ri_rs_grid_points ...
     142              : !> \param mat_phi_mu_l ...
     143              : !> \param mat_Z_lP ...
     144              : ! **************************************************************************************************
     145           88 :    SUBROUTINE compute_Z_lP_standard(qs_env, bs_env, ri_rs_grid_points, mat_phi_mu_l, mat_Z_lP)
     146              : 
     147              :       TYPE(qs_environment_type), POINTER                 :: qs_env
     148              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     149              :       REAL(KIND=dp), ALLOCATABLE, INTENT(INOUT)          :: ri_rs_grid_points(:, :)
     150              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_phi_mu_l
     151              :       TYPE(dbcsr_type), INTENT(OUT)                      :: mat_Z_lP
     152              : 
     153              :       CHARACTER(LEN=*), PARAMETER :: key = 'PROPERTIES%BANDSTRUCTURE%GW%PRINT%RESTART', &
     154              :          routineN = 'compute_Z_lP_standard'
     155              : 
     156              :       INTEGER :: atom_j_mepos, atom_j_stride, atom_P, G, handle, handle_dpotrf, handle_dpotrs, &
     157              :          i_blk, iatom, idx, info, iphase, j, max_ao_size, my_group, n_ao_total, n_ao_used, n_big, &
     158              :          n_done, n_groups, n_loc_ri, n_local_grid, n_my_atoms, n_small, natom, npcol_phi, &
     159              :          num_grid_chunks, phase_hi
     160           88 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: ao_col_map, big_list, local_grid_idx, &
     161           44 :                                                             my_atoms_A, my_atoms_B, &
     162           44 :                                                             n_local_grid_atom, row_offset, &
     163           44 :                                                             small_list
     164           88 :       INTEGER, DIMENSION(:), POINTER                     :: col_dist_ri, r_blk_sizes, ri_blk_sizes, &
     165           44 :                                                             row_dist_grid
     166              :       LOGICAL                                            :: do_scatter, use_dist
     167              :       REAL(KIND=dp)                                      :: balance_A, balance_B, cutoff_ri, &
     168              :                                                             item_start_time, r_c, t1
     169           44 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: cutoff_ri_per_atom, d_vec_local
     170           44 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: D_local, d_lp_local, phi_local
     171              :       TYPE(cell_type), POINTER                           :: cell
     172              :       TYPE(cp_blacs_env_type), POINTER                   :: blacs_env_sub
     173              :       TYPE(cp_fm_struct_type), POINTER                   :: fm_struct_b, fm_struct_D
     174              :       TYPE(cp_fm_type)                                   :: fm_b, fm_D
     175              :       TYPE(cp_logger_type), POINTER                      :: logger
     176              :       TYPE(dbcsr_distribution_type)                      :: dist_phi, dist_Z
     177          572 :       TYPE(gw_3c_ctx_type)                               :: ctx_3c
     178              :       TYPE(mp_para_env_type), POINTER                    :: para_env, para_env_sub
     179           44 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
     180           44 :       TYPE(qs_kind_type), DIMENSION(:), POINTER          :: qs_kind_set
     181              :       TYPE(section_vals_type), POINTER                   :: input
     182              : 
     183           44 :       CALL timeset(routineN, handle)
     184              : 
     185           44 :       t1 = m_walltime()
     186              : 
     187              :       CALL get_qs_env(qs_env, para_env=para_env, particle_set=particle_set, input=input, &
     188           44 :                       qs_kind_set=qs_kind_set, cell=cell)
     189              : 
     190           44 :       NULLIFY (para_env_sub, blacs_env_sub)
     191              : 
     192           44 :       natom = bs_env%n_atom
     193           44 :       n_ao_total = bs_env%i_ao_end_from_atom(natom)
     194              : 
     195              :       ! =========================================================================
     196              :       ! 1. SETUP DBCSR TOPOLOGY & EXACT OFFSETS
     197              :       !    mat_Z_lP inherits the grid row blocking (and row distribution) of
     198              :       !    mat_phi_mu_l; its columns are one block per RI atom.
     199              :       ! =========================================================================
     200           44 :       CALL dbcsr_get_info(mat_phi_mu_l, row_blk_size=r_blk_sizes, distribution=dist_phi)
     201           44 :       CALL dbcsr_distribution_get(dist_phi, row_dist=row_dist_grid, npcols=npcol_phi)
     202              : 
     203           44 :       num_grid_chunks = SIZE(r_blk_sizes)
     204              : 
     205          132 :       ALLOCATE (row_offset(num_grid_chunks))
     206           44 :       row_offset(1) = 0
     207          602 :       DO i_blk = 2, num_grid_chunks
     208          602 :          row_offset(i_blk) = row_offset(i_blk - 1) + r_blk_sizes(i_blk - 1)
     209              :       END DO
     210              : 
     211          176 :       ALLOCATE (ri_blk_sizes(natom), col_dist_ri(natom))
     212          150 :       DO iatom = 1, natom
     213              :          ri_blk_sizes(iatom) = &
     214          106 :             bs_env%i_RI_end_from_atom(iatom) - bs_env%i_RI_start_from_atom(iatom) + 1
     215          150 :          col_dist_ri(iatom) = MOD(iatom - 1, npcol_phi)
     216              :       END DO
     217              : 
     218              :       CALL dbcsr_distribution_new(dist_Z, template=dist_phi, &
     219           44 :                                   row_dist=row_dist_grid, col_dist=col_dist_ri)
     220              : 
     221           44 :       IF (bs_env%ri_rs%Z_lP_exists) THEN
     222              :          CALL dbcsr_binary_read(filepath=TRIM(bs_env%prefix)//"Z_lP.matrix", &
     223              :                                 distribution=dist_Z, &
     224            2 :                                 matrix_new=mat_Z_lP)
     225            2 :          IF (bs_env%unit_nr > 0) THEN
     226              :             WRITE (bs_env%unit_nr, '(T2,A,T57,A,F7.1,A)') &
     227            1 :                'Read Z_lP from file ', ' Execution time', m_walltime() - t1, ' s'
     228              :             ! The grid rows use Morton order (spatial_atom_order). A Z_lP.matrix
     229              :             ! written with another grid ordering would be silently read into
     230              :             ! the current row order. Delete stale Z_lP.matrix files and recompute if in doubt.
     231              :             WRITE (bs_env%unit_nr, '(T2,A)') &
     232            1 :                '*** NOTE: Z_lP restart must match the current (spatial) grid row ordering ***'
     233            1 :             WRITE (bs_env%unit_nr, '(A)') ' '
     234              :          END IF
     235              :       ELSE
     236              : 
     237           42 :          IF (bs_env%unit_nr > 0) THEN
     238           21 :             WRITE (bs_env%unit_nr, '(A)') ' '
     239           21 :             WRITE (bs_env%unit_nr, '(T2,A)') 'Started computing Z_lP'
     240           21 :             CALL m_flush(bs_env%unit_nr)
     241              :          END IF
     242              :          CALL dbcsr_create(mat_Z_lP, name="mat_Z_lP", dist=dist_Z, &
     243              :                            matrix_type=dbcsr_type_no_symmetry, &
     244           42 :                            row_blk_size=r_blk_sizes, col_blk_size=ri_blk_sizes)
     245              : 
     246              :          ! Largest per-atom AO block, needed to size the 3c-integral work buffers.
     247           42 :          max_ao_size = 0
     248          142 :          DO j = 1, bs_env%n_atom
     249              :             max_ao_size = MAX(max_ao_size, &
     250              :                               bs_env%i_ao_end_from_atom(j) - &
     251          142 :                               bs_env%i_ao_start_from_atom(j) + 1)
     252              :          END DO
     253              : 
     254              :          ! Per-atom RI-RS integration sphere:
     255              :          !   cutoff_ri(P) = r_c + r_RI(P)
     256              :          ! where r_c is the truncated-Coulomb cutoff of the RI metric and r_RI the radius of
     257              :          ! the most diffuse RI auxiliary Gaussian on P. The CUTOFF_RADIUS_RL_RI keyword
     258              :          ! (when > 0) overrides the entire cutoff calculation.
     259          126 :          ALLOCATE (cutoff_ri_per_atom(natom))
     260              : 
     261           42 :          IF (bs_env%ri_rs%cutoff_radius_ri_rs > 0.0_dp) THEN
     262            8 :             cutoff_ri_per_atom(:) = bs_env%ri_rs%cutoff_radius_ri_rs
     263              :          ELSE
     264           40 :             r_c = bs_env%ri_metric%cutoff_radius
     265          134 :             DO iatom = 1, natom
     266          134 :                cutoff_ri_per_atom(iatom) = r_c + bs_env%ri_rs%radius_ri_per_atom(iatom)
     267              :             END DO
     268              :          END IF
     269              : 
     270           42 :          CALL print_sphere_cutoff_table(bs_env, cutoff_ri_per_atom)
     271              : 
     272              :          ! =========================================================================
     273              :          ! 2. PER-ATOM SOLVER CLASSIFICATION
     274              :          !    Split the atoms into "small" (single-rank LAPACK, Phase A) and "big"
     275              :          !    (distributed ScaLAPACK over subgroups of G ranks, Phase B) by comparing
     276              :          !    each atom's estimated solve peak memory against the measured budget.
     277              :          ! =========================================================================
     278              :          CALL classify_z_lp_atoms(bs_env, ri_rs_grid_points, cutoff_ri_per_atom, ri_blk_sizes, &
     279           42 :                                   n_local_grid_atom, small_list, n_small, big_list, n_big, G)
     280              : 
     281              :          ! LPT scheduling: sort the atoms of each phase by estimated solve cost
     282              :          ! (n_local_grid^3, Cholesky-dominated) and greedily assign to the least-loaded
     283              :          ! rank (Phase A) / subgroup (Phase B).
     284              :          CALL lpt_assign_atoms(small_list, n_small, n_local_grid_atom, para_env%num_pe, &
     285           42 :                                para_env%mepos, my_atoms_A, balance_A)
     286           42 :          IF (n_big > 0) THEN
     287            0 :             n_groups = para_env%num_pe/G
     288            0 :             my_group = MIN(para_env%mepos/G, n_groups - 1)
     289              :             CALL lpt_assign_atoms(big_list, n_big, n_local_grid_atom, n_groups, my_group, &
     290            0 :                                   my_atoms_B, balance_B)
     291              :          ELSE
     292           42 :             ALLOCATE (my_atoms_B(0))
     293           42 :             balance_B = 1.0_dp
     294              :          END IF
     295              : 
     296              :          ! Atoms this rank will process across both phases for rank-0 progress
     297           42 :          n_my_atoms = SIZE(my_atoms_A) + SIZE(my_atoms_B)
     298           42 :          n_done = 0
     299              : 
     300              :          ! Shared context for the three-center integrals (μν|P) of the RHS build
     301              :          CALL gw_3c_ctx_create(ctx_3c, bs_env, bs_env%ri_metric, &
     302              :                                basis_j=bs_env%basis_set_AO, basis_k=bs_env%basis_set_AO, &
     303           42 :                                basis_i=bs_env%basis_set_RI)
     304              : 
     305              :          ! =========================================================================
     306              :          ! 3. TWO-PHASE LOOP OVER ATOMS
     307              :          !    Phase A processes the "small" atoms with the single-rank BLAS path
     308              :          !    Phase B processes the "big" atoms with the distributed ScaLAPACK path
     309              :          !    over rank subgroups of size G. phi_local for each atom's cutoff sphere
     310              :          !    is built on the fly to avoid replicating a global grid x AO matrix.
     311              :          ! =========================================================================
     312          126 :          DO iphase = 1, 2
     313           84 :             IF (iphase == 1) THEN
     314           42 :                use_dist = .FALSE.
     315           42 :                atom_j_mepos = 0
     316           42 :                atom_j_stride = 1
     317           42 :                phase_hi = SIZE(my_atoms_A)
     318              :             ELSE
     319           42 :                IF (n_big == 0) CYCLE
     320            0 :                use_dist = .TRUE.
     321            0 :                n_groups = para_env%num_pe/G
     322            0 :                my_group = MIN(para_env%mepos/G, n_groups - 1)
     323            0 :                ALLOCATE (para_env_sub)
     324            0 :                CALL para_env_sub%from_split(para_env, my_group)
     325            0 :                CALL cp_blacs_env_create(blacs_env=blacs_env_sub, para_env=para_env_sub)
     326            0 :                atom_j_mepos = para_env_sub%mepos
     327            0 :                atom_j_stride = para_env_sub%num_pe
     328              :                ! All ranks of a subgroup share my_group, hence the identical my_atoms_B list
     329              :                ! (the per-atom ScaLAPACK solve is collective over the subgroup).
     330            0 :                phase_hi = SIZE(my_atoms_B)
     331              :             END IF
     332              : 
     333           92 :             DO idx = 1, phase_hi
     334           50 :                item_start_time = m_walltime()
     335           50 :                IF (iphase == 1) THEN
     336           50 :                   atom_P = my_atoms_A(idx)
     337              :                ELSE
     338            0 :                   atom_P = my_atoms_B(idx)
     339              :                END IF
     340              : 
     341           50 :                n_loc_ri = ri_blk_sizes(atom_P)
     342           50 :                cutoff_ri = cutoff_ri_per_atom(atom_P)
     343              : 
     344              :                ! ---------------------------------------------------------------------
     345              :                ! A. Sphere-local AO matrix ϕ_μ(r_l): select the grid points with
     346              :                !    |r_l - R_P| <= cutoff_ri(P), evaluate every AO on them, and drop
     347              :                !    points whose largest AO amplitude is below EPS_FILTER.
     348              :                ! ---------------------------------------------------------------------
     349              :                CALL build_phi_on_sphere(bs_env, qs_kind_set, &
     350              :                                         ri_rs_grid_points, atom_P, cutoff_ri, n_ao_total, &
     351              :                                         local_grid_idx, n_local_grid, phi_local, &
     352           50 :                                         ao_col_map, n_ao_used)
     353              : 
     354              :                ! ---------------------------------------------------------------------
     355              :                ! B. Right-hand side  D_lP = Σ_μν ϕ_μ(r_l) ϕ_ν(r_l) (μν|P)
     356              :                ! ---------------------------------------------------------------------
     357          200 :                ALLOCATE (d_lp_local(n_local_grid, n_loc_ri))
     358           50 :                d_lp_local = 0.0_dp
     359              : 
     360              :                CALL compute_d_lp(bs_env, ctx_3c, phi_local, ao_col_map, d_lp_local, n_local_grid, &
     361           50 :                                  n_loc_ri, atom_P, max_ao_size, atom_j_mepos, atom_j_stride)
     362              : 
     363              :                ! Reduce per-subgroup-rank partials into the replicated d_lp_local.
     364              :                ! Skipped for BLAS path: each rank has the full sum locally.
     365           50 :                IF (use_dist) THEN
     366            0 :                   CALL para_env_sub%sum(d_lp_local)
     367              :                END IF
     368              : 
     369              :                ! ---------------------------------------------------------------------
     370              :                ! C. Jacobi conditioning vector d_l = 1/sqrt(D_ll) and, on the BLAS path,
     371              :                !    the dense conditioned matrix D'_ll' = d_l D_ll' d_l' + λδ_ll'.
     372              :                ! ---------------------------------------------------------------------
     373          150 :                ALLOCATE (d_vec_local(n_local_grid))
     374              : 
     375           50 :                IF (.NOT. use_dist) THEN
     376              :                   CALL build_gram_jacobi_blas(phi_local, n_local_grid, n_ao_used, &
     377           50 :                                               bs_env%ri_rs%tikhonov, D_local, d_vec_local)
     378              :                ELSE
     379              :                   ! ScaLAPACK path: only d_vec is needed here (= 1/||phi(r_l)||^2);
     380              :                   ! solve_D_lp_distributed builds its block-cyclic slice of D' internally
     381              :                   ! with the squared+scaled values, so no dense D_local on this rank.
     382              :                   CALL build_jacobi_diag_from_phi(phi_local, n_local_grid, n_ao_used, &
     383            0 :                                                   d_vec_local)
     384              :                END IF
     385              : 
     386              :                ! ---------------------------------------------------------------------
     387              :                ! D. Pre-scale the RHS:  D'_lP = d_l * D_lP
     388              :                ! ---------------------------------------------------------------------
     389           50 :                CALL scale_rows_by_diag(d_lp_local, d_vec_local, n_local_grid, n_loc_ri)
     390              : 
     391              :                ! ---------------------------------------------------------------------
     392              :                ! E. Cholesky solve  Σ_l' D'_ll' Z'_l'P = D'_lP
     393              :                !    (BLAS dpotrf/dpotrs or ScaLAPACK pdpotrf/pdpotrs)
     394              :                ! ---------------------------------------------------------------------
     395           50 :                IF (.NOT. use_dist) THEN
     396           50 :                   CALL timeset(routineN//"_dpotrf", handle_dpotrf)
     397           50 :                   CALL dpotrf('L', n_local_grid, D_local, n_local_grid, info)
     398           50 :                   CALL timestop(handle_dpotrf)
     399           50 :                   IF (info /= 0) CPABORT("RI-RS Cholesky factorization failed")
     400           50 :                   CALL timeset(routineN//"_dpotrs", handle_dpotrs)
     401              :                   CALL dpotrs('L', n_local_grid, n_loc_ri, D_local, n_local_grid, &
     402           50 :                               d_lp_local, n_local_grid, info)
     403           50 :                   CALL timestop(handle_dpotrs)
     404           50 :                   IF (info /= 0) CPABORT("RI-RS Cholesky solve failed")
     405           50 :                   DEALLOCATE (D_local)
     406              :                ELSE
     407              :                   CALL solve_D_lp_distributed(phi_local, d_vec_local, d_lp_local, &
     408              :                                               n_local_grid, n_ao_used, n_loc_ri, &
     409              :                                               bs_env%ri_rs%tikhonov, &
     410              :                                               para_env_sub, blacs_env_sub, &
     411            0 :                                               fm_struct_D, fm_struct_b, fm_D, fm_b, info)
     412            0 :                   IF (info /= 0) CPABORT("Distributed RI-RS Cholesky solve failed")
     413              :                END IF
     414              : 
     415              :                ! ---------------------------------------------------------------------
     416              :                ! F. Undo the conditioning:  Z_lP = d_l * Z'_lP
     417              :                ! ---------------------------------------------------------------------
     418           50 :                CALL scale_rows_by_diag(d_lp_local, d_vec_local, n_local_grid, n_loc_ri)
     419              : 
     420              :                ! ---------------------------------------------------------------------
     421              :                ! G. Scatter the solved Z columns back into the global sparse mat_Z_lP.
     422              :                ! ---------------------------------------------------------------------
     423           50 :                do_scatter = .TRUE.
     424           50 :                IF (use_dist) do_scatter = (para_env_sub%mepos == 0)
     425            0 :                IF (do_scatter) THEN
     426              :                   CALL store_Z_lP_columns(mat_Z_lP, d_lp_local, local_grid_idx, n_local_grid, &
     427              :                                           n_loc_ri, atom_P, r_blk_sizes, row_offset, &
     428           50 :                                           bs_env%eps_filter)
     429              :                END IF
     430              : 
     431           50 :                DEALLOCATE (d_vec_local, d_lp_local)
     432           50 :                DEALLOCATE (local_grid_idx, phi_local, ao_col_map)
     433              : 
     434              :                ! Report each atom completed by the printing rank. No inter-rank communication is
     435              :                ! needed; the final message below is printed only after the global synchronization.
     436           50 :                n_done = n_done + 1
     437              :                CALL print_Z_lP_progress(bs_env, n_done, n_my_atoms, &
     438           92 :                                         m_walltime() - item_start_time)
     439              :             END DO   ! idx: atoms of this phase owned by this rank / subgroup
     440              : 
     441              :             ! Tear down the Phase-B subgroup (all ranks created it collectively).
     442           84 :             IF (iphase == 2) THEN
     443            0 :                CALL cp_blacs_env_release(blacs_env_sub)
     444            0 :                CALL para_env_sub%free()
     445            0 :                DEALLOCATE (para_env_sub)
     446              :             END IF
     447              :          END DO   ! iphase
     448              : 
     449           42 :          DEALLOCATE (cutoff_ri_per_atom)
     450           42 :          DEALLOCATE (small_list, big_list)
     451              : 
     452           42 :          CALL gw_3c_ctx_release(ctx_3c)
     453              : 
     454           42 :          CALL dbcsr_finalize(mat_Z_lP)
     455              : 
     456           42 :          CALL para_env%sync()
     457              :          CALL print_Z_lP_progress(bs_env, natom, natom, m_walltime() - t1, &
     458           42 :                                   all_mpi_ranks=.TRUE.)
     459              : 
     460           42 :          logger => cp_get_default_logger()
     461              : 
     462           42 :          IF (BTEST(cp_print_key_should_output(logger%iter_info, input, key), cp_p_file)) THEN
     463            4 :             CALL dbcsr_binary_write(matrix=mat_Z_lP, filepath=TRIM(bs_env%prefix)//"Z_lP.matrix")
     464              :          END IF
     465              : 
     466              :       END IF
     467              : 
     468           44 :       DEALLOCATE (row_offset, ri_blk_sizes, col_dist_ri)
     469           44 :       CALL dbcsr_distribution_release(dist_Z)
     470              : 
     471           44 :       DEALLOCATE (ri_rs_grid_points)
     472              : 
     473           44 :       CALL timestop(handle)
     474              : 
     475          220 :    END SUBROUTINE compute_Z_lP_standard
     476              : 
     477              : ! **************************************************************************************************
     478              : !> \brief Prints the per-kind maximum RI-RS integration-sphere cutoff table.
     479              : !> \param bs_env ...
     480              : !> \param cutoff_ri_per_atom ...
     481              : ! **************************************************************************************************
     482           42 :    SUBROUTINE print_sphere_cutoff_table(bs_env, cutoff_ri_per_atom)
     483              : 
     484              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     485              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: cutoff_ri_per_atom
     486              : 
     487              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'print_sphere_cutoff_table'
     488              : 
     489              :       INTEGER                                            :: handle, iatom, ikind, nkind
     490           42 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: cutoff_ri_per_kind
     491           42 :       TYPE(atomic_kind_type), DIMENSION(:), POINTER      :: atomic_kind_set
     492           42 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
     493              : 
     494           42 :       CALL timeset(routineN, handle)
     495              : 
     496           42 :       atomic_kind_set => bs_env%ri_rs%atomic_kind_set
     497           42 :       particle_set => bs_env%ri_rs%particle_set
     498              : 
     499           42 :       IF (bs_env%unit_nr <= 0) THEN
     500           21 :          CALL timestop(handle)
     501              :          RETURN
     502              :       END IF
     503              : 
     504           21 :       nkind = SIZE(atomic_kind_set)
     505           63 :       ALLOCATE (cutoff_ri_per_kind(nkind))
     506           21 :       cutoff_ri_per_kind(:) = 0.0_dp
     507              : 
     508           71 :       DO iatom = 1, bs_env%n_atom
     509           50 :          ikind = particle_set(iatom)%atomic_kind%kind_number
     510           71 :          cutoff_ri_per_kind(ikind) = MAX(cutoff_ri_per_kind(ikind), cutoff_ri_per_atom(iatom))
     511              :       END DO
     512              : 
     513           21 :       WRITE (bs_env%unit_nr, '(T2,A)') 'Per-kind maximum RI-RS sphere cutoff (Å):'
     514           21 :       WRITE (bs_env%unit_nr, '(T4,A4,A14)') 'Kind', 'cutoff (Å)'
     515           50 :       DO ikind = 1, nkind
     516              :          WRITE (bs_env%unit_nr, '(T4,A4,F14.4)') &
     517           29 :             atomic_kind_set(ikind)%element_symbol, &
     518           79 :             cutoff_ri_per_kind(ikind)*angstrom
     519              :       END DO
     520           21 :       WRITE (bs_env%unit_nr, '(A)') ' '
     521              : 
     522           21 :       DEALLOCATE (cutoff_ri_per_kind)
     523              : 
     524           21 :       CALL timestop(handle)
     525              : 
     526           42 :    END SUBROUTINE print_sphere_cutoff_table
     527              : 
     528              : ! **************************************************************************************************
     529              : !> \brief LPT (longest-processing-time) assignment of the Z_lP atoms to workers (MPI ranks in
     530              : !>        Phase A, rank subgroups in Phase B): sort by estimated solve cost n_local_grid^3
     531              : !>        (the per-atom Cholesky dominates; the n^2 assembly terms order the atoms the same
     532              : !>        way) and greedily give each atom to the least-loaded worker.
     533              : !> \param atom_list ...
     534              : !> \param n_atoms ...
     535              : !> \param n_local_grid_atom ...
     536              : !> \param n_workers ...
     537              : !> \param my_worker ...
     538              : !> \param my_atoms ...
     539              : !> \param max_over_mean ...
     540              : ! **************************************************************************************************
     541           42 :    SUBROUTINE lpt_assign_atoms(atom_list, n_atoms, n_local_grid_atom, n_workers, my_worker, &
     542              :                                my_atoms, max_over_mean)
     543              : 
     544              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: atom_list
     545              :       INTEGER, INTENT(IN)                                :: n_atoms
     546              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: n_local_grid_atom
     547              :       INTEGER, INTENT(IN)                                :: n_workers, my_worker
     548              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: my_atoms
     549              :       REAL(KIND=dp), INTENT(OUT)                         :: max_over_mean
     550              : 
     551              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'lpt_assign_atoms'
     552              : 
     553              :       INTEGER                                            :: handle, i, iw, n_mine, w_min
     554           42 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: mine_tmp, perm
     555           42 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: cost, load
     556              : 
     557           42 :       CALL timeset(routineN, handle)
     558              : 
     559           42 :       max_over_mean = 1.0_dp
     560           42 :       IF (n_atoms <= 0) THEN
     561            0 :          ALLOCATE (my_atoms(0))
     562            0 :          CALL timestop(handle)
     563            0 :          RETURN
     564              :       END IF
     565              : 
     566          336 :       ALLOCATE (cost(n_atoms), perm(n_atoms), mine_tmp(n_atoms), load(n_workers))
     567          142 :       DO i = 1, n_atoms
     568          142 :          cost(i) = REAL(n_local_grid_atom(atom_list(i)), dp)**3
     569              :       END DO
     570           42 :       CALL sort(cost, n_atoms, perm)   ! ascending; walk backwards for largest-first
     571              : 
     572           42 :       load(:) = 0.0_dp
     573           42 :       n_mine = 0
     574          142 :       DO i = n_atoms, 1, -1
     575              :          w_min = 1
     576          200 :          DO iw = 2, n_workers
     577          200 :             IF (load(iw) < load(w_min)) w_min = iw
     578              :          END DO
     579          100 :          load(w_min) = load(w_min) + cost(i)
     580          142 :          IF (w_min - 1 == my_worker) THEN
     581           50 :             n_mine = n_mine + 1
     582           50 :             mine_tmp(n_mine) = atom_list(perm(i))
     583              :          END IF
     584              :       END DO
     585              : 
     586          126 :       ALLOCATE (my_atoms(n_mine))
     587           92 :       my_atoms(:) = mine_tmp(1:n_mine)
     588          294 :       IF (SUM(load) > 0.0_dp) max_over_mean = MAXVAL(load)*REAL(n_workers, dp)/SUM(load)
     589              : 
     590           42 :       CALL timestop(handle)
     591              : 
     592           84 :    END SUBROUTINE lpt_assign_atoms
     593              : 
     594              : ! **************************************************************************************************
     595              : !> \brief Computes the dense localized right-hand side for one RI atom P,
     596              : !>
     597              : !>          d_lP = Σ_μν ϕ_μ(r_l) ϕ_ν(r_l) (μν|P).
     598              : !>
     599              : !>        RI atom P, OMP-threaded over (atom_j, atom_k) AO-pair blocks: per thread, build the 3c
     600              : !>        block, then contract grid-chunked pair densities into a private d_lp partial; partials
     601              : !>        are reduced into d_lp at the end.
     602              : !>        Pair screening is handled inside build_3c_integral_block_ctx via the `screened` output.
     603              : !> \param bs_env ...
     604              : !> \param ctx ...
     605              : !> \param phi_val ...
     606              : !> \param ao_col_map ...
     607              : !> \param d_lp ...
     608              : !> \param n_grid_total ...
     609              : !> \param n_loc_ri ...
     610              : !> \param atom_P ...
     611              : !> \param max_ao_size ...
     612              : !> \param atom_j_mepos ...
     613              : !> \param atom_j_stride ...
     614              : ! **************************************************************************************************
     615           50 :    SUBROUTINE compute_d_lp(bs_env, ctx, phi_val, ao_col_map, d_lp, n_grid_total, n_loc_ri, atom_P, &
     616              :                            max_ao_size, atom_j_mepos, atom_j_stride)
     617              : 
     618              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     619              :       TYPE(gw_3c_ctx_type), INTENT(IN)                   :: ctx
     620              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: phi_val
     621              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: ao_col_map
     622              :       INTEGER, INTENT(IN)                                :: n_grid_total, n_loc_ri
     623              :       REAL(KIND=dp), INTENT(INOUT)                       :: d_lp(n_grid_total, n_loc_ri)
     624              :       INTEGER, INTENT(IN)                                :: atom_P, max_ao_size, atom_j_mepos, &
     625              :                                                             atom_j_stride
     626              : 
     627              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'compute_d_lp'
     628              :       INTEGER, PARAMETER                                 :: grid_chunk = 1024
     629              : 
     630              :       INTEGER                                            :: atom_j, atom_k, c, handle, handle_dgemm, &
     631              :                                                             j, jk_idx, jsize, jstart, k, ksize, &
     632              :                                                             kstart, l, l0, n_grid_pair, point, ri
     633           50 :       INTEGER, ALLOCATABLE                               :: grid_index(:)
     634              :       LOGICAL                                            :: screened
     635           50 :       LOGICAL, ALLOCATABLE                               :: skip_grid_point(:, :)
     636              :       REAL(KIND=dp)                                      :: pair_factor
     637           50 :       REAL(KIND=dp), ALLOCATABLE                         :: grid_result(:, :)
     638           50 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: d_lp_prv, int_2d_prv, rho_chunk
     639           50 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: int_3c_prv
     640           50 :       TYPE(gw_3c_ws_type)                                :: ws
     641              : 
     642           50 :       CALL timeset(routineN, handle)
     643              : 
     644              :       !$OMP PARALLEL DEFAULT(NONE) &
     645              :       !$OMP SHARED(skip_grid_point, bs_env, ctx, phi_val, ao_col_map, d_lp, n_grid_total, n_loc_ri, atom_P, max_ao_size, &
     646              :       !$OMP        atom_j_mepos, atom_j_stride) &
     647              :       !$OMP PRIVATE(grid_index, grid_result, n_grid_pair, point, pair_factor, &
     648              :       !$OMP         atom_j, atom_k, c, handle_dgemm, j, jk_idx, jsize, jstart, k, ksize, kstart, &
     649           50 :       !$OMP         l, l0, ri, screened, d_lp_prv, int_2d_prv, rho_chunk, int_3c_prv, ws)
     650              : 
     651              :       CALL gw_3c_ws_create(ws, ctx)
     652              :       ALLOCATE (int_3c_prv(max_ao_size, max_ao_size, n_loc_ri))
     653              :       ALLOCATE (int_2d_prv(max_ao_size*max_ao_size, n_loc_ri))
     654              :       ALLOCATE (rho_chunk(grid_chunk, max_ao_size*max_ao_size))
     655              :       ALLOCATE (d_lp_prv(n_grid_total, n_loc_ri))
     656              :       ALLOCATE (grid_index(n_grid_total), grid_result(grid_chunk, n_loc_ri))
     657              :       d_lp_prv(:, :) = 0.0_dp
     658              : 
     659              :       !$OMP SINGLE
     660              :       ALLOCATE (skip_grid_point(n_grid_total, bs_env%n_atom))
     661              :       CALL compute_skip_grid_point(bs_env, phi_val, ao_col_map, skip_grid_point)
     662              :       !$OMP END SINGLE
     663              : 
     664              :       ! MPI assigns each unordered pair through its first atom; OpenMP divides those atoms.
     665              :       ! Skip only exact-zero pair-grid support, without a new screening threshold.
     666              :       !$OMP DO SCHEDULE(DYNAMIC)
     667              :       DO atom_j = atom_j_mepos + 1, bs_env%n_atom, atom_j_stride
     668              :          DO atom_k = atom_j, bs_env%n_atom
     669              :             jstart = ao_col_map(bs_env%i_ao_start_from_atom(atom_j))
     670              :             kstart = ao_col_map(bs_env%i_ao_start_from_atom(atom_k))
     671              :             IF (jstart == 0 .OR. kstart == 0) CYCLE
     672              :             jsize = bs_env%i_ao_end_from_atom(atom_j) - bs_env%i_ao_start_from_atom(atom_j) + 1
     673              :             ksize = bs_env%i_ao_end_from_atom(atom_k) - bs_env%i_ao_start_from_atom(atom_k) + 1
     674              : 
     675              :             n_grid_pair = 0
     676              :             DO point = 1, n_grid_total
     677              :                IF (skip_grid_point(point, atom_j) .OR. skip_grid_point(point, atom_k)) CYCLE
     678              :                n_grid_pair = n_grid_pair + 1
     679              :                grid_index(n_grid_pair) = point
     680              :             END DO
     681              :             IF (n_grid_pair == 0) CYCLE
     682              :             ! (μν|P) = (νμ|P): distinct atom pairs contribute twice.
     683              :             pair_factor = 1.0_dp
     684              :             IF (atom_j /= atom_k) pair_factor = 2.0_dp
     685              : 
     686              :             int_3c_prv(1:jsize, 1:ksize, 1:n_loc_ri) = 0.0_dp
     687              : 
     688              :             ! Compute B_{μν,P} = (μν|P); ctx-internal triangle-inequality screening on
     689              :             ! kind_radius sets `screened=.TRUE.` for negligible triples.
     690              :             CALL build_3c_integral_block_ctx(int_3c_prv(1:jsize, 1:ksize, 1:n_loc_ri), &
     691              :                                              ctx, ws, atom_j=atom_j, atom_k=atom_k, atom_i=atom_P, &
     692              :                                              screened=screened)
     693              : 
     694              :             IF (screened) CYCLE
     695              : 
     696              :             ! Flatten 3D B_{μν, P} tensor to 2D B_{(μν), P} matrix for BLAS
     697              :             DO ri = 1, n_loc_ri
     698              :                DO k = 1, ksize
     699              :                   DO j = 1, jsize
     700              :                      jk_idx = (k - 1)*jsize + j
     701              :                      int_2d_prv(jk_idx, ri) = int_3c_prv(j, k, ri)
     702              :                   END DO
     703              :                END DO
     704              :             END DO
     705              : 
     706              :             ! Pair density ρ(l, μν) = ϕ_μ(r_l) ϕ_ν(r_l) in grid chunks, contracted on the fly:
     707              :             ! d_{l,P} += ρ(l, μν) B_{(μν),P}  (dgemm runs serially inside the parallel region)
     708              :             DO l0 = 1, n_grid_pair, grid_chunk
     709              :                c = MIN(grid_chunk, n_grid_pair - l0 + 1)
     710              :                DO k = 1, ksize
     711              :                   DO j = 1, jsize
     712              :                      jk_idx = (k - 1)*jsize + j
     713              :                      DO l = 1, c
     714              :                         point = grid_index(l0 + l - 1)
     715              :                         rho_chunk(l, jk_idx) = phi_val(point, jstart + j - 1)* &
     716              :                                                phi_val(point, kstart + k - 1)
     717              :                      END DO
     718              :                   END DO
     719              :                END DO
     720              :                CALL timeset(routineN//"_dgemm", handle_dgemm)
     721              :                CALL dgemm("N", "N", c, n_loc_ri, jsize*ksize, &
     722              :                           pair_factor, rho_chunk, grid_chunk, &
     723              :                           int_2d_prv, max_ao_size*max_ao_size, &
     724              :                           0.0_dp, grid_result, grid_chunk)
     725              :                DO ri = 1, n_loc_ri
     726              :                   DO l = 1, c
     727              :                      point = grid_index(l0 + l - 1)
     728              :                      d_lp_prv(point, ri) = d_lp_prv(point, ri) + grid_result(l, ri)
     729              :                   END DO
     730              :                END DO
     731              :                CALL timestop(handle_dgemm)
     732              :             END DO
     733              :          END DO
     734              :       END DO
     735              :       !$OMP END DO
     736              : 
     737              :       !$OMP CRITICAL (compute_d_lp_reduce)
     738              :       d_lp(1:n_grid_total, 1:n_loc_ri) = d_lp(1:n_grid_total, 1:n_loc_ri) + &
     739              :                                          d_lp_prv(1:n_grid_total, 1:n_loc_ri)
     740              :       !$OMP END CRITICAL (compute_d_lp_reduce)
     741              : 
     742              :       DEALLOCATE (int_3c_prv, int_2d_prv, rho_chunk, d_lp_prv, grid_index, grid_result)
     743              :       CALL gw_3c_ws_release(ws)
     744              : 
     745              :       !$OMP SINGLE
     746              :       DEALLOCATE (skip_grid_point)
     747              :       !$OMP END SINGLE
     748              : 
     749              :       !$OMP END PARALLEL
     750              : 
     751           50 :       CALL timestop(handle)
     752              : 
     753           50 :    END SUBROUTINE compute_d_lp
     754              : 
     755              : ! **************************************************************************************************
     756              : !> \brief Marks grid points where all stored AO values of an atom are exactly zero.
     757              : !> \param bs_env ...
     758              : !> \param phi_val ...
     759              : !> \param ao_col_map ...
     760              : !> \param skip_grid_point ...
     761              : ! **************************************************************************************************
     762           50 :    SUBROUTINE compute_skip_grid_point(bs_env, phi_val, ao_col_map, skip_grid_point)
     763              : 
     764              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     765              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: phi_val
     766              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: ao_col_map
     767              :       LOGICAL, DIMENSION(:, :), INTENT(OUT)              :: skip_grid_point
     768              : 
     769              :       INTEGER                                            :: atom, first_ao, number_of_aos
     770              : 
     771        45743 :       skip_grid_point(:, :) = .TRUE.
     772          174 :       DO atom = 1, bs_env%n_atom
     773          124 :          first_ao = ao_col_map(bs_env%i_ao_start_from_atom(atom))
     774          124 :          IF (first_ao == 0) CYCLE
     775          124 :          number_of_aos = bs_env%i_ao_end_from_atom(atom) - bs_env%i_ao_start_from_atom(atom) + 1
     776       167011 :          skip_grid_point(:, atom) = ALL(phi_val(:, first_ao:first_ao + number_of_aos - 1) == 0.0_dp, DIM=2)
     777              :       END DO
     778              : 
     779           50 :    END SUBROUTINE compute_skip_grid_point
     780              : 
     781              : ! **************************************************************************************************
     782              : !> \brief Computes the RI-RS matrix Z_lP for an automatically optimized RI basis.
     783              : !>
     784              : !>        The fitting coefficients Z_lP satisfy
     785              : !>
     786              : !>          Σ_l' D_ll' Z_l'P = d_lP,
     787              : !>          D_ll' = [Σ_μ ϕ_μ(r_l) ϕ_μ(r_l')]²,
     788              : !>          d_lP = Σ_μν ϕ_μ(r_l) ϕ_ν(r_l) (μν|P).
     789              : !>
     790              : !>        For the standard RI-RS fit, P is an atom-centered RI function. Here an optimized
     791              : !>        function may contain reference functions on neighboring atoms,
     792              : !>
     793              : !>          φ_p(r) = Σ_A Σ_{P∈A} U_Pp^A φ_P^A(r),
     794              : !>
     795              : !>        so all atomic contributions to d_lp must be accumulated before solving for Z_lp.
     796              : !>        If every optimized column uses the complete RI-RS grid, all columns are solved in one
     797              : !>        system. Otherwise, each atom-blocked column set is fitted on its own integration sphere;
     798              : !>        grid points outside that sphere are excluded from its fitting equations.
     799              : !> \param qs_env ...
     800              : !> \param bs_env ...
     801              : !> \param ri_rs_grid_points ...
     802              : !> \param mat_phi_mu_l ...
     803              : !> \param mat_Z_lP ...
     804              : ! **************************************************************************************************
     805            4 :    SUBROUTINE compute_Z_lP_auto_ri(qs_env, bs_env, ri_rs_grid_points, mat_phi_mu_l, mat_Z_lP)
     806              :       TYPE(qs_environment_type), POINTER                 :: qs_env
     807              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     808              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: ri_rs_grid_points
     809              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_phi_mu_l
     810              :       TYPE(dbcsr_type), INTENT(OUT)                      :: mat_Z_lP
     811              : 
     812              :       CHARACTER(LEN=*), PARAMETER :: key = 'PROPERTIES%BANDSTRUCTURE%GW%PRINT%RESTART', &
     813              :          routineN = 'compute_Z_lP_auto_ri'
     814              : 
     815              :       INTEGER :: AB_block, atom_A, atom_B, block_size_b, column_first, first_p_AB, fit_atom, &
     816              :          handle, handle_dpotrf, handle_dpotrs, info, max_ao_size, max_nRI_ref, mypcol, myprow, &
     817              :          n_ao_total, n_ao_used, n_done, n_my_atoms, n_to_a, natom, ncol, ngrid, nri, nRI_ref_A, &
     818              :          nRI_ref_B, output_offset, ri_atom
     819            8 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: ao_col_map, local_grid_idx, row_offset
     820            8 :       INTEGER, DIMENSION(:), POINTER                     :: col_dist_ri, ri_blk_sizes, &
     821            4 :                                                             row_dist_grid, row_size_grid
     822              :       LOGICAL                                            :: AB_block_local, common_grid_available, &
     823              :                                                             have_fitted_columns, &
     824              :                                                             reuse_atomic_integrals
     825            4 :       LOGICAL, ALLOCATABLE, DIMENSION(:)                 :: active_atom
     826              :       REAL(KIND=dp)                                      :: cutoff_ri, item_start_time, r_c, t1
     827            4 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: d_vec
     828            4 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: D_local, d_lp_all, d_lp_local, &
     829            4 :                                                             phi_local, U_Pp_AB
     830            4 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: U_Pp_by_atom
     831              :       REAL(KIND=dp), DIMENSION(3)                        :: center
     832              :       TYPE(cell_type), POINTER                           :: cell
     833              :       TYPE(cp_logger_type), POINTER                      :: logger
     834              :       TYPE(dbcsr_distribution_type)                      :: dist_Z
     835              :       TYPE(dbcsr_type)                                   :: mat_rhs
     836           52 :       TYPE(gw_3c_ctx_type)                               :: ctx_3c
     837              :       TYPE(mp_para_env_type), POINTER                    :: para_env, para_env_col
     838            4 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
     839            4 :       TYPE(qs_kind_type), DIMENSION(:), POINTER          :: qs_kind_set
     840              :       TYPE(section_vals_type), POINTER                   :: input
     841              : 
     842            4 :       CALL timeset(routineN, handle)
     843            4 :       t1 = m_walltime()
     844            4 :       IF (bs_env%unit_nr > 0) THEN
     845            2 :          WRITE (bs_env%unit_nr, '(A)') ' '
     846            2 :          WRITE (bs_env%unit_nr, '(T2,A)') 'Started computing Z_lP'
     847            2 :          CALL m_flush(bs_env%unit_nr)
     848              :       END IF
     849              :       CALL get_qs_env(qs_env, para_env=para_env, particle_set=particle_set, input=input, &
     850            4 :                       qs_kind_set=qs_kind_set, cell=cell)
     851            4 :       NULLIFY (para_env_col)
     852              : 
     853            4 :       natom = bs_env%n_atom
     854            4 :       n_ao_total = bs_env%i_ao_end_from_atom(natom)
     855            4 :       CPASSERT(bs_env%auto_ri%AB_block_count > 0)
     856            4 :       CPASSERT(SIZE(particle_set) == natom)
     857              : 
     858              :       CALL prepare_Z_lP_auto_ri(bs_env, mat_phi_mu_l, mat_Z_lP, dist_Z, row_size_grid, &
     859              :                                 row_dist_grid, col_dist_ri, ri_blk_sizes, row_offset, &
     860            4 :                                 myprow, mypcol, max_ao_size)
     861              : 
     862              :       CALL gw_3c_ctx_create(ctx_3c, bs_env, bs_env%ri_metric, &
     863              :                             basis_j=bs_env%basis_set_AO, basis_k=bs_env%basis_set_AO, &
     864            4 :                             basis_i=bs_env%basis_set_RI)
     865              : 
     866              :       ! Check whether one grid can represent d_lp for every optimized atomic column block.
     867              :       CALL common_Z_lP_grid_available(bs_env, ri_rs_grid_points, &
     868            4 :                                       common_grid_available, have_fitted_columns)
     869              : 
     870            4 :       IF (common_grid_available .AND. have_fitted_columns) THEN
     871              :          ! Evaluate ϕ_μ(r_l) once on the common grid.
     872              :          CALL build_phi_on_complete_grid(bs_env, qs_kind_set, &
     873              :                                          ri_rs_grid_points, n_ao_total, local_grid_idx, ngrid, &
     874            4 :                                          phi_local, ao_col_map, n_ao_used)
     875           16 :          nri = SUM(ri_blk_sizes)
     876           16 :          ALLOCATE (d_lp_all(ngrid, nri), source=0.0_dp)
     877              :          ! d_lp = Σ_A Σ_{P∈A} d_lP U_Pp for every optimized RI function q.
     878              :          CALL compute_d_lp_auto_ri_batch(bs_env, ctx_3c, phi_local, ao_col_map, d_lp_all, ngrid, &
     879            4 :                                          max_ao_size, para_env%mepos, para_env%num_pe)
     880            4 :          CALL para_env%sum(d_lp_all)
     881           12 :          ALLOCATE (d_vec(ngrid))
     882              :          ! D_ll' = [Σ_μ ϕ_μ(r_l) ϕ_μ(r_l')]², followed by D Z = d.
     883              :          CALL build_gram_jacobi_blas(phi_local, ngrid, n_ao_used, bs_env%ri_rs%tikhonov, &
     884            4 :                                      D_local, d_vec)
     885            4 :          CALL scale_rows_by_diag(d_lp_all, d_vec, ngrid, nri)
     886            4 :          CALL timeset(routineN//'_dpotrf', handle_dpotrf)
     887            4 :          CALL dpotrf('L', ngrid, D_local, ngrid, info)
     888            4 :          CALL timestop(handle_dpotrf)
     889            4 :          CPASSERT(info == 0)
     890            4 :          CALL timeset(routineN//'_dpotrs', handle_dpotrs)
     891            4 :          CALL dpotrs('L', ngrid, nri, D_local, ngrid, d_lp_all, ngrid, info)
     892            4 :          CALL timestop(handle_dpotrs)
     893            4 :          CPASSERT(info == 0)
     894            4 :          CALL scale_rows_by_diag(d_lp_all, d_vec, ngrid, nri)
     895              : 
     896           22 :          DO AB_block = 1, bs_env%auto_ri%AB_block_count
     897           18 :             atom_A = bs_env%auto_ri%AB_atom_A(AB_block)
     898           18 :             atom_B = bs_env%auto_ri%AB_atom_B(AB_block)
     899           18 :             AB_block_local = col_dist_ri(atom_A) == mypcol
     900           18 :             IF (atom_B /= atom_A) AB_block_local = AB_block_local .OR. col_dist_ri(atom_B) == mypcol
     901           12 :             IF (.NOT. AB_block_local) CYCLE
     902           18 :             ncol = bs_env%auto_ri%AB_size_opt_RI(AB_block)
     903           18 :             n_to_a = bs_env%auto_ri%AB_size_opt_RI_to_A(AB_block)
     904           18 :             IF (n_to_a > 0 .AND. col_dist_ri(atom_A) == mypcol) THEN
     905              :                output_offset = SUM(ri_blk_sizes(:atom_A - 1)) + &
     906           28 :                                bs_env%auto_ri%AB_first_p_A(AB_block) - 1
     907              :                CALL add_Z_lP_columns(mat_Z_lP, &
     908              :                                      d_lp_all(:, output_offset + 1:output_offset + n_to_a), &
     909              :                                      local_grid_idx, ngrid, atom_A, &
     910              :                                      bs_env%auto_ri%AB_first_p_A(AB_block), &
     911              :                                      ri_blk_sizes(atom_A), row_size_grid, row_offset, &
     912              :                                      row_dist_grid, &
     913           14 :                                      myprow, bs_env%eps_filter)
     914              :             END IF
     915           22 :             IF (n_to_a < ncol) THEN
     916            4 :                CPASSERT(atom_B /= atom_A)
     917            4 :                IF (col_dist_ri(atom_B) == mypcol) THEN
     918            4 :                   block_size_b = ri_blk_sizes(atom_B)
     919              :                   output_offset = SUM(ri_blk_sizes(:atom_B - 1)) + &
     920           10 :                                   bs_env%auto_ri%AB_first_p_B(AB_block) - 1
     921              :                   CALL add_Z_lP_columns(mat_Z_lP, &
     922              :                                         d_lp_all(:, output_offset + 1: &
     923              :                                                  output_offset + ncol - n_to_a), &
     924              :                                         local_grid_idx, ngrid, atom_B, &
     925              :                                         bs_env%auto_ri%AB_first_p_B(AB_block), block_size_b, &
     926              :                                         row_size_grid, row_offset, row_dist_grid, myprow, &
     927            4 :                                         bs_env%eps_filter)
     928              :                END IF
     929              :             END IF
     930              :          END DO
     931           12 :          DEALLOCATE (D_local, d_vec, d_lp_all, local_grid_idx, phi_local, ao_col_map)
     932              :       ELSE
     933              :          reuse_atomic_integrals = &
     934              :             bs_env%ri_rs%cutoff_radius_ri_ao <= 0.0_dp .OR. &
     935              :             bs_env%ri_rs%cutoff_radius_ri_ao <= &
     936            0 :             MINVAL(bs_env%ri_rs%radius_ao_per_atom)
     937              :          IF (reuse_atomic_integrals) THEN
     938              :             ! Form d_lp = Σ_A Σ_{P∈A} d_lP U_Pp before fitting each atomic column block.
     939              :             CALL compute_auto_ri_d_lp(qs_env, bs_env, ctx_3c, &
     940              :                                       ri_rs_grid_points, mat_phi_mu_l, mat_rhs, &
     941            0 :                                       max_ao_size)
     942            0 :             CALL dbcsr_release(mat_Z_lP)
     943              :             CALL dbcsr_create(mat_Z_lP, name='mat_Z_lP localized AA/AB', dist=dist_Z, &
     944              :                               matrix_type=dbcsr_type_no_symmetry, row_blk_size=row_size_grid, &
     945            0 :                               col_blk_size=ri_blk_sizes)
     946              :             ! Solve Σ_l' D_ll' Z_l'q = d_lp on each atomic fitting grid.
     947            0 :             CALL fit_auto_ri_z_lp(qs_env, bs_env, ri_rs_grid_points, mat_rhs, mat_Z_lP)
     948            0 :             CALL dbcsr_release(mat_rhs)
     949              :          ELSE
     950            0 :             ALLOCATE (para_env_col)
     951            0 :             CALL para_env_col%from_split(para_env, mypcol)
     952            0 :             n_my_atoms = 0
     953            0 :             DO fit_atom = 1, natom
     954            0 :                IF (col_dist_ri(fit_atom) /= mypcol) CYCLE
     955            0 :                IF (ri_blk_sizes(fit_atom) == 0) CYCLE
     956            0 :                n_my_atoms = n_my_atoms + 1
     957              :             END DO
     958            0 :             n_done = 0
     959            0 :             max_nRI_ref = 0
     960            0 :             DO ri_atom = 1, natom
     961            0 :                max_nRI_ref = MAX(max_nRI_ref, get_ref_RI_size(bs_env, ri_atom))
     962              :             END DO
     963            0 :             DO fit_atom = 1, natom
     964            0 :                IF (col_dist_ri(fit_atom) /= mypcol) CYCLE
     965            0 :                ncol = ri_blk_sizes(fit_atom)
     966            0 :                IF (ncol == 0) CYCLE
     967            0 :                item_start_time = m_walltime()
     968            0 :                ALLOCATE (U_Pp_by_atom(max_nRI_ref, ncol, natom), source=0.0_dp)
     969            0 :                ALLOCATE (active_atom(natom), source=.FALSE.)
     970              : 
     971            0 :                DO AB_block = 1, bs_env%auto_ri%AB_block_count
     972            0 :                   atom_A = bs_env%auto_ri%AB_atom_A(AB_block)
     973            0 :                   atom_B = bs_env%auto_ri%AB_atom_B(AB_block)
     974            0 :                   n_to_a = bs_env%auto_ri%AB_size_opt_RI_to_A(AB_block)
     975            0 :                   IF (fit_atom == atom_A .AND. n_to_a > 0) THEN
     976            0 :                      column_first = bs_env%auto_ri%AB_first_p_A(AB_block)
     977            0 :                      first_p_AB = 1
     978            0 :                      ncol = n_to_a
     979            0 :                   ELSE IF (fit_atom == atom_B .AND. &
     980              :                            n_to_a < bs_env%auto_ri%AB_size_opt_RI(AB_block)) THEN
     981            0 :                      column_first = bs_env%auto_ri%AB_first_p_B(AB_block)
     982            0 :                      first_p_AB = n_to_a + 1
     983            0 :                      ncol = bs_env%auto_ri%AB_size_opt_RI(AB_block) - n_to_a
     984              :                   ELSE
     985              :                      CYCLE
     986              :                   END IF
     987            0 :                   CALL get_U_Pp_AB(bs_env%auto_ri, AB_block, U_Pp_AB)
     988              : 
     989            0 :                   nRI_ref_A = get_ref_RI_size(bs_env, atom_A)
     990              :                   U_Pp_by_atom(1:nRI_ref_A, &
     991              :                                column_first:column_first + ncol - 1, atom_A) = &
     992              :                      U_Pp_AB( &
     993            0 :                      1:nRI_ref_A, first_p_AB:first_p_AB + ncol - 1)
     994            0 :                   active_atom(atom_A) = .TRUE.
     995            0 :                   IF (atom_B /= atom_A) THEN
     996            0 :                      nRI_ref_B = get_ref_RI_size(bs_env, atom_B)
     997              :                      U_Pp_by_atom(1:nRI_ref_B, &
     998              :                                   column_first:column_first + ncol - 1, atom_B) = &
     999              :                         U_Pp_AB( &
    1000              :                         nRI_ref_A + 1:nRI_ref_A + nRI_ref_B, &
    1001            0 :                         first_p_AB:first_p_AB + ncol - 1)
    1002            0 :                      active_atom(atom_B) = .TRUE.
    1003              :                   END IF
    1004            0 :                   DEALLOCATE (U_Pp_AB)
    1005              :                END DO
    1006              : 
    1007            0 :                center = particle_set(fit_atom)%r
    1008            0 :                IF (bs_env%ri_rs%cutoff_radius_ri_rs > 0.0_dp) THEN
    1009            0 :                   cutoff_ri = bs_env%ri_rs%cutoff_radius_ri_rs
    1010              :                ELSE
    1011            0 :                   r_c = bs_env%ri_metric%cutoff_radius
    1012            0 :                   cutoff_ri = 0.0_dp
    1013            0 :                   DO ri_atom = 1, natom
    1014            0 :                      IF (.NOT. active_atom(ri_atom)) CYCLE
    1015              :                      cutoff_ri = MAX(cutoff_ri, &
    1016              :                                      r_c + bs_env%ri_rs%radius_ri_per_atom(ri_atom) + &
    1017            0 :                                      NORM2(center - particle_set(ri_atom)%r))
    1018              :                   END DO
    1019              :                END IF
    1020              :                CALL build_phi_on_sphere(bs_env, qs_kind_set, &
    1021              :                                         ri_rs_grid_points, fit_atom, cutoff_ri, n_ao_total, &
    1022              :                                         local_grid_idx, ngrid, phi_local, ao_col_map, n_ao_used, &
    1023            0 :                                         center=center)
    1024            0 :                ncol = ri_blk_sizes(fit_atom)
    1025            0 :                ALLOCATE (d_lp_local(ngrid, ncol), source=0.0_dp)
    1026              :                ! d_lp = Σ_A Σ_{P∈A} d_lP U_Pp on the grid of this atomic column block.
    1027              :                CALL compute_d_lp_auto_ri_atoms(bs_env, ctx_3c, phi_local, ao_col_map, &
    1028              :                                                d_lp_local, ngrid, U_Pp_by_atom, &
    1029              :                                                active_atom, max_ao_size, &
    1030            0 :                                                para_env_col%mepos, para_env_col%num_pe)
    1031            0 :                CALL para_env_col%sum(d_lp_local)
    1032            0 :                ALLOCATE (d_vec(ngrid))
    1033              :                ! D_ll' = [Σ_μ ϕ_μ(r_l) ϕ_μ(r_l')]², followed by D Z = d.
    1034              :                CALL build_gram_jacobi_blas(phi_local, ngrid, n_ao_used, bs_env%ri_rs%tikhonov, &
    1035            0 :                                            D_local, d_vec)
    1036            0 :                CALL scale_rows_by_diag(d_lp_local, d_vec, ngrid, ncol)
    1037            0 :                CALL timeset(routineN//'_dpotrf', handle_dpotrf)
    1038            0 :                CALL dpotrf('L', ngrid, D_local, ngrid, info)
    1039            0 :                CALL timestop(handle_dpotrf)
    1040            0 :                CPASSERT(info == 0)
    1041            0 :                CALL timeset(routineN//'_dpotrs', handle_dpotrs)
    1042            0 :                CALL dpotrs('L', ngrid, ncol, D_local, ngrid, d_lp_local, ngrid, info)
    1043            0 :                CALL timestop(handle_dpotrs)
    1044            0 :                CPASSERT(info == 0)
    1045            0 :                CALL scale_rows_by_diag(d_lp_local, d_vec, ngrid, ncol)
    1046              :                CALL add_Z_lP_columns(mat_Z_lP, d_lp_local, local_grid_idx, ngrid, fit_atom, 1, &
    1047              :                                      ri_blk_sizes(fit_atom), row_size_grid, row_offset, &
    1048              :                                      row_dist_grid, &
    1049            0 :                                      myprow, bs_env%eps_filter)
    1050            0 :                DEALLOCATE (D_local, d_vec, d_lp_local, local_grid_idx, phi_local, ao_col_map)
    1051            0 :                DEALLOCATE (U_Pp_by_atom, active_atom)
    1052            0 :                n_done = n_done + 1
    1053              :                CALL print_Z_lP_progress(bs_env, n_done, n_my_atoms, &
    1054            0 :                                         m_walltime() - item_start_time)
    1055              :             END DO
    1056            0 :             CALL para_env_col%free()
    1057            0 :             DEALLOCATE (para_env_col)
    1058              :          END IF
    1059              :       END IF
    1060            4 :       CALL gw_3c_ctx_release(ctx_3c)
    1061              : 
    1062            4 :       CALL dbcsr_filter(mat_Z_lP, bs_env%eps_filter)
    1063            4 :       CALL dbcsr_finalize(mat_Z_lP)
    1064            4 :       CALL para_env%sync()
    1065              :       CALL print_Z_lP_progress(bs_env, natom, natom, m_walltime() - t1, &
    1066            4 :                                all_mpi_ranks=.TRUE.)
    1067            4 :       logger => cp_get_default_logger()
    1068            4 :       IF (BTEST(cp_print_key_should_output(logger%iter_info, input, key), cp_p_file)) THEN
    1069            0 :          CALL dbcsr_binary_write(matrix=mat_Z_lP, filepath=TRIM(bs_env%prefix)//'Z_lP.matrix')
    1070              :       END IF
    1071              : 
    1072            4 :       DEALLOCATE (row_offset, ri_blk_sizes, col_dist_ri)
    1073            4 :       CALL dbcsr_distribution_release(dist_Z)
    1074            4 :       CALL timestop(handle)
    1075              : 
    1076           12 :    END SUBROUTINE compute_Z_lP_auto_ri
    1077              : 
    1078              : ! **************************************************************************************************
    1079              : !> \brief Prints completed Z_lP atoms and execution time from the printing rank.
    1080              : !> \param bs_env ...
    1081              : !> \param n_done Number of atoms completed by the printing rank.
    1082              : !> \param n_total Total number of atoms assigned to the printing rank.
    1083              : !> \param execution_time Wall-clock time used for the reported work.
    1084              : !> \param all_mpi_ranks Whether all MPI ranks have completed the calculation.
    1085              : ! **************************************************************************************************
    1086          102 :    SUBROUTINE print_Z_lP_progress(bs_env, n_done, n_total, execution_time, all_mpi_ranks)
    1087              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1088              :       INTEGER, INTENT(IN)                                :: n_done, n_total
    1089              :       REAL(KIND=dp), INTENT(IN)                          :: execution_time
    1090              :       LOGICAL, INTENT(IN), OPTIONAL                      :: all_mpi_ranks
    1091              : 
    1092              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'print_Z_lP_progress'
    1093              : 
    1094              :       INTEGER                                            :: handle
    1095              :       LOGICAL                                            :: completed
    1096              : 
    1097          102 :       CALL timeset(routineN, handle)
    1098              : 
    1099          102 :       IF (bs_env%unit_nr > 0) THEN
    1100           55 :          completed = .FALSE.
    1101           55 :          IF (PRESENT(all_mpi_ranks)) completed = all_mpi_ranks
    1102           23 :          IF (completed) THEN
    1103              :             WRITE (bs_env%unit_nr, '(T2,A,T58,A,F7.1,A,/)') &
    1104           23 :                'Computed Z_lP (all MPI ranks) for all atoms,', &
    1105           46 :                'Execution time', execution_time, ' s'
    1106              :          ELSE
    1107              :             WRITE (bs_env%unit_nr, '(T2,A,I11,A,I3,A,F7.1,A)') &
    1108           32 :                'Computed Z_lP (MPI rank 0) for atom', n_done, ' /', n_total, &
    1109           64 :                ',    Execution time', execution_time, ' s'
    1110              :          END IF
    1111           55 :          CALL m_flush(bs_env%unit_nr)
    1112              :       END IF
    1113              : 
    1114          102 :       CALL timestop(handle)
    1115              : 
    1116          102 :    END SUBROUTINE print_Z_lP_progress
    1117              : 
    1118              : ! **************************************************************************************************
    1119              : !> \brief Prepares the distributed Z_lP matrix and its atom-blocked column layout. Column block A
    1120              : !>        contains all optimized functions assigned to atom A. Every local block is reserved once
    1121              : !>        because several AA/AB contraction blocks may contribute to the same block.
    1122              : !> \param bs_env ...
    1123              : !> \param mat_phi_mu_l ...
    1124              : !> \param mat_Z_lP ...
    1125              : !> \param dist_Z ...
    1126              : !> \param row_size_grid ...
    1127              : !> \param row_dist_grid ...
    1128              : !> \param col_dist_ri ...
    1129              : !> \param ri_blk_sizes ...
    1130              : !> \param row_offset ...
    1131              : !> \param myprow ...
    1132              : !> \param mypcol ...
    1133              : !> \param max_ao_size ...
    1134              : ! **************************************************************************************************
    1135            4 :    SUBROUTINE prepare_Z_lP_auto_ri(bs_env, mat_phi_mu_l, mat_Z_lP, dist_Z, row_size_grid, &
    1136              :                                    row_dist_grid, col_dist_ri, ri_blk_sizes, row_offset, &
    1137              :                                    myprow, mypcol, max_ao_size)
    1138              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1139              :       TYPE(dbcsr_type), INTENT(IN)                       :: mat_phi_mu_l
    1140              :       TYPE(dbcsr_type), INTENT(OUT)                      :: mat_Z_lP
    1141              :       TYPE(dbcsr_distribution_type), INTENT(OUT)         :: dist_Z
    1142              :       INTEGER, DIMENSION(:), POINTER                     :: row_size_grid, row_dist_grid, &
    1143              :                                                             col_dist_ri, ri_blk_sizes
    1144              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: row_offset
    1145              :       INTEGER, INTENT(OUT)                               :: myprow, mypcol, max_ao_size
    1146              : 
    1147              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'prepare_Z_lP_auto_ri'
    1148              : 
    1149              :       INTEGER                                            :: handle, i_blk, iatom, npcol
    1150              :       TYPE(dbcsr_distribution_type)                      :: dist_phi
    1151              : 
    1152            4 :       CALL timeset(routineN, handle)
    1153              : 
    1154            4 :       CALL dbcsr_get_info(mat_phi_mu_l, row_blk_size=row_size_grid, distribution=dist_phi)
    1155              :       CALL dbcsr_distribution_get(dist_phi, row_dist=row_dist_grid, npcols=npcol, &
    1156            4 :                                   myprow=myprow, mypcol=mypcol)
    1157           12 :       ALLOCATE (row_offset(SIZE(row_size_grid)))
    1158            4 :       row_offset(1) = 0
    1159           12 :       DO i_blk = 2, SIZE(row_size_grid)
    1160           12 :          row_offset(i_blk) = row_offset(i_blk - 1) + row_size_grid(i_blk - 1)
    1161              :       END DO
    1162              : 
    1163           20 :       ALLOCATE (ri_blk_sizes(bs_env%n_atom), col_dist_ri(bs_env%n_atom))
    1164           32 :       ri_blk_sizes = bs_env%auto_ri%sizes_opt_RI
    1165           16 :       DO iatom = 1, bs_env%n_atom
    1166           16 :          col_dist_ri(iatom) = MOD(iatom - 1, npcol)
    1167              :       END DO
    1168              :       CALL dbcsr_distribution_new(dist_Z, template=dist_phi, row_dist=row_dist_grid, &
    1169            4 :                                   col_dist=col_dist_ri)
    1170              :       CALL dbcsr_create(mat_Z_lP, name='mat_Z_lP localized AA/AB', dist=dist_Z, &
    1171              :                         matrix_type=dbcsr_type_no_symmetry, row_blk_size=row_size_grid, &
    1172            4 :                         col_blk_size=ri_blk_sizes)
    1173            4 :       CALL dbcsr_reserve_all_blocks(mat_Z_lP)
    1174            4 :       CALL dbcsr_set(mat_Z_lP, 0.0_dp)
    1175              : 
    1176            4 :       max_ao_size = 0
    1177           16 :       DO iatom = 1, bs_env%n_atom
    1178              :          max_ao_size = MAX(max_ao_size, bs_env%i_ao_end_from_atom(iatom) - &
    1179           16 :                            bs_env%i_ao_start_from_atom(iatom) + 1)
    1180              :       END DO
    1181              : 
    1182            4 :       CALL timestop(handle)
    1183              : 
    1184            8 :    END SUBROUTINE prepare_Z_lP_auto_ri
    1185              : 
    1186              : ! **************************************************************************************************
    1187              : !> \brief Tests whether every fitted atomic block can use the complete RI-RS grid.
    1188              : !>
    1189              : !>        For a block centered on A, every grid point must satisfy
    1190              : !>
    1191              : !>          |r_l - R_A| <= R_A^fit,
    1192              : !>
    1193              : !>        and every atom B whose AOs can contribute must satisfy
    1194              : !>
    1195              : !>          |R_B - R_A| <= R_B^AO + R_A^fit.
    1196              : !>
    1197              : !>        Pair-midpoint grids do not satisfy this atom-centered criterion in general.
    1198              : !> \param bs_env ...
    1199              : !> \param ri_rs_grid_points ...
    1200              : !> \param common_grid_available ...
    1201              : !> \param have_fitted_columns ...
    1202              : ! **************************************************************************************************
    1203            4 :    SUBROUTINE common_Z_lP_grid_available(bs_env, ri_rs_grid_points, &
    1204              :                                          common_grid_available, have_fitted_columns)
    1205              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1206              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: ri_rs_grid_points
    1207              :       LOGICAL, INTENT(OUT)                               :: common_grid_available, &
    1208              :                                                             have_fitted_columns
    1209              : 
    1210              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'common_Z_lP_grid_available'
    1211              : 
    1212              :       INTEGER                                            :: AB_block, atom_A, atom_B, fit_atom, &
    1213              :                                                             handle, iatom, igrid, n_to_a, natom
    1214            4 :       LOGICAL, ALLOCATABLE, DIMENSION(:)                 :: active_atom
    1215              :       REAL(KIND=dp)                                      :: cutoff_ri
    1216              :       REAL(KIND=dp), DIMENSION(3)                        :: center
    1217            4 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
    1218              : 
    1219            4 :       CALL timeset(routineN, handle)
    1220              : 
    1221            4 :       particle_set => bs_env%ri_rs%particle_set
    1222            4 :       natom = bs_env%n_atom
    1223            4 :       common_grid_available = .TRUE.
    1224            4 :       have_fitted_columns = .FALSE.
    1225           12 :       ALLOCATE (active_atom(natom))
    1226           16 :       DO fit_atom = 1, natom
    1227           12 :          IF (bs_env%auto_ri%sizes_opt_RI(fit_atom) == 0) CYCLE
    1228           12 :          have_fitted_columns = .TRUE.
    1229           12 :          active_atom = .FALSE.
    1230           66 :          DO AB_block = 1, bs_env%auto_ri%AB_block_count
    1231           54 :             atom_A = bs_env%auto_ri%AB_atom_A(AB_block)
    1232           54 :             atom_B = bs_env%auto_ri%AB_atom_B(AB_block)
    1233           54 :             n_to_a = bs_env%auto_ri%AB_size_opt_RI_to_A(AB_block)
    1234           54 :             IF (.NOT. (fit_atom == atom_A .AND. n_to_a > 0) .AND. &
    1235              :                 .NOT. (fit_atom == atom_B .AND. &
    1236              :                        n_to_a < bs_env%auto_ri%AB_size_opt_RI(AB_block))) CYCLE
    1237           18 :             active_atom(atom_A) = .TRUE.
    1238           30 :             IF (atom_B /= atom_A) active_atom(atom_B) = .TRUE.
    1239              :          END DO
    1240              : 
    1241           48 :          center = particle_set(fit_atom)%r
    1242           12 :          IF (bs_env%ri_rs%cutoff_radius_ri_rs > 0.0_dp) THEN
    1243              :             cutoff_ri = bs_env%ri_rs%cutoff_radius_ri_rs
    1244              :          ELSE
    1245              :             cutoff_ri = 0.0_dp
    1246           48 :             DO iatom = 1, natom
    1247           36 :                IF (.NOT. active_atom(iatom)) CYCLE
    1248              :                cutoff_ri = MAX(cutoff_ri, bs_env%ri_metric%cutoff_radius + &
    1249              :                                bs_env%ri_rs%radius_ri_per_atom(iatom) + &
    1250          102 :                                NORM2(center - particle_set(iatom)%r))
    1251              :             END DO
    1252              :          END IF
    1253              : 
    1254          390 :          DO igrid = 1, bs_env%ri_rs%n_grid_points
    1255         1524 :             IF (NORM2(ri_rs_grid_points(1:3, igrid) - center) > cutoff_ri) THEN
    1256            0 :                common_grid_available = .FALSE.
    1257            0 :                EXIT
    1258              :             END IF
    1259              :          END DO
    1260           12 :          IF (.NOT. common_grid_available) EXIT
    1261              : 
    1262           48 :          DO iatom = 1, natom
    1263          144 :             IF (NORM2(particle_set(iatom)%r - center) > &
    1264           12 :                 bs_env%ri_rs%radius_ao_per_atom(iatom) + cutoff_ri) THEN
    1265            0 :                common_grid_available = .FALSE.
    1266            0 :                EXIT
    1267              :             END IF
    1268              :          END DO
    1269           16 :          IF (.NOT. common_grid_available) EXIT
    1270              :       END DO
    1271            4 :       DEALLOCATE (active_atom)
    1272              : 
    1273            4 :       CALL timestop(handle)
    1274              : 
    1275            4 :    END SUBROUTINE common_Z_lP_grid_available
    1276              : 
    1277              : ! **************************************************************************************************
    1278              : !> \brief Evaluates the AO collocation matrix on the complete RI-RS grid,
    1279              : !>
    1280              : !>          ϕ_lμ = ϕ_μ(r_l),  l = 1, ..., N_grid.
    1281              : !>
    1282              : !>        "Complete common grid" means that the same full set of grid points is valid for every
    1283              : !>        optimized RI column block. The enclosing sphere is only an implementation device passed
    1284              : !>        to build_phi_on_sphere; it contains every r_l and every AO center, so its chosen center
    1285              : !>        does not select an AA or AB contraction.
    1286              : !> \param bs_env ...
    1287              : !> \param qs_kind_set ...
    1288              : !> \param ri_rs_grid_points ...
    1289              : !> \param n_ao_total ...
    1290              : !> \param local_grid_idx ...
    1291              : !> \param ngrid ...
    1292              : !> \param phi_local ...
    1293              : !> \param ao_col_map ...
    1294              : !> \param n_ao_used ...
    1295              : ! **************************************************************************************************
    1296            8 :    SUBROUTINE build_phi_on_complete_grid(bs_env, qs_kind_set, &
    1297            4 :                                          ri_rs_grid_points, n_ao_total, local_grid_idx, ngrid, &
    1298              :                                          phi_local, ao_col_map, n_ao_used)
    1299              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1300              :       TYPE(qs_kind_type), DIMENSION(:), POINTER          :: qs_kind_set
    1301              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: ri_rs_grid_points
    1302              :       INTEGER, INTENT(IN)                                :: n_ao_total
    1303              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: local_grid_idx
    1304              :       INTEGER, INTENT(OUT)                               :: ngrid
    1305              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
    1306              :          INTENT(OUT)                                     :: phi_local
    1307              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: ao_col_map
    1308              :       INTEGER, INTENT(OUT)                               :: n_ao_used
    1309              : 
    1310              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'build_phi_on_complete_grid'
    1311              : 
    1312              :       INTEGER                                            :: handle, iatom, igrid, reference_atom
    1313              :       REAL(KIND=dp)                                      :: cutoff_ri
    1314              :       REAL(KIND=dp), DIMENSION(3)                        :: center
    1315            4 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
    1316              : 
    1317            4 :       CALL timeset(routineN, handle)
    1318              : 
    1319            4 :       particle_set => bs_env%ri_rs%particle_set
    1320            4 :       reference_atom = 1
    1321           16 :       center = particle_set(reference_atom)%r
    1322            4 :       cutoff_ri = 0.0_dp
    1323          130 :       DO igrid = 1, bs_env%ri_rs%n_grid_points
    1324          508 :          cutoff_ri = MAX(cutoff_ri, NORM2(ri_rs_grid_points(1:3, igrid) - center))
    1325              :       END DO
    1326           16 :       DO iatom = 1, bs_env%n_atom
    1327           52 :          cutoff_ri = MAX(cutoff_ri, NORM2(particle_set(iatom)%r - center))
    1328              :       END DO
    1329            4 :       cutoff_ri = cutoff_ri + 1.0_dp
    1330              : 
    1331              :       CALL build_phi_on_sphere(bs_env, qs_kind_set, &
    1332              :                                ri_rs_grid_points, reference_atom, cutoff_ri, n_ao_total, &
    1333              :                                local_grid_idx, ngrid, phi_local, ao_col_map, n_ao_used, &
    1334            4 :                                center=center)
    1335              : 
    1336            4 :       CALL timestop(handle)
    1337              : 
    1338            4 :    END SUBROUTINE build_phi_on_complete_grid
    1339              : 
    1340              : ! **************************************************************************************************
    1341              : !> \brief Writes Z_l,p0+p += z_lp into the process-owned grid-row blocks of one atomic column
    1342              : !>        block. The routine performs no explicit MPI communication; DBCSR summation combines
    1343              : !>        contributions when a block receives columns from more than one contraction group.
    1344              : !> \param mat_Z_lP ...
    1345              : !> \param z_block ...
    1346              : !> \param local_grid_idx ...
    1347              : !> \param n_local_grid ...
    1348              : !> \param atom_index ...
    1349              : !> \param first_column ...
    1350              : !> \param atom_block_size ...
    1351              : !> \param r_blk_sizes ...
    1352              : !> \param row_offset ...
    1353              : !> \param row_dist ...
    1354              : !> \param myprow ...
    1355              : !> \param eps_filter ...
    1356              : ! **************************************************************************************************
    1357           18 :    SUBROUTINE add_Z_lP_columns(mat_Z_lP, z_block, local_grid_idx, n_local_grid, atom_index, &
    1358           18 :                                first_column, atom_block_size, r_blk_sizes, row_offset, row_dist, &
    1359              :                                myprow, eps_filter)
    1360              : 
    1361              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_Z_lP
    1362              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: z_block
    1363              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: local_grid_idx
    1364              :       INTEGER, INTENT(IN)                                :: n_local_grid, atom_index, first_column, &
    1365              :                                                             atom_block_size
    1366              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: r_blk_sizes, row_offset, row_dist
    1367              :       INTEGER, INTENT(IN)                                :: myprow
    1368              :       REAL(KIND=dp), INTENT(IN)                          :: eps_filter
    1369              : 
    1370              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'add_Z_lP_columns'
    1371              : 
    1372              :       INTEGER                                            :: current_chunk_size, g_pt, handle, i_blk, &
    1373              :                                                             loc_ptr, ncolumn, r_end, r_start
    1374              :       LOGICAL                                            :: row_owned
    1375           18 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: Z_blk
    1376              : 
    1377           18 :       CALL timeset(routineN, handle)
    1378              : 
    1379           18 :       ncolumn = SIZE(z_block, 2)
    1380           18 :       CPASSERT(first_column > 0)
    1381           18 :       CPASSERT(first_column + ncolumn - 1 <= atom_block_size)
    1382          126 :       ALLOCATE (Z_blk(MAXVAL(r_blk_sizes), atom_block_size), source=0.0_dp)
    1383           18 :       loc_ptr = 1
    1384           72 :       DO i_blk = 1, SIZE(r_blk_sizes)
    1385           54 :          r_start = row_offset(i_blk) + 1
    1386           54 :          r_end = row_offset(i_blk) + r_blk_sizes(i_blk)
    1387           54 :          current_chunk_size = r_blk_sizes(i_blk)
    1388           54 :          row_owned = row_dist(i_blk) == myprow
    1389           54 :          Z_blk = 0.0_dp
    1390          684 :          DO WHILE (loc_ptr <= n_local_grid)
    1391          666 :             g_pt = local_grid_idx(loc_ptr)
    1392          666 :             IF (g_pt > r_end) EXIT
    1393          630 :             IF (row_owned) THEN
    1394              :                Z_blk(g_pt - r_start + 1, first_column:first_column + ncolumn - 1) = &
    1395         1302 :                   z_block(loc_ptr, 1:ncolumn)
    1396              :             END IF
    1397          666 :             loc_ptr = loc_ptr + 1
    1398              :          END DO
    1399         3750 :          IF (row_owned .AND. MAXVAL(ABS(Z_blk(1:current_chunk_size, :))) > eps_filter) THEN
    1400              :             CALL dbcsr_put_block(mat_Z_lP, row=i_blk, col=atom_index, &
    1401           27 :                                  block=Z_blk(1:current_chunk_size, :), summation=.TRUE.)
    1402              :          END IF
    1403              :       END DO
    1404           18 :       DEALLOCATE (Z_blk)
    1405              : 
    1406           18 :       CALL timestop(handle)
    1407              : 
    1408           18 :    END SUBROUTINE add_Z_lP_columns
    1409              : 
    1410              : ! **************************************************************************************************
    1411              : !> \brief Computes m_l^A = OR_{μ∈A}[ϕ_μ(r_l) /= 0]. The product
    1412              : !>
    1413              : !>          ϕ_μ(r_l) ϕ_ν(r_l),  μ∈A, ν∈B,
    1414              : !>
    1415              : !>        is evaluated only where m_l^A AND m_l^B is true. This avoids products and contractions
    1416              : !>        whenever either atom has no nonzero AO on r_l; no additional numerical threshold is used.
    1417              : !> \param bs_env ...
    1418              : !> \param phi_val ...
    1419              : !> \param ao_col_map ...
    1420              : !> \param nonzero_ao ...
    1421              : ! **************************************************************************************************
    1422            6 :    SUBROUTINE compute_nonzero_AO_grid_mask(bs_env, phi_val, ao_col_map, nonzero_ao)
    1423              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1424              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: phi_val
    1425              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: ao_col_map
    1426              :       LOGICAL, ALLOCATABLE, DIMENSION(:, :), INTENT(OUT) :: nonzero_ao
    1427              : 
    1428              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_nonzero_AO_grid_mask'
    1429              : 
    1430              :       INTEGER                                            :: col, first, handle, iatom, last, natom, &
    1431              :                                                             ngrid, point
    1432              : 
    1433            6 :       CALL timeset(routineN, handle)
    1434              : 
    1435            6 :       ngrid = SIZE(phi_val, 1)
    1436            6 :       natom = bs_env%n_atom
    1437           24 :       ALLOCATE (nonzero_ao(ngrid, natom), source=.FALSE.)
    1438              :       !$OMP PARALLEL DO DEFAULT(NONE) SCHEDULE(STATIC) &
    1439              :       !$OMP SHARED(bs_env, phi_val, ao_col_map, nonzero_ao, ngrid, natom) &
    1440            6 :       !$OMP PRIVATE(iatom, first, last, col, point)
    1441              :       DO iatom = 1, natom
    1442              :          first = ao_col_map(bs_env%i_ao_start_from_atom(iatom))
    1443              :          IF (first == 0) CYCLE
    1444              :          last = ao_col_map(bs_env%i_ao_end_from_atom(iatom))
    1445              :          DO col = first, last
    1446              :             DO point = 1, ngrid
    1447              :                nonzero_ao(point, iatom) = &
    1448              :                   nonzero_ao(point, iatom) .OR. phi_val(point, col) /= 0.0_dp
    1449              :             END DO
    1450              :          END DO
    1451              :       END DO
    1452              :       !$OMP END PARALLEL DO
    1453              : 
    1454            6 :       CALL timestop(handle)
    1455            6 :    END SUBROUTINE compute_nonzero_AO_grid_mask
    1456              : 
    1457              : ! **************************************************************************************************
    1458              : !> \brief Computes d_lp = Σ_{μνP} ϕ_μ(r_l)ϕ_ν(r_l)(μν|P)U_Pp for all optimized
    1459              : !>        columns p that contain reference RI functions P on atom I.
    1460              : !> \param bs_env ...
    1461              : !> \param ctx ...
    1462              : !> \param phi_val ...
    1463              : !> \param ao_col_map ...
    1464              : !> \param d_lp ...
    1465              : !> \param n_grid ...
    1466              : !> \param iatom ...
    1467              : !> \param U_Pp ...
    1468              : !> \param max_ao_size ...
    1469              : ! **************************************************************************************************
    1470            6 :    SUBROUTINE compute_d_lp_auto_ri_atom(bs_env, ctx, phi_val, ao_col_map, d_lp, n_grid, iatom, &
    1471            6 :                                         U_Pp, max_ao_size)
    1472              : !$    USE OMP_LIB, ONLY: omp_get_max_threads, omp_get_thread_num
    1473              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1474              :       TYPE(gw_3c_ctx_type), INTENT(IN)                   :: ctx
    1475              :       REAL(KIND=dp), CONTIGUOUS, DIMENSION(:, :), &
    1476              :          INTENT(IN)                                      :: phi_val
    1477              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: ao_col_map
    1478              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(INOUT)      :: d_lp
    1479              :       INTEGER, INTENT(IN)                                :: n_grid, iatom
    1480              :       REAL(KIND=dp), CONTIGUOUS, DIMENSION(:, :), &
    1481              :          INTENT(IN)                                      :: U_Pp
    1482              :       INTEGER, INTENT(IN)                                :: max_ao_size
    1483              : 
    1484              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_d_lp_auto_ri_atom'
    1485              :       INTEGER, PARAMETER                                 :: grid_chunk = 1024
    1486              : 
    1487              :       INTEGER :: jatom, katom, c, handle, i_thread, j, jk_idx, jsize, jstart, k, ksize, &
    1488              :                  kstart, l, l0, ncol, nRI_ref, nthreads, ri, thread_id
    1489              :       LOGICAL                                            :: screened
    1490              :       REAL(KIND=dp)                                      :: pair_factor
    1491            6 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: int_2d_prv, rho_chunk
    1492            6 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: d_lp_threads, int_3c_prv
    1493            6 :       TYPE(gw_3c_ws_type)                                :: ws
    1494              : 
    1495            6 :       LOGICAL, ALLOCATABLE, DIMENSION(:, :) :: nonzero_ao
    1496            6 :       INTEGER, ALLOCATABLE, DIMENSION(:) :: grid_index
    1497              :       INTEGER :: n_grid_pair, grid_l, point
    1498            6 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :) :: grid_result
    1499              : 
    1500            6 :       CALL timeset(routineN, handle)
    1501            6 :       ncol = SIZE(U_Pp, 2)
    1502            6 :       nRI_ref = get_ref_RI_size(bs_env, iatom)
    1503            6 :       nthreads = 1
    1504            6 : !$    nthreads = omp_get_max_threads()
    1505            6 :       CPASSERT(SIZE(d_lp, 1) == n_grid)
    1506            6 :       CPASSERT(SIZE(d_lp, 2) == ncol)
    1507            6 :       CPASSERT(SIZE(U_Pp, 1) == nRI_ref)
    1508           30 :       ALLOCATE (d_lp_threads(n_grid, ncol, nthreads), source=0.0_dp)
    1509              : 
    1510            6 :       CALL compute_nonzero_AO_grid_mask(bs_env, phi_val, ao_col_map, nonzero_ao)
    1511              : 
    1512              :       !$OMP PARALLEL DEFAULT(NONE) &
    1513              :       !$OMP SHARED(nonzero_ao, bs_env, ctx, phi_val, ao_col_map, d_lp, n_grid, iatom, &
    1514              :       !$OMP        U_Pp, &
    1515              :       !$OMP        max_ao_size, ncol, nRI_ref, d_lp_threads, nthreads) &
    1516              :       !$OMP PRIVATE(grid_index, n_grid_pair, grid_l, point, grid_result, &
    1517              :       !$OMP         jatom, katom, c, &
    1518              :       !$OMP         i_thread, j, jk_idx, jsize, jstart, k, ksize, &
    1519              :       !$OMP         kstart, l, l0, ri, screened, pair_factor, int_2d_prv, rho_chunk, &
    1520            6 :       !$OMP         int_3c_prv, ws, thread_id)
    1521              : 
    1522              :       thread_id = 1
    1523              : !$    thread_id = omp_get_thread_num() + 1
    1524              : 
    1525              :       CALL gw_3c_ws_create(ws, ctx)
    1526              :       ALLOCATE (int_3c_prv(max_ao_size, max_ao_size, ncol))
    1527              :       ALLOCATE (int_2d_prv(max_ao_size*max_ao_size, ncol))
    1528              :       ALLOCATE (rho_chunk(grid_chunk, max_ao_size*max_ao_size))
    1529              :       ALLOCATE (grid_index(n_grid), grid_result(grid_chunk, ncol))
    1530              : 
    1531              :       !$OMP DO SCHEDULE(DYNAMIC)
    1532              :       DO jatom = 1, bs_env%n_atom
    1533              :          DO katom = jatom, bs_env%n_atom
    1534              :             jstart = ao_col_map(bs_env%i_ao_start_from_atom(jatom))
    1535              :             kstart = ao_col_map(bs_env%i_ao_start_from_atom(katom))
    1536              :             IF (jstart == 0 .OR. kstart == 0) CYCLE
    1537              :             jsize = bs_env%i_ao_end_from_atom(jatom) - bs_env%i_ao_start_from_atom(jatom) + 1
    1538              :             ksize = bs_env%i_ao_end_from_atom(katom) - bs_env%i_ao_start_from_atom(katom) + 1
    1539              :             n_grid_pair = 0
    1540              :             DO grid_l = 1, n_grid
    1541              :                IF (.NOT. (nonzero_ao(grid_l, jatom) .AND. nonzero_ao(grid_l, katom))) CYCLE
    1542              :                n_grid_pair = n_grid_pair + 1
    1543              :                grid_index(n_grid_pair) = grid_l
    1544              :             END DO
    1545              :             IF (n_grid_pair == 0) CYCLE
    1546              :             int_3c_prv(1:jsize, 1:ksize, 1:ncol) = 0.0_dp
    1547              :             CALL build_3c_integral_block_auto_ri_ctx( &
    1548              :                int_3c_prv(1:jsize, 1:ksize, 1:ncol), ctx, ws, &
    1549              :                atom_j=jatom, atom_k=katom, atom_i=iatom, &
    1550              :                transform=U_Pp, transform_row=1, screened=screened)
    1551              :             IF (screened) CYCLE
    1552              :             DO ri = 1, ncol
    1553              :                DO k = 1, ksize
    1554              :                   DO j = 1, jsize
    1555              :                      jk_idx = (k - 1)*jsize + j
    1556              :                      int_2d_prv(jk_idx, ri) = int_3c_prv(j, k, ri)
    1557              :                   END DO
    1558              :                END DO
    1559              :             END DO
    1560              :             pair_factor = 1.0_dp
    1561              :             IF (jatom /= katom) pair_factor = 2.0_dp
    1562              :             DO l0 = 1, n_grid_pair, grid_chunk
    1563              :                c = MIN(grid_chunk, n_grid_pair - l0 + 1)
    1564              :                DO k = 1, ksize
    1565              :                   DO j = 1, jsize
    1566              :                      jk_idx = (k - 1)*jsize + j
    1567              :                      DO l = 1, c
    1568              :                         point = grid_index(l0 + l - 1)
    1569              :                         rho_chunk(l, jk_idx) = phi_val(point, jstart + j - 1)* &
    1570              :                                                phi_val(point, kstart + k - 1)
    1571              :                      END DO
    1572              :                   END DO
    1573              :                END DO
    1574              :                CALL dgemm('N', 'N', c, ncol, jsize*ksize, pair_factor, rho_chunk, grid_chunk, &
    1575              :                           int_2d_prv, max_ao_size*max_ao_size, 0.0_dp, &
    1576              :                           grid_result, grid_chunk)
    1577              :                DO ri = 1, ncol
    1578              :                   DO l = 1, c
    1579              :                      point = grid_index(l0 + l - 1)
    1580              :                      d_lp_threads(point, ri, thread_id) = &
    1581              :                         d_lp_threads(point, ri, thread_id) + grid_result(l, ri)
    1582              :                   END DO
    1583              :                END DO
    1584              :             END DO
    1585              :          END DO
    1586              :       END DO
    1587              :       !$OMP END DO
    1588              : 
    1589              :       !$OMP DO COLLAPSE(2) SCHEDULE(STATIC)
    1590              :       DO ri = 1, ncol
    1591              :          DO l = 1, n_grid
    1592              :             DO i_thread = 1, nthreads
    1593              :                d_lp(l, ri) = d_lp(l, ri) + d_lp_threads(l, ri, i_thread)
    1594              :             END DO
    1595              :          END DO
    1596              :       END DO
    1597              :       !$OMP END DO
    1598              :       DEALLOCATE (int_3c_prv, int_2d_prv, rho_chunk)
    1599              :       DEALLOCATE (grid_index, grid_result)
    1600              :       CALL gw_3c_ws_release(ws)
    1601              :       !$OMP END PARALLEL
    1602              : 
    1603            6 :       DEALLOCATE (d_lp_threads)
    1604              : 
    1605            6 :       DEALLOCATE (nonzero_ao)
    1606              : 
    1607            6 :       CALL timestop(handle)
    1608              : 
    1609           12 :    END SUBROUTINE compute_d_lp_auto_ri_atom
    1610              : 
    1611              : ! **************************************************************************************************
    1612              : !> \brief Computes d_lp = Σ_{μνP} ϕ_μ(r_l)ϕ_ν(r_l)(μν|P)U_Pp for all AA and AB
    1613              : !>        columns p of one RI-RS matrix block. P can belong to either atom of an AB contraction.
    1614              : !> \param bs_env ...
    1615              : !> \param ctx ...
    1616              : !> \param phi_val ...
    1617              : !> \param ao_col_map ...
    1618              : !> \param d_lp ...
    1619              : !> \param n_grid ...
    1620              : !> \param ri_coefficients ...
    1621              : !> \param active_atom ...
    1622              : !> \param max_ao_size ...
    1623              : !> \param atom_j_mepos ...
    1624              : !> \param atom_j_stride ...
    1625              : ! **************************************************************************************************
    1626            0 :    SUBROUTINE compute_d_lp_auto_ri_atoms(bs_env, ctx, phi_val, ao_col_map, d_lp, n_grid, &
    1627            0 :                                          ri_coefficients, active_atom, max_ao_size, atom_j_mepos, &
    1628              :                                          atom_j_stride)
    1629              : !$    USE OMP_LIB, ONLY: omp_get_max_threads, omp_get_thread_num
    1630              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1631              :       TYPE(gw_3c_ctx_type), INTENT(IN)                   :: ctx
    1632              :       REAL(KIND=dp), CONTIGUOUS, DIMENSION(:, :), &
    1633              :          INTENT(IN)                                      :: phi_val
    1634              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: ao_col_map
    1635              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(INOUT)      :: d_lp
    1636              :       INTEGER, INTENT(IN)                                :: n_grid
    1637              :       REAL(KIND=dp), DIMENSION(:, :, :), INTENT(IN)      :: ri_coefficients
    1638              :       LOGICAL, DIMENSION(:), INTENT(IN)                  :: active_atom
    1639              :       INTEGER, INTENT(IN)                                :: max_ao_size, atom_j_mepos, atom_j_stride
    1640              : 
    1641              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_d_lp_auto_ri_atoms'
    1642              :       INTEGER, PARAMETER                                 :: grid_chunk = 1024
    1643              : 
    1644              :       INTEGER :: active_column, iatom, jatom, katom, c, handle, i_thread, j, jk_idx, jsize, &
    1645              :                  jstart, k, ksize, kstart, l, l0, &
    1646              :                  max_active, nactive, ncol, nRI_ref, nthreads, ri, thread_id
    1647            0 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: atom_ncol
    1648            0 :       INTEGER, ALLOCATABLE, DIMENSION(:, :)              :: atom_column
    1649              :       LOGICAL                                            :: any_integral, screened
    1650              :       REAL(KIND=dp)                                      :: pair_factor
    1651            0 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: int_2d_prv, rho_chunk
    1652            0 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: coefficient_compact, d_lp_threads, &
    1653            0 :                                                             int_3c_prv, int_3c_atom
    1654            0 :       TYPE(gw_3c_ws_type)                                :: ws
    1655              : 
    1656            0 :       LOGICAL, ALLOCATABLE, DIMENSION(:, :) :: nonzero_ao
    1657            0 :       INTEGER, ALLOCATABLE, DIMENSION(:) :: grid_index
    1658              :       INTEGER :: n_grid_pair, grid_l, point
    1659            0 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :) :: grid_result
    1660              : 
    1661            0 :       CALL timeset(routineN, handle)
    1662            0 :       ncol = SIZE(d_lp, 2)
    1663            0 :       nthreads = 1
    1664            0 : !$    nthreads = omp_get_max_threads()
    1665            0 :       CPASSERT(SIZE(d_lp, 1) == n_grid)
    1666            0 :       CPASSERT(SIZE(ri_coefficients, 2) == ncol)
    1667            0 :       CPASSERT(SIZE(ri_coefficients, 3) == SIZE(active_atom))
    1668            0 :       ALLOCATE (atom_ncol(SIZE(active_atom)), atom_column(ncol, SIZE(active_atom)))
    1669            0 :       atom_ncol = 0
    1670            0 :       atom_column = 0
    1671            0 :       DO iatom = 1, SIZE(active_atom)
    1672            0 :          IF (.NOT. active_atom(iatom)) CYCLE
    1673            0 :          nRI_ref = get_ref_RI_size(bs_env, iatom)
    1674            0 :          DO ri = 1, ncol
    1675            0 :             IF (.NOT. ANY(ri_coefficients(1:nRI_ref, ri, iatom) /= 0.0_dp)) CYCLE
    1676            0 :             atom_ncol(iatom) = atom_ncol(iatom) + 1
    1677            0 :             atom_column(atom_ncol(iatom), iatom) = ri
    1678              :          END DO
    1679              :       END DO
    1680            0 :       max_active = MAXVAL(atom_ncol)
    1681            0 :       CPASSERT(max_active > 0)
    1682            0 :       ALLOCATE (coefficient_compact(SIZE(ri_coefficients, 1), max_active, &
    1683            0 :                                     SIZE(active_atom)), source=0.0_dp)
    1684            0 :       DO iatom = 1, SIZE(active_atom)
    1685            0 :          nRI_ref = get_ref_RI_size(bs_env, iatom)
    1686            0 :          DO active_column = 1, atom_ncol(iatom)
    1687            0 :             ri = atom_column(active_column, iatom)
    1688              :             coefficient_compact(1:nRI_ref, active_column, iatom) = &
    1689            0 :                ri_coefficients(1:nRI_ref, ri, iatom)
    1690              :          END DO
    1691              :       END DO
    1692            0 :       ALLOCATE (d_lp_threads(n_grid, ncol, nthreads), source=0.0_dp)
    1693              : 
    1694            0 :       CALL compute_nonzero_AO_grid_mask(bs_env, phi_val, ao_col_map, nonzero_ao)
    1695              : 
    1696              :       !$OMP PARALLEL DEFAULT(NONE) &
    1697              :       !$OMP SHARED(nonzero_ao, bs_env, ctx, phi_val, ao_col_map, d_lp, n_grid, ri_coefficients, &
    1698              :       !$OMP        active_atom, max_ao_size, atom_j_mepos, atom_j_stride, ncol, &
    1699              :       !$OMP        d_lp_threads, nthreads, atom_ncol, &
    1700              :       !$OMP        atom_column, coefficient_compact, max_active) &
    1701              :       !$OMP PRIVATE(grid_index, n_grid_pair, grid_l, point, grid_result, &
    1702              :       !$OMP         active_column, iatom, jatom, katom, c, i_thread, j, jk_idx, jsize, jstart, &
    1703              :       !$OMP         k, ksize, kstart, l, l0, &
    1704              :       !$OMP         nactive, nRI_ref, ri, any_integral, screened, pair_factor, &
    1705              :       !$OMP         int_2d_prv, rho_chunk, &
    1706            0 :       !$OMP         int_3c_prv, int_3c_atom, ws, thread_id)
    1707              : 
    1708              :       thread_id = 1
    1709              : !$    thread_id = omp_get_thread_num() + 1
    1710              : 
    1711              :       CALL gw_3c_ws_create(ws, ctx)
    1712              :       ALLOCATE (int_3c_prv(max_ao_size, max_ao_size, ncol))
    1713              :       ALLOCATE (int_3c_atom(max_ao_size, max_ao_size, max_active))
    1714              :       ALLOCATE (int_2d_prv(max_ao_size*max_ao_size, ncol))
    1715              :       ALLOCATE (rho_chunk(grid_chunk, max_ao_size*max_ao_size))
    1716              :       ALLOCATE (grid_index(n_grid), grid_result(grid_chunk, ncol))
    1717              : 
    1718              :       !$OMP DO SCHEDULE(DYNAMIC)
    1719              :       DO jatom = atom_j_mepos + 1, bs_env%n_atom, atom_j_stride
    1720              :          DO katom = jatom, bs_env%n_atom
    1721              :             jstart = ao_col_map(bs_env%i_ao_start_from_atom(jatom))
    1722              :             kstart = ao_col_map(bs_env%i_ao_start_from_atom(katom))
    1723              :             IF (jstart == 0 .OR. kstart == 0) CYCLE
    1724              :             jsize = bs_env%i_ao_end_from_atom(jatom) - bs_env%i_ao_start_from_atom(jatom) + 1
    1725              :             ksize = bs_env%i_ao_end_from_atom(katom) - bs_env%i_ao_start_from_atom(katom) + 1
    1726              :             n_grid_pair = 0
    1727              :             DO grid_l = 1, n_grid
    1728              :                IF (.NOT. (nonzero_ao(grid_l, jatom) .AND. nonzero_ao(grid_l, katom))) CYCLE
    1729              :                n_grid_pair = n_grid_pair + 1
    1730              :                grid_index(n_grid_pair) = grid_l
    1731              :             END DO
    1732              :             IF (n_grid_pair == 0) CYCLE
    1733              :             int_3c_prv(1:jsize, 1:ksize, 1:ncol) = 0.0_dp
    1734              :             any_integral = .FALSE.
    1735              :             DO iatom = 1, SIZE(active_atom)
    1736              :                IF (.NOT. active_atom(iatom)) CYCLE
    1737              :                nRI_ref = get_ref_RI_size(bs_env, iatom)
    1738              :                nactive = atom_ncol(iatom)
    1739              :                int_3c_atom(1:jsize, 1:ksize, 1:nactive) = 0.0_dp
    1740              :                CALL build_3c_integral_block_auto_ri_ctx( &
    1741              :                   int_3c_atom(1:jsize, 1:ksize, 1:nactive), ctx, ws, &
    1742              :                   atom_j=jatom, atom_k=katom, atom_i=iatom, &
    1743              :                   transform=coefficient_compact(1:nRI_ref, 1:nactive, iatom), &
    1744              :                   transform_row=1, screened=screened)
    1745              :                IF (.NOT. screened) THEN
    1746              :                   any_integral = .TRUE.
    1747              :                   DO active_column = 1, nactive
    1748              :                      ri = atom_column(active_column, iatom)
    1749              :                      int_3c_prv(1:jsize, 1:ksize, ri) = &
    1750              :                         int_3c_prv(1:jsize, 1:ksize, ri) + &
    1751              :                         int_3c_atom(1:jsize, 1:ksize, active_column)
    1752              :                   END DO
    1753              :                END IF
    1754              :             END DO
    1755              :             IF (.NOT. any_integral) CYCLE
    1756              : 
    1757              :             DO ri = 1, ncol
    1758              :                DO k = 1, ksize
    1759              :                   DO j = 1, jsize
    1760              :                      jk_idx = (k - 1)*jsize + j
    1761              :                      int_2d_prv(jk_idx, ri) = int_3c_prv(j, k, ri)
    1762              :                   END DO
    1763              :                END DO
    1764              :             END DO
    1765              : 
    1766              :             pair_factor = 1.0_dp
    1767              :             IF (jatom /= katom) pair_factor = 2.0_dp
    1768              :             DO l0 = 1, n_grid_pair, grid_chunk
    1769              :                c = MIN(grid_chunk, n_grid_pair - l0 + 1)
    1770              :                DO k = 1, ksize
    1771              :                   DO j = 1, jsize
    1772              :                      jk_idx = (k - 1)*jsize + j
    1773              :                      DO l = 1, c
    1774              :                         point = grid_index(l0 + l - 1)
    1775              :                         rho_chunk(l, jk_idx) = phi_val(point, jstart + j - 1)* &
    1776              :                                                phi_val(point, kstart + k - 1)
    1777              :                      END DO
    1778              :                   END DO
    1779              :                END DO
    1780              :                CALL dgemm('N', 'N', c, ncol, jsize*ksize, pair_factor, rho_chunk, grid_chunk, &
    1781              :                           int_2d_prv, max_ao_size*max_ao_size, 0.0_dp, &
    1782              :                           grid_result, grid_chunk)
    1783              :                DO ri = 1, ncol
    1784              :                   DO l = 1, c
    1785              :                      point = grid_index(l0 + l - 1)
    1786              :                      d_lp_threads(point, ri, thread_id) = &
    1787              :                         d_lp_threads(point, ri, thread_id) + grid_result(l, ri)
    1788              :                   END DO
    1789              :                END DO
    1790              :             END DO
    1791              :          END DO
    1792              :       END DO
    1793              :       !$OMP END DO
    1794              : 
    1795              :       !$OMP DO COLLAPSE(2) SCHEDULE(STATIC)
    1796              :       DO ri = 1, ncol
    1797              :          DO l = 1, n_grid
    1798              :             DO i_thread = 1, nthreads
    1799              :                d_lp(l, ri) = d_lp(l, ri) + d_lp_threads(l, ri, i_thread)
    1800              :             END DO
    1801              :          END DO
    1802              :       END DO
    1803              :       !$OMP END DO
    1804              :       DEALLOCATE (int_3c_prv, int_3c_atom, int_2d_prv, rho_chunk)
    1805              :       DEALLOCATE (grid_index, grid_result)
    1806              :       CALL gw_3c_ws_release(ws)
    1807              :       !$OMP END PARALLEL
    1808              : 
    1809            0 :       DEALLOCATE (coefficient_compact, d_lp_threads, atom_column, atom_ncol)
    1810              : 
    1811            0 :       DEALLOCATE (nonzero_ao)
    1812              : 
    1813            0 :       CALL timestop(handle)
    1814              : 
    1815            0 :    END SUBROUTINE compute_d_lp_auto_ri_atoms
    1816              : 
    1817              : ! **************************************************************************************************
    1818              : !> \brief Computes d_lp = Σ_A Σ_{P∈A} d_lP U_Pp in rank-sized batches on a common grid.
    1819              : !> \param bs_env ...
    1820              : !> \param ctx ...
    1821              : !> \param phi_val ...
    1822              : !> \param ao_col_map ...
    1823              : !> \param d_lp ...
    1824              : !> \param n_grid ...
    1825              : !> \param max_ao_size ...
    1826              : !> \param mepos ...
    1827              : !> \param num_pe ...
    1828              : ! **************************************************************************************************
    1829            4 :    SUBROUTINE compute_d_lp_auto_ri_batch(bs_env, ctx, phi_val, ao_col_map, d_lp, n_grid, &
    1830              :                                          max_ao_size, mepos, num_pe)
    1831              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1832              :       TYPE(gw_3c_ctx_type), INTENT(IN)                   :: ctx
    1833              :       REAL(KIND=dp), CONTIGUOUS, DIMENSION(:, :), &
    1834              :          INTENT(IN)                                      :: phi_val
    1835              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: ao_col_map
    1836              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(INOUT)      :: d_lp
    1837              :       INTEGER, INTENT(IN)                                :: n_grid, max_ao_size, mepos, num_pe
    1838              : 
    1839              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_d_lp_auto_ri_batch'
    1840              : 
    1841              :       INTEGER                                            :: column, handle, iatom, n_done, n_total, &
    1842              :                                                             ncol_batch
    1843            4 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: global_map
    1844              :       REAL(KIND=dp)                                      :: item_start_time
    1845            4 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: d_lp_batch, U_Pp
    1846              : 
    1847            4 :       CALL timeset(routineN, handle)
    1848              : 
    1849            4 :       n_total = 0
    1850            4 :       DO iatom = mepos + 1, bs_env%n_atom, num_pe
    1851            6 :          n_total = n_total + 1
    1852              :       END DO
    1853            4 :       n_done = 0
    1854              : 
    1855           10 :       DO iatom = mepos + 1, bs_env%n_atom, num_pe
    1856            6 :          item_start_time = m_walltime()
    1857            6 :          CALL collect_auto_ri_columns_for_atom(bs_env, iatom, U_Pp, global_map)
    1858            6 :          ncol_batch = SIZE(global_map)
    1859            6 :          IF (ncol_batch == 0) THEN
    1860            0 :             DEALLOCATE (U_Pp, global_map)
    1861            0 :             n_done = n_done + 1
    1862              :             CALL print_Z_lP_progress(bs_env, n_done, n_total, &
    1863            0 :                                      m_walltime() - item_start_time)
    1864            0 :             CYCLE
    1865              :          END IF
    1866              : 
    1867           24 :          ALLOCATE (d_lp_batch(n_grid, ncol_batch), source=0.0_dp)
    1868              :          CALL compute_d_lp_auto_ri_atom(bs_env, ctx, phi_val, ao_col_map, d_lp_batch, &
    1869            6 :                                         n_grid, iatom, U_Pp, max_ao_size)
    1870           44 :          DO column = 1, ncol_batch
    1871         1409 :             d_lp(:, global_map(column)) = d_lp(:, global_map(column)) + d_lp_batch(:, column)
    1872              :          END DO
    1873            6 :          DEALLOCATE (U_Pp, global_map, d_lp_batch)
    1874            6 :          n_done = n_done + 1
    1875              :          CALL print_Z_lP_progress(bs_env, n_done, n_total, &
    1876           10 :                                   m_walltime() - item_start_time)
    1877              :       END DO
    1878            4 :       CALL timestop(handle)
    1879              : 
    1880            4 :    END SUBROUTINE compute_d_lp_auto_ri_batch
    1881              : 
    1882              : ! **************************************************************************************************
    1883              : !> \brief Collects every optimized column p containing reference functions P on atom A. It returns
    1884              : !>
    1885              : !>          U_Pp^A
    1886              : !>
    1887              : !>        together with the global optimized-column index of q.
    1888              : !> \param bs_env ...
    1889              : !> \param iatom ...
    1890              : !> \param U_Pp ...
    1891              : !> \param global_map ...
    1892              : ! **************************************************************************************************
    1893            6 :    SUBROUTINE collect_auto_ri_columns_for_atom(bs_env, iatom, U_Pp, global_map)
    1894              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1895              :       INTEGER, INTENT(IN)                                :: iatom
    1896              :       REAL(KIND=dp), ALLOCATABLE, INTENT(OUT)            :: U_Pp(:, :)
    1897              :       INTEGER, ALLOCATABLE, INTENT(OUT)                  :: global_map(:)
    1898              : 
    1899              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'collect_auto_ri_columns_for_atom'
    1900              : 
    1901              :       INTEGER :: AB_block, atom_A, atom_B, column, global_column, handle, local_column, ncol, &
    1902              :          ncol_batch, nRI_ref, nRI_ref_A, output_offset, row_first
    1903            6 :       REAL(KIND=dp), ALLOCATABLE                         :: U_Pp_AB(:, :)
    1904              : 
    1905            6 :       CALL timeset(routineN, handle)
    1906              : 
    1907            6 :       ncol_batch = 0
    1908           33 :       DO AB_block = 1, bs_env%auto_ri%AB_block_count
    1909           27 :          atom_A = bs_env%auto_ri%AB_atom_A(AB_block)
    1910           27 :          atom_B = bs_env%auto_ri%AB_atom_B(AB_block)
    1911           33 :          IF (iatom == atom_A .OR. iatom == atom_B) THEN
    1912           12 :             ncol_batch = ncol_batch + bs_env%auto_ri%AB_size_opt_RI(AB_block)
    1913              :          END IF
    1914              :       END DO
    1915              : 
    1916            6 :       nRI_ref = get_ref_RI_size(bs_env, iatom)
    1917           24 :       ALLOCATE (U_Pp(nRI_ref, ncol_batch), source=0.0_dp)
    1918           18 :       ALLOCATE (global_map(ncol_batch))
    1919            6 :       column = 0
    1920           33 :       DO AB_block = 1, bs_env%auto_ri%AB_block_count
    1921           27 :          atom_A = bs_env%auto_ri%AB_atom_A(AB_block)
    1922           27 :          atom_B = bs_env%auto_ri%AB_atom_B(AB_block)
    1923           27 :          IF (iatom /= atom_A .AND. iatom /= atom_B) CYCLE
    1924           12 :          ncol = bs_env%auto_ri%AB_size_opt_RI(AB_block)
    1925           12 :          CALL get_U_Pp_AB(bs_env%auto_ri, AB_block, U_Pp_AB)
    1926           12 :          row_first = 1
    1927           12 :          IF (iatom == atom_B .AND. atom_B /= atom_A) THEN
    1928            3 :             nRI_ref_A = get_ref_RI_size(bs_env, atom_A)
    1929            3 :             row_first = 1 + nRI_ref_A
    1930              :          END IF
    1931              :          U_Pp(:, column + 1:column + ncol) = &
    1932              :             U_Pp_AB( &
    1933         3892 :             row_first:row_first + nRI_ref - 1, 1:ncol)
    1934              : 
    1935           50 :          DO local_column = 1, ncol
    1936           38 :             IF (local_column <= bs_env%auto_ri%AB_size_opt_RI_to_A(AB_block)) THEN
    1937              :                output_offset = SUM(bs_env%auto_ri%sizes_opt_RI(:atom_A - 1)) + &
    1938           30 :                                bs_env%auto_ri%AB_first_p_A(AB_block) - 1
    1939           22 :                global_column = output_offset + local_column
    1940              :             ELSE
    1941           16 :                CPASSERT(atom_B /= atom_A)
    1942              :                output_offset = SUM(bs_env%auto_ri%sizes_opt_RI(:atom_B - 1)) + &
    1943           40 :                                bs_env%auto_ri%AB_first_p_B(AB_block) - 1
    1944              :                global_column = output_offset + local_column - &
    1945           16 :                                bs_env%auto_ri%AB_size_opt_RI_to_A(AB_block)
    1946              :             END IF
    1947           50 :             global_map(column + local_column) = global_column
    1948              :          END DO
    1949           12 :          column = column + ncol
    1950           33 :          DEALLOCATE (U_Pp_AB)
    1951              :       END DO
    1952            6 :       CPASSERT(column == ncol_batch)
    1953              : 
    1954            6 :       CALL timestop(handle)
    1955              : 
    1956           12 :    END SUBROUTINE collect_auto_ri_columns_for_atom
    1957              : 
    1958              : ! **************************************************************************************************
    1959              : !> \brief Unpacks one AB contraction matrix U_Pp; B=A denotes an AA block.
    1960              : !> \param auto_ri ...
    1961              : !> \param AB_block ...
    1962              : !> \param U_Pp ...
    1963              : ! **************************************************************************************************
    1964           12 :    SUBROUTINE get_U_Pp_AB(auto_ri, AB_block, U_Pp)
    1965              :       TYPE(auto_ri_type), INTENT(IN)                     :: auto_ri
    1966              :       INTEGER, INTENT(IN)                                :: AB_block
    1967              :       REAL(KIND=dp), ALLOCATABLE, INTENT(OUT)            :: U_Pp(:, :)
    1968              : 
    1969              :       INTEGER                                            :: first, last, ncolumn, nrow
    1970              : 
    1971           12 :       nrow = auto_ri%AB_size_ref_RI(AB_block)
    1972           12 :       ncolumn = auto_ri%AB_size_opt_RI(AB_block)
    1973           12 :       first = auto_ri%U_Pp_AB_offset(AB_block)
    1974           12 :       last = first + nrow*ncolumn - 1
    1975           48 :       ALLOCATE (U_Pp(nrow, ncolumn))
    1976           36 :       U_Pp(:, :) = RESHAPE(auto_ri%U_Pp_AB(first:last), [nrow, ncolumn])
    1977              : 
    1978           12 :    END SUBROUTINE get_U_Pp_AB
    1979              : 
    1980              : ! **************************************************************************************************
    1981              : !> \brief Computes the fitting radius for every optimized atomic column block. If q assigned to A
    1982              : !>        contains reference functions on B, the required radius is
    1983              : !>
    1984              : !>          R_A^fit = max_B [r_c + R_B^RI + |R_A - R_B|].
    1985              : !> \param bs_env ...
    1986              : !> \param radius ...
    1987              : ! **************************************************************************************************
    1988            0 :    SUBROUTINE compute_auto_ri_grid_radii(bs_env, radius)
    1989              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1990              :       REAL(KIND=dp), INTENT(OUT)                         :: radius(:)
    1991              : 
    1992              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_auto_ri_grid_radii'
    1993              : 
    1994              :       INTEGER                                            :: a, AB_block, b, handle, n_to_a, ncol
    1995              :       REAL(KIND=dp)                                      :: cutoff, distance
    1996            0 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
    1997              : 
    1998            0 :       CALL timeset(routineN, handle)
    1999              : 
    2000            0 :       particle_set => bs_env%ri_rs%particle_set
    2001            0 :       radius = bs_env%ri_rs%cutoff_radius_ri_rs
    2002            0 :       IF (bs_env%ri_rs%cutoff_radius_ri_rs > 0.0_dp) THEN
    2003            0 :          CALL timestop(handle)
    2004            0 :          RETURN
    2005              :       END IF
    2006            0 :       radius = 0.0_dp
    2007            0 :       cutoff = bs_env%ri_metric%cutoff_radius
    2008            0 :       DO AB_block = 1, bs_env%auto_ri%AB_block_count
    2009            0 :          a = bs_env%auto_ri%AB_atom_A(AB_block)
    2010            0 :          b = bs_env%auto_ri%AB_atom_B(AB_block)
    2011            0 :          n_to_a = bs_env%auto_ri%AB_size_opt_RI_to_A(AB_block)
    2012            0 :          ncol = bs_env%auto_ri%AB_size_opt_RI(AB_block)
    2013            0 :          IF (n_to_a > 0) THEN
    2014            0 :             radius(a) = MAX(radius(a), cutoff + bs_env%ri_rs%radius_ri_per_atom(a))
    2015            0 :             IF (b /= a) THEN
    2016            0 :                distance = NORM2(particle_set(a)%r - particle_set(b)%r)
    2017            0 :                radius(a) = MAX(radius(a), cutoff + bs_env%ri_rs%radius_ri_per_atom(b) + distance)
    2018              :             END IF
    2019              :          END IF
    2020            0 :          IF (n_to_a < ncol) THEN
    2021            0 :             CPASSERT(b /= a)
    2022            0 :             distance = NORM2(particle_set(b)%r - particle_set(a)%r)
    2023              :             radius(b) = MAX(radius(b), cutoff + bs_env%ri_rs%radius_ri_per_atom(b), &
    2024            0 :                             cutoff + bs_env%ri_rs%radius_ri_per_atom(a) + distance)
    2025              :          END IF
    2026              :       END DO
    2027              : 
    2028            0 :       CALL timestop(handle)
    2029            0 :    END SUBROUTINE compute_auto_ri_grid_radii
    2030              : 
    2031              : ! **************************************************************************************************
    2032              : !> \brief Computes
    2033              : !>
    2034              : !>          d_lp = Σ_A Σ_{P∈A} d_lP U_Pp^A,
    2035              : !>          d_lP = Σ_μν ϕ_μ(r_l) ϕ_ν(r_l) (μν|P),
    2036              : !>
    2037              : !>        on the union of the atomic RI-RS grids needed by q. Each atom's three-center integrals are
    2038              : !>        evaluated once and transformed with U_Pp^A.
    2039              : !> \param qs_env ...
    2040              : !> \param bs_env ...
    2041              : !> \param ctx ...
    2042              : !> \param grid ...
    2043              : !> \param mat_phi ...
    2044              : !> \param mat_rhs ...
    2045              : !> \param max_ao_size ...
    2046              : ! **************************************************************************************************
    2047            0 :    SUBROUTINE compute_auto_ri_d_lp(qs_env, bs_env, ctx, grid, mat_phi, mat_rhs, max_ao_size)
    2048              :       TYPE(qs_environment_type), POINTER                 :: qs_env
    2049              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2050              :       TYPE(gw_3c_ctx_type), INTENT(IN)                   :: ctx
    2051              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: grid
    2052              :       TYPE(dbcsr_type), INTENT(IN)                       :: mat_phi
    2053              :       TYPE(dbcsr_type), INTENT(OUT)                      :: mat_rhs
    2054              :       INTEGER, INTENT(IN)                                :: max_ao_size
    2055              : 
    2056              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_auto_ri_d_lp'
    2057              : 
    2058              :       INTEGER :: AB_block, atom_A, atom_B, first, fit_atom, handle, handle_project, handle_rhs, l, &
    2059              :          last, n_done, n_first_p_ABs, n_to_a, n_total, n_union, nao, natom, ncol, ngrid, npcol, &
    2060              :          nprow, ri_atom
    2061            0 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: ao_map, global_map, local_index, &
    2062            0 :                                                             row_offset, union_index
    2063            0 :       INTEGER, DIMENSION(:), POINTER                     :: AB_row_dist, col_dist, &
    2064            0 :                                                             first_p_ABs_per_atom, retained_size, &
    2065            0 :                                                             row_dist, row_size
    2066            0 :       LOGICAL, ALLOCATABLE, DIMENSION(:)                 :: needed_fit_atom, union_mask
    2067              :       REAL(KIND=dp)                                      :: item_start_time, radius
    2068            0 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: fit_radius
    2069            0 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: column_map, phi, rhs, U_Pp, union_grid
    2070              :       TYPE(cell_type), POINTER                           :: cell
    2071              :       TYPE(dbcsr_distribution_type)                      :: dist_AB, dist_phi, dist_t
    2072              :       TYPE(dbcsr_type)                                   :: AB_d_lp, transform
    2073              :       TYPE(mp_para_env_type), POINTER                    :: para_env
    2074            0 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
    2075            0 :       TYPE(qs_kind_type), DIMENSION(:), POINTER          :: qs_kind_set
    2076              : 
    2077            0 :       CALL timeset(routineN, handle)
    2078              :       CALL get_qs_env(qs_env, para_env=para_env, particle_set=particle_set, &
    2079            0 :                       qs_kind_set=qs_kind_set, cell=cell)
    2080            0 :       natom = bs_env%n_atom
    2081            0 :       CALL dbcsr_get_info(mat_phi, row_blk_size=row_size, distribution=dist_phi)
    2082            0 :       CALL dbcsr_distribution_get(dist_phi, row_dist=row_dist, nprows=nprow, npcols=npcol)
    2083              :       ALLOCATE (first_p_ABs_per_atom(natom), col_dist(natom), AB_row_dist(natom), &
    2084            0 :                 retained_size(natom), row_offset(SIZE(row_size)))
    2085            0 :       retained_size(:) = bs_env%auto_ri%sizes_opt_RI
    2086            0 :       DO ri_atom = 1, natom
    2087            0 :          first_p_ABs_per_atom(ri_atom) = 0
    2088            0 :          DO AB_block = 1, bs_env%auto_ri%AB_block_count
    2089            0 :             IF (ri_atom == bs_env%auto_ri%AB_atom_A(AB_block) .OR. &
    2090            0 :                 ri_atom == bs_env%auto_ri%AB_atom_B(AB_block)) THEN
    2091              :                first_p_ABs_per_atom(ri_atom) = &
    2092              :                   first_p_ABs_per_atom(ri_atom) + &
    2093            0 :                   bs_env%auto_ri%AB_size_opt_RI(AB_block)
    2094              :             END IF
    2095              :          END DO
    2096            0 :          col_dist(ri_atom) = MOD(ri_atom - 1, npcol)
    2097            0 :          AB_row_dist(ri_atom) = MOD(ri_atom - 1, nprow)
    2098              :       END DO
    2099            0 :       row_offset(1) = 0
    2100            0 :       DO l = 2, SIZE(row_size)
    2101            0 :          row_offset(l) = row_offset(l - 1) + row_size(l - 1)
    2102              :       END DO
    2103              :       CALL dbcsr_distribution_new(dist_AB, template=dist_phi, &
    2104            0 :                                   row_dist=row_dist, col_dist=col_dist)
    2105              :       CALL dbcsr_create(AB_d_lp, name='AUTO_RI AA/AB d_lp', dist=dist_AB, &
    2106              :                         matrix_type=dbcsr_type_no_symmetry, row_blk_size=row_size, &
    2107            0 :                         col_blk_size=first_p_ABs_per_atom)
    2108              :       CALL dbcsr_distribution_new(dist_t, template=dist_phi, &
    2109            0 :                                   row_dist=AB_row_dist, col_dist=col_dist)
    2110              :       CALL dbcsr_create(transform, name='AUTO_RI U_Pp', dist=dist_t, &
    2111              :                         matrix_type=dbcsr_type_no_symmetry, row_blk_size=first_p_ABs_per_atom, &
    2112            0 :                         col_blk_size=retained_size)
    2113              :       CALL dbcsr_create(mat_rhs, name='AUTO_RI optimized d_lp', dist=dist_AB, &
    2114              :                         matrix_type=dbcsr_type_no_symmetry, row_blk_size=row_size, &
    2115            0 :                         col_blk_size=retained_size)
    2116            0 :       ALLOCATE (fit_radius(natom))
    2117            0 :       CALL compute_auto_ri_grid_radii(bs_env, fit_radius)
    2118            0 :       ALLOCATE (needed_fit_atom(natom), union_mask(SIZE(grid, 2)))
    2119            0 :       n_total = 0
    2120            0 :       DO ri_atom = para_env%mepos + 1, natom, para_env%num_pe
    2121            0 :          n_total = n_total + 1
    2122              :       END DO
    2123            0 :       n_done = 0
    2124            0 :       CALL timeset(routineN//'_AB_d_lp', handle_rhs)
    2125            0 :       DO ri_atom = para_env%mepos + 1, natom, para_env%num_pe
    2126            0 :          item_start_time = m_walltime()
    2127            0 :          needed_fit_atom = .FALSE.
    2128            0 :          DO AB_block = 1, bs_env%auto_ri%AB_block_count
    2129            0 :             atom_A = bs_env%auto_ri%AB_atom_A(AB_block)
    2130            0 :             atom_B = bs_env%auto_ri%AB_atom_B(AB_block)
    2131            0 :             IF (ri_atom /= atom_A .AND. ri_atom /= atom_B) CYCLE
    2132            0 :             n_to_a = bs_env%auto_ri%AB_size_opt_RI_to_A(AB_block)
    2133            0 :             IF (n_to_a > 0) needed_fit_atom(atom_A) = .TRUE.
    2134            0 :             IF (n_to_a < bs_env%auto_ri%AB_size_opt_RI(AB_block)) THEN
    2135            0 :                needed_fit_atom(atom_B) = .TRUE.
    2136              :             END IF
    2137              :          END DO
    2138            0 :          IF (.NOT. ANY(needed_fit_atom)) THEN
    2139            0 :             n_done = n_done + 1
    2140              :             CALL print_Z_lP_progress(bs_env, n_done, n_total, &
    2141            0 :                                      m_walltime() - item_start_time)
    2142            0 :             CYCLE
    2143              :          END IF
    2144            0 :          union_mask = .FALSE.
    2145            0 :          radius = 0.0_dp
    2146            0 :          DO fit_atom = 1, natom
    2147            0 :             IF (.NOT. needed_fit_atom(fit_atom)) CYCLE
    2148              :             radius = MAX(radius, fit_radius(fit_atom) + &
    2149            0 :                          NORM2(particle_set(fit_atom)%r - particle_set(ri_atom)%r))
    2150            0 :             DO l = 1, SIZE(grid, 2)
    2151            0 :                IF (NORM2(grid(:, l) - particle_set(fit_atom)%r) <= &
    2152            0 :                    fit_radius(fit_atom)) union_mask(l) = .TRUE.
    2153              :             END DO
    2154              :          END DO
    2155            0 :          n_union = COUNT(union_mask)
    2156            0 :          ALLOCATE (union_index(n_union), union_grid(3, n_union))
    2157            0 :          n_union = 0
    2158            0 :          DO l = 1, SIZE(grid, 2)
    2159            0 :             IF (.NOT. union_mask(l)) CYCLE
    2160            0 :             n_union = n_union + 1
    2161            0 :             union_index(n_union) = l
    2162            0 :             union_grid(:, n_union) = grid(:, l)
    2163              :          END DO
    2164              :          CALL build_phi_on_sphere(bs_env, qs_kind_set, union_grid, ri_atom, &
    2165              :                                   radius + 1.0_dp, bs_env%i_ao_end_from_atom(natom), local_index, &
    2166            0 :                                   ngrid, phi, ao_map, nao)
    2167            0 :          local_index(1:ngrid) = union_index(local_index(1:ngrid))
    2168            0 :          n_first_p_ABs = first_p_ABs_per_atom(ri_atom)
    2169            0 :          CALL collect_auto_ri_columns_for_atom(bs_env, ri_atom, U_Pp, global_map)
    2170            0 :          CPASSERT(SIZE(global_map) == n_first_p_ABs)
    2171            0 :          first = 1
    2172            0 :          DO fit_atom = 1, natom
    2173            0 :             ncol = retained_size(fit_atom)
    2174            0 :             last = first + ncol - 1
    2175            0 :             IF (ANY(global_map >= first .AND. global_map <= last)) THEN
    2176            0 :                ALLOCATE (column_map(n_first_p_ABs, ncol), source=0.0_dp)
    2177            0 :                DO l = 1, n_first_p_ABs
    2178            0 :                   IF (global_map(l) < first .OR. global_map(l) > last) CYCLE
    2179            0 :                   column_map(l, global_map(l) - first + 1) = 1.0_dp
    2180              :                END DO
    2181            0 :                CALL dbcsr_put_block(transform, ri_atom, fit_atom, column_map)
    2182            0 :                DEALLOCATE (column_map)
    2183              :             END IF
    2184            0 :             first = last + 1
    2185              :          END DO
    2186            0 :          ALLOCATE (rhs(ngrid, n_first_p_ABs), source=0.0_dp)
    2187              :          CALL compute_d_lp_auto_ri_atom(bs_env, ctx, phi, ao_map, rhs, ngrid, &
    2188            0 :                                         ri_atom, U_Pp, max_ao_size)
    2189              :          CALL store_Z_lP_columns(AB_d_lp, rhs, local_index, ngrid, n_first_p_ABs, ri_atom, &
    2190            0 :                                  row_size, row_offset, 0.0_dp)
    2191            0 :          DEALLOCATE (rhs, U_Pp, global_map, phi, ao_map, local_index, &
    2192            0 :                      union_grid, union_index)
    2193            0 :          n_done = n_done + 1
    2194              :          CALL print_Z_lP_progress(bs_env, n_done, n_total, &
    2195            0 :                                   m_walltime() - item_start_time)
    2196              :       END DO
    2197            0 :       CALL dbcsr_finalize(transform)
    2198            0 :       CALL dbcsr_finalize(AB_d_lp)
    2199            0 :       CALL timestop(handle_rhs)
    2200            0 :       CALL timeset(routineN//'_transform', handle_project)
    2201              :       CALL dbcsr_multiply('N', 'N', 1.0_dp, AB_d_lp, transform, 0.0_dp, &
    2202            0 :                           mat_rhs, filter_eps=0.0_dp)
    2203            0 :       CALL timestop(handle_project)
    2204            0 :       CALL dbcsr_release(AB_d_lp)
    2205            0 :       CALL dbcsr_release(transform)
    2206            0 :       CALL dbcsr_distribution_release(dist_AB)
    2207            0 :       CALL dbcsr_distribution_release(dist_t)
    2208            0 :       DEALLOCATE (first_p_ABs_per_atom, retained_size, col_dist, AB_row_dist, row_offset, &
    2209            0 :                   fit_radius, needed_fit_atom, union_mask)
    2210            0 :       CALL timestop(handle)
    2211            0 :    END SUBROUTINE compute_auto_ri_d_lp
    2212              : 
    2213              : ! **************************************************************************************************
    2214              : !> \brief Extracts rhs(i,q) = d_{local_index(i),q} from one optimized atomic DBCSR column block.
    2215              : !> \param mat_rhs ...
    2216              : !> \param atom_index ...
    2217              : !> \param local_index ...
    2218              : !> \param row_offset ...
    2219              : !> \param rhs ...
    2220              : ! **************************************************************************************************
    2221            0 :    SUBROUTINE extract_atom_d_lp(mat_rhs, atom_index, local_index, row_offset, rhs)
    2222              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_rhs
    2223              :       INTEGER, INTENT(IN)                                :: atom_index
    2224              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: local_index, row_offset
    2225              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(OUT)        :: rhs
    2226              : 
    2227              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'extract_atom_d_lp'
    2228              : 
    2229              :       INTEGER                                            :: handle, l, next_row, row
    2230              :       LOGICAL                                            :: found
    2231            0 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: block
    2232              : 
    2233            0 :       CALL timeset(routineN, handle)
    2234              : 
    2235            0 :       rhs = 0.0_dp
    2236            0 :       row = 0
    2237            0 :       next_row = 1
    2238            0 :       NULLIFY (block)
    2239            0 :       DO l = 1, SIZE(rhs, 1)
    2240            0 :          DO WHILE (next_row <= SIZE(row_offset))
    2241            0 :             IF (row_offset(next_row) >= local_index(l)) EXIT
    2242            0 :             row = next_row
    2243            0 :             next_row = next_row + 1
    2244            0 :             CALL dbcsr_get_block_p(mat_rhs, row, atom_index, block, found)
    2245            0 :             IF (.NOT. found) NULLIFY (block)
    2246              :          END DO
    2247            0 :          IF (ASSOCIATED(block)) rhs(l, :) = block(local_index(l) - row_offset(row), :)
    2248              :       END DO
    2249              : 
    2250            0 :       CALL timestop(handle)
    2251            0 :    END SUBROUTINE extract_atom_d_lp
    2252              : 
    2253              : ! **************************************************************************************************
    2254              : !> \brief Solves Σ_l' D_ll' Z_l'q = d_lp for each optimized atomic column block. Small grids are
    2255              : !>        gathered within one process column and solved by a local Cholesky factorization.
    2256              : !> \param qs_env ...
    2257              : !> \param bs_env ...
    2258              : !> \param grid ...
    2259              : !> \param mat_rhs ...
    2260              : !> \param mat_z ...
    2261              : ! **************************************************************************************************
    2262            0 :    SUBROUTINE fit_auto_ri_z_lp(qs_env, bs_env, grid, mat_rhs, mat_z)
    2263              :       TYPE(qs_environment_type), POINTER                 :: qs_env
    2264              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2265              :       REAL(KIND=dp), INTENT(IN)                          :: grid(:, :)
    2266              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_rhs, mat_z
    2267              : 
    2268              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'fit_auto_ri_z_lp'
    2269              : 
    2270              :       INTEGER                                            :: atom_index, base, group_size, handle, &
    2271              :                                                             handle_gather, i, info, mypcol, n_big, &
    2272              :                                                             n_small, nao, natom, ncol, ngrid, &
    2273              :                                                             npcol, slot
    2274            0 :       INTEGER, ALLOCATABLE                               :: all_index(:), ao_map(:), big_list(:), &
    2275            0 :                                                             grid_size(:), local_index(:), &
    2276            0 :                                                             row_offset(:), small_list(:)
    2277            0 :       INTEGER, POINTER                                   :: row_size(:)
    2278            0 :       LOGICAL, ALLOCATABLE                               :: single_rank(:)
    2279            0 :       REAL(KIND=dp), ALLOCATABLE                         :: atom_rhs(:, :), buffer(:, :), &
    2280            0 :                                                             diagonal(:), gram(:, :), &
    2281            0 :                                                             local_rhs(:, :), phi(:, :), radius(:)
    2282              :       TYPE(dbcsr_distribution_type)                      :: distribution
    2283              :       TYPE(mp_para_env_type), POINTER                    :: column_env, para_env
    2284            0 :       TYPE(qs_kind_type), POINTER                        :: qs_kinds(:)
    2285              : 
    2286            0 :       CALL timeset(routineN, handle)
    2287            0 :       CALL get_qs_env(qs_env, para_env=para_env, qs_kind_set=qs_kinds)
    2288            0 :       CALL dbcsr_get_info(mat_rhs, distribution=distribution, row_blk_size=row_size)
    2289            0 :       CALL dbcsr_distribution_get(distribution, mypcol=mypcol, npcols=npcol)
    2290            0 :       ALLOCATE (column_env)
    2291            0 :       CALL column_env%from_split(para_env, mypcol)
    2292            0 :       natom = bs_env%n_atom
    2293            0 :       ALLOCATE (radius(natom), all_index(SIZE(grid, 2)), row_offset(SIZE(row_size)))
    2294            0 :       CALL compute_auto_ri_grid_radii(bs_env, radius)
    2295              :       CALL classify_z_lp_atoms(bs_env, grid, radius, &
    2296              :                                bs_env%auto_ri%sizes_opt_RI, grid_size, &
    2297            0 :                                small_list, n_small, big_list, n_big, group_size)
    2298            0 :       ALLOCATE (single_rank(natom), source=.FALSE.)
    2299            0 :       single_rank(small_list(:n_small)) = .TRUE.
    2300            0 :       DO i = 1, SIZE(all_index)
    2301            0 :          all_index(i) = i
    2302              :       END DO
    2303            0 :       row_offset(1) = 0
    2304            0 :       DO i = 2, SIZE(row_size)
    2305            0 :          row_offset(i) = row_offset(i - 1) + row_size(i - 1)
    2306              :       END DO
    2307            0 :       DO base = mypcol + 1, natom, npcol*column_env%num_pe
    2308            0 :          CALL timeset(routineN//'_gather_rhs', handle_gather)
    2309            0 :          DO slot = 0, column_env%num_pe - 1
    2310            0 :             atom_index = base + slot*npcol
    2311            0 :             IF (atom_index > natom) EXIT
    2312            0 :             IF (.NOT. single_rank(atom_index)) CYCLE
    2313            0 :             ncol = bs_env%auto_ri%sizes_opt_RI(atom_index)
    2314            0 :             IF (ncol == 0) CYCLE
    2315            0 :             ALLOCATE (buffer(SIZE(grid, 2), ncol))
    2316            0 :             CALL extract_atom_d_lp(mat_rhs, atom_index, all_index, row_offset, buffer)
    2317            0 :             CALL column_env%sum(buffer, slot)
    2318            0 :             IF (column_env%mepos == slot) THEN
    2319            0 :                CALL MOVE_ALLOC(buffer, atom_rhs)
    2320              :             ELSE
    2321            0 :                DEALLOCATE (buffer)
    2322              :             END IF
    2323              :          END DO
    2324            0 :          CALL timestop(handle_gather)
    2325            0 :          atom_index = base + column_env%mepos*npcol
    2326            0 :          IF (atom_index > natom) CYCLE
    2327            0 :          IF (.NOT. single_rank(atom_index)) CYCLE
    2328            0 :          ncol = bs_env%auto_ri%sizes_opt_RI(atom_index)
    2329            0 :          IF (ncol == 0) CYCLE
    2330              :          CALL build_phi_on_sphere(bs_env, qs_kinds, grid, atom_index, &
    2331              :                                   radius(atom_index), bs_env%i_ao_end_from_atom(natom), &
    2332            0 :                                   local_index, ngrid, phi, ao_map, nao)
    2333            0 :          ALLOCATE (local_rhs(ngrid, ncol), diagonal(ngrid))
    2334            0 :          DO i = 1, ngrid
    2335            0 :             local_rhs(i, :) = atom_rhs(local_index(i), :)
    2336              :          END DO
    2337            0 :          DEALLOCATE (atom_rhs)
    2338            0 :          CALL build_gram_jacobi_blas(phi, ngrid, nao, bs_env%ri_rs%tikhonov, gram, diagonal)
    2339            0 :          CALL scale_rows_by_diag(local_rhs, diagonal, ngrid, ncol)
    2340            0 :          CALL dpotrf('L', ngrid, gram, ngrid, info)
    2341            0 :          CPASSERT(info == 0)
    2342            0 :          CALL dpotrs('L', ngrid, ncol, gram, ngrid, local_rhs, ngrid, info)
    2343            0 :          CPASSERT(info == 0)
    2344            0 :          CALL scale_rows_by_diag(local_rhs, diagonal, ngrid, ncol)
    2345              :          CALL store_Z_lP_columns(mat_z, local_rhs, local_index, ngrid, ncol, atom_index, &
    2346            0 :                                  row_size, row_offset, 0.0_dp)
    2347            0 :          DEALLOCATE (local_rhs, diagonal, gram, phi, ao_map, local_index)
    2348              :       END DO
    2349            0 :       IF (n_big > 0) THEN
    2350              :          CALL fit_distributed_auto_ri_z_lp(qs_env, bs_env, grid, mat_rhs, mat_z, radius, &
    2351              :                                            big_list(:n_big), group_size, row_size, row_offset, &
    2352            0 :                                            all_index)
    2353              :       END IF
    2354            0 :       DEALLOCATE (radius, all_index, row_offset, single_rank, grid_size, small_list, big_list)
    2355            0 :       CALL column_env%free()
    2356            0 :       DEALLOCATE (column_env)
    2357            0 :       CALL dbcsr_finalize(mat_z)
    2358            0 :       CALL timestop(handle)
    2359            0 :    END SUBROUTINE fit_auto_ri_z_lp
    2360              : 
    2361              : ! **************************************************************************************************
    2362              : !> \brief Solves Σ_l' D_ll' Z_l'q = d_lp for large atomic grids with distributed Cholesky
    2363              : !>        factorization. Each d_lp block is gathered once and distributed over its assigned rank
    2364              : !>        group.
    2365              : !> \param qs_env ...
    2366              : !> \param bs_env ...
    2367              : !> \param grid ...
    2368              : !> \param mat_rhs ...
    2369              : !> \param mat_z ...
    2370              : !> \param radius ...
    2371              : !> \param atom_list ...
    2372              : !> \param group_size ...
    2373              : !> \param row_size ...
    2374              : !> \param row_offset ...
    2375              : !> \param all_index ...
    2376              : ! **************************************************************************************************
    2377            0 :    SUBROUTINE fit_distributed_auto_ri_z_lp(qs_env, bs_env, grid, mat_rhs, mat_z, radius, &
    2378            0 :                                            atom_list, group_size, row_size, row_offset, &
    2379            0 :                                            all_index)
    2380              :       TYPE(qs_environment_type), POINTER                 :: qs_env
    2381              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2382              :       REAL(KIND=dp), INTENT(IN)                          :: grid(:, :)
    2383              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_rhs, mat_z
    2384              :       REAL(KIND=dp), INTENT(IN)                          :: radius(:)
    2385              :       INTEGER, INTENT(IN)                                :: atom_list(:), group_size, row_size(:), &
    2386              :                                                             row_offset(:), all_index(:)
    2387              : 
    2388              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'fit_distributed_auto_ri_z_lp'
    2389              : 
    2390              :       INTEGER                                            :: atom_index, base, handle, i, info, &
    2391              :                                                             my_group, nao, ncol, ngrid, ngroups, &
    2392              :                                                             root, slot
    2393            0 :       INTEGER, ALLOCATABLE                               :: ao_map(:), local_index(:)
    2394            0 :       REAL(KIND=dp), ALLOCATABLE                         :: atom_rhs(:, :), buffer(:, :), &
    2395            0 :                                                             diagonal(:), local_rhs(:, :), phi(:, :)
    2396              :       TYPE(cp_blacs_env_type), POINTER                   :: blacs_env
    2397              :       TYPE(cp_fm_struct_type), POINTER                   :: gram_struct, rhs_struct
    2398              :       TYPE(cp_fm_type)                                   :: gram, rhs
    2399              :       TYPE(mp_para_env_type), POINTER                    :: group_env, para_env
    2400            0 :       TYPE(qs_kind_type), POINTER                        :: qs_kinds(:)
    2401              : 
    2402            0 :       CALL timeset(routineN, handle)
    2403            0 :       CALL get_qs_env(qs_env, para_env=para_env, qs_kind_set=qs_kinds)
    2404            0 :       ngroups = MAX(1, para_env%num_pe/group_size)
    2405            0 :       my_group = MIN(para_env%mepos/group_size, ngroups - 1)
    2406            0 :       ALLOCATE (group_env)
    2407            0 :       CALL group_env%from_split(para_env, my_group)
    2408            0 :       NULLIFY (blacs_env, gram_struct, rhs_struct)
    2409            0 :       CALL cp_blacs_env_create(blacs_env=blacs_env, para_env=group_env)
    2410            0 :       DO base = 1, SIZE(atom_list), ngroups
    2411            0 :          DO slot = 0, ngroups - 1
    2412            0 :             IF (base + slot > SIZE(atom_list)) EXIT
    2413            0 :             atom_index = atom_list(base + slot)
    2414            0 :             ncol = bs_env%auto_ri%sizes_opt_RI(atom_index)
    2415            0 :             IF (ncol == 0) CYCLE
    2416            0 :             root = slot*group_size
    2417            0 :             ALLOCATE (buffer(SIZE(grid, 2), ncol))
    2418            0 :             CALL extract_atom_d_lp(mat_rhs, atom_index, all_index, row_offset, buffer)
    2419            0 :             CALL para_env%sum(buffer, root)
    2420            0 :             IF (para_env%mepos == root) THEN
    2421            0 :                CALL MOVE_ALLOC(buffer, atom_rhs)
    2422              :             ELSE
    2423            0 :                DEALLOCATE (buffer)
    2424              :             END IF
    2425              :          END DO
    2426            0 :          IF (base + my_group > SIZE(atom_list)) CYCLE
    2427            0 :          atom_index = atom_list(base + my_group)
    2428            0 :          ncol = bs_env%auto_ri%sizes_opt_RI(atom_index)
    2429            0 :          IF (ncol == 0) CYCLE
    2430            0 :          IF (group_env%mepos /= 0) ALLOCATE (atom_rhs(SIZE(grid, 2), ncol))
    2431            0 :          CALL group_env%bcast(atom_rhs, 0)
    2432              :          CALL build_phi_on_sphere(bs_env, qs_kinds, grid, atom_index, &
    2433              :                                   radius(atom_index), bs_env%i_ao_end_from_atom(bs_env%n_atom), &
    2434            0 :                                   local_index, ngrid, phi, ao_map, nao)
    2435            0 :          ALLOCATE (local_rhs(ngrid, ncol), diagonal(ngrid))
    2436            0 :          DO i = 1, ngrid
    2437            0 :             local_rhs(i, :) = atom_rhs(local_index(i), :)
    2438              :          END DO
    2439            0 :          DEALLOCATE (atom_rhs)
    2440            0 :          CALL build_jacobi_diag_from_phi(phi, ngrid, nao, diagonal)
    2441            0 :          CALL scale_rows_by_diag(local_rhs, diagonal, ngrid, ncol)
    2442              :          CALL solve_D_lp_distributed(phi, diagonal, local_rhs, ngrid, nao, ncol, &
    2443              :                                      bs_env%ri_rs%tikhonov, group_env, blacs_env, &
    2444            0 :                                      gram_struct, rhs_struct, gram, rhs, info)
    2445            0 :          CPASSERT(info == 0)
    2446            0 :          CALL scale_rows_by_diag(local_rhs, diagonal, ngrid, ncol)
    2447            0 :          IF (group_env%mepos == 0) THEN
    2448              :             CALL store_Z_lP_columns(mat_z, local_rhs, local_index, ngrid, ncol, atom_index, &
    2449            0 :                                     row_size, row_offset, 0.0_dp)
    2450              :          END IF
    2451            0 :          DEALLOCATE (local_rhs, diagonal, phi, ao_map, local_index)
    2452              :       END DO
    2453            0 :       CALL cp_blacs_env_release(blacs_env)
    2454            0 :       CALL group_env%free()
    2455            0 :       DEALLOCATE (group_env)
    2456            0 :       CALL timestop(handle)
    2457            0 :    END SUBROUTINE fit_distributed_auto_ri_z_lp
    2458              : 
    2459              : ! **************************************************************************************************
    2460              : !> \brief Splits the atoms of the Z_lP solve into a single-rank list ("small", Phase A: LAPACK
    2461              : !>        dpotrf/dpotrs on one rank) and a distributed list ("big", Phase B: ScaLAPACK
    2462              : !>        pdpotrf/pdpotrs over a rank subgroup of size G), and sizes G.
    2463              : !>        AUTO mode (N_PROCS_PER_ATOM_Z_LP <= 0, the default): estimate each atom's single-rank
    2464              : !>        peak memory
    2465              : !>          peak(P) = 8*n_local_grid(P)^2                (dense matrix D'_ll', stored in D_local)
    2466              : !>                  + 8*n_local_grid(P)*n_ao_used(P)     (phi_local)
    2467              : !>                  + 8*n_local_grid(P)*n_RI(P)*(1+n_threads)   (d_lp + OMP partials)
    2468              : !>        and send atoms whose peak exceeds mem_safety * available-memory-per-proc to the
    2469              : !>        distributed path; G is auto-sized so the biggest atom's distributed D_local (/G)
    2470              : !>        fits alongside the replicated phi_local + d_lp.
    2471              : !>        MANUAL mode (> 0): 1 forces the single-rank path for every atom; > 1 keeps the
    2472              : !>        memory-based classification but forces that fixed subgroup size G.
    2473              : !>        In every mode G is floored by the ScaLAPACK 32-bit index limit (a local block-cyclic
    2474              : !>        slice of ~n_local_grid^2/G elements must stay below 2^31 or pdpotrf segfaults).
    2475              : !> \param bs_env ...
    2476              : !> \param ri_rs_grid_points ...
    2477              : !> \param cutoff_ri_per_atom ...
    2478              : !> \param ri_blk_sizes per-atom ...
    2479              : !> \param n_local_grid_atom ...
    2480              : !> \param small_list ...
    2481              : !> \param n_small ...
    2482              : !> \param big_list ...
    2483              : !> \param n_big ...
    2484              : !> \param G ...
    2485              : ! **************************************************************************************************
    2486           42 :    SUBROUTINE classify_z_lp_atoms(bs_env, ri_rs_grid_points, cutoff_ri_per_atom, ri_blk_sizes, &
    2487              :                                   n_local_grid_atom, small_list, n_small, big_list, n_big, G)
    2488              : 
    2489              : !$    USE OMP_LIB, ONLY: omp_get_max_threads
    2490              : 
    2491              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2492              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: ri_rs_grid_points
    2493              : 
    2494              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: cutoff_ri_per_atom
    2495              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: ri_blk_sizes
    2496              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: n_local_grid_atom, small_list, big_list
    2497              :       INTEGER, INTENT(OUT)                               :: n_small, n_big, G
    2498              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'classify_z_lp_atoms'
    2499              : 
    2500              :       INTEGER                                            :: handle
    2501              : 
    2502              :       ! Conservative fraction of measured available memory usable per rank for the Z_lP
    2503              :       REAL(KIND=dp), PARAMETER                           :: mem_safety = 0.8_dp
    2504              : 
    2505              :       ! ScaLAPACK/BLACS index the per-rank local block-cyclic slice (~n_local_grid^2/G
    2506              :       ! elements) with 32-bit integers; keep it safely below 2^31 or pdpotrf segfaults.
    2507              :       REAL(KIND=dp), PARAMETER                           :: scalapack_loc_limit = 2.0E9_dp
    2508              : 
    2509              :       INTEGER                                            :: G_atom, G_int32, G_int32_max, l, &
    2510              :                                                             n_ao_used_atom, n_grid_total, &
    2511              :                                                             n_local_grid, natom, nthreads_cls, &
    2512              :                                                             P_loop_atom
    2513              :       LOGICAL                                            :: auto_mode
    2514              :       REAL(KIND=dp)                                      :: budget_bytes, cutoff_ri, dlp_bytes, &
    2515              :                                                             mem_avail_GB, ng, nri, peak_bytes, &
    2516              :                                                             phi_bytes
    2517              :       REAL(KIND=dp), DIMENSION(3)                        :: pos_P
    2518              :       TYPE(mp_para_env_type), POINTER                    :: para_env
    2519           42 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
    2520              : 
    2521           42 :       CALL timeset(routineN, handle)
    2522              : 
    2523           42 :       para_env => bs_env%para_env
    2524           42 :       particle_set => bs_env%ri_rs%particle_set
    2525           42 :       natom = bs_env%n_atom
    2526           42 :       n_grid_total = bs_env%ri_rs%n_grid_points
    2527           42 :       CPASSERT(SIZE(ri_rs_grid_points, 2) == n_grid_total)
    2528              : 
    2529              :       ! Per-atom sphere size: n_local_grid(P) = number of l with |r_l - R_P| <= cutoff_ri(P).
    2530              :       ! It sets both the memory footprint (D_local is n_local_grid^2) and the solve
    2531              :       ! cost (~n_local_grid^3), so it drives classification and the LPT load balancing.
    2532          210 :       ALLOCATE (n_local_grid_atom(natom), small_list(natom), big_list(natom))
    2533          142 :       DO P_loop_atom = 1, natom
    2534          400 :          pos_P(:) = particle_set(P_loop_atom)%r(:)
    2535          100 :          cutoff_ri = cutoff_ri_per_atom(P_loop_atom)
    2536          100 :          n_local_grid = 0
    2537        38088 :          DO l = 1, n_grid_total
    2538       152052 :             IF (SUM((ri_rs_grid_points(1:3, l) - pos_P(1:3))**2) <= cutoff_ri**2) THEN
    2539        36464 :                n_local_grid = n_local_grid + 1
    2540              :             END IF
    2541              :          END DO
    2542          142 :          n_local_grid_atom(P_loop_atom) = n_local_grid
    2543              :       END DO
    2544              : 
    2545           42 :       nthreads_cls = 1
    2546           42 : !$    nthreads_cls = omp_get_max_threads()
    2547              :       ! N_PROCS_PER_ATOM_Z_LP: -1 (default) = AUTO (classify by memory, auto-size G);
    2548              :       ! 1 = force single-rank BLAS for every atom; >1 = classify by memory but use this
    2549              :       ! fixed subgroup size G for the big atoms.
    2550           42 :       auto_mode = (bs_env%ri_rs%n_procs_per_atom_z_lp <= 0)
    2551           42 :       CALL mp_mem_avail_per_rank_GB(bs_env%para_env, mem_avail_GB)  ! collective over all ranks
    2552           42 :       budget_bytes = mem_safety*mem_avail_GB*1.0E9_dp
    2553              : 
    2554           42 :       n_small = 0
    2555           42 :       n_big = 0
    2556           42 :       G = 1
    2557           42 :       G_atom = 1              ! max G a big atom needs (memory + ScaLAPACK int32 floor)
    2558           42 :       G_int32_max = 1         ! max ScaLAPACK-int32 floor over the distributed atoms
    2559           42 :       IF (bs_env%ri_rs%n_procs_per_atom_z_lp == 1) THEN
    2560              :          ! Force single-rank BLAS for every atom.
    2561            0 :          DO P_loop_atom = 1, natom
    2562            0 :             n_small = n_small + 1
    2563            0 :             small_list(n_small) = P_loop_atom
    2564              :          END DO
    2565           42 :       ELSE IF (mem_avail_GB <= 0.0_dp) THEN
    2566              :          ! No /proc/meminfo => cannot size by memory.
    2567            0 :          IF (auto_mode) THEN
    2568            0 :             IF (bs_env%unit_nr > 0) THEN
    2569            0 :                CPWARN("RI-RS Z_lP: no meminfo; single-rank solve for all atoms")
    2570              :             END IF
    2571            0 :             DO P_loop_atom = 1, natom
    2572            0 :                n_small = n_small + 1
    2573            0 :                small_list(n_small) = P_loop_atom
    2574              :             END DO
    2575              :          ELSE
    2576              :             ! Fixed G, no meminfo: distribute all atoms; still floor G by the int32 limit.
    2577            0 :             DO P_loop_atom = 1, natom
    2578            0 :                ng = REAL(n_local_grid_atom(P_loop_atom), dp)
    2579            0 :                G_int32_max = MAX(G_int32_max, CEILING(ng*ng/scalapack_loc_limit))
    2580            0 :                n_big = n_big + 1
    2581            0 :                big_list(n_big) = P_loop_atom
    2582              :             END DO
    2583            0 :             G = MIN(bs_env%ri_rs%n_procs_per_atom_z_lp, para_env%num_pe)
    2584            0 :             IF (G < G_int32_max) THEN
    2585            0 :                G = MIN(G_int32_max, para_env%num_pe)
    2586            0 :                IF (bs_env%unit_nr > 0) THEN
    2587            0 :                   CPWARN("RI-RS Z_lP: raised G to avoid ScaLAPACK overflow")
    2588              :                END IF
    2589              :             END IF
    2590              :          END IF
    2591              :       ELSE
    2592              :          ! Classify by memory: peak (D_local + phi_local + d_lp) vs budget. Small -> BLAS,
    2593              :          ! big -> distributed. Same classification for AUTO and fixed-G modes.
    2594          142 :          DO P_loop_atom = 1, natom
    2595          100 :             ng = REAL(n_local_grid_atom(P_loop_atom), dp)
    2596          100 :             nri = REAL(ri_blk_sizes(P_loop_atom), dp)
    2597              :             CALL get_n_ao_in_sphere(bs_env, P_loop_atom, &
    2598          100 :                                     cutoff_ri_per_atom(P_loop_atom), n_ao_used_atom)
    2599          100 :             phi_bytes = 8.0_dp*ng*REAL(n_ao_used_atom, dp)
    2600          100 :             dlp_bytes = 8.0_dp*ng*nri*REAL(1 + nthreads_cls, dp)
    2601          100 :             peak_bytes = 8.0_dp*ng*ng + phi_bytes + dlp_bytes
    2602          142 :             IF (peak_bytes <= budget_bytes) THEN
    2603          100 :                n_small = n_small + 1
    2604          100 :                small_list(n_small) = P_loop_atom
    2605              :             ELSE
    2606            0 :                n_big = n_big + 1
    2607            0 :                big_list(n_big) = P_loop_atom
    2608              :                ! G must satisfy BOTH: (a) memory — distributed D_local (/G) fits next to the
    2609              :                ! replicated phi_local + d_lp; (b) ScaLAPACK — local ~ng^2/G below the int32 limit.
    2610            0 :                G_int32 = CEILING(ng*ng/scalapack_loc_limit)
    2611            0 :                G_int32_max = MAX(G_int32_max, G_int32)
    2612              :                G_atom = MAX(G_atom, G_int32, &
    2613            0 :                             CEILING(8.0_dp*ng*ng/MAX(budget_bytes - phi_bytes - dlp_bytes, 1.0_dp)))
    2614              :             END IF
    2615              :          END DO
    2616           42 :          IF (n_big > 0) THEN
    2617            0 :             IF (auto_mode) THEN
    2618              :                ! Auto-size G from the most demanding big atom.
    2619            0 :                IF (G_atom > para_env%num_pe) THEN
    2620              :                   CALL cp_abort(__LOCATION__, &
    2621              :                                 "RI-RS Z_lP: an atom is too large to fit even when "// &
    2622              :                                 "distributed over all ranks. Add nodes, use fewer MPI ranks "// &
    2623              :                                 "per node, lower CUTOFF_RADIUS_RL_RI, or raise EPS_FILTER "// &
    2624            0 :                                 "for more grid screening.")
    2625              :                END IF
    2626            0 :                G = MIN(MAX(G_atom, 2), para_env%num_pe)
    2627              :             ELSE
    2628              :                ! Fixed G from the keyword. Hard-floor by the ScaLAPACK int32 limit (below it
    2629              :                ! pdpotrf segfaults); warn if it is still below the memory recommendation.
    2630            0 :                G = MIN(bs_env%ri_rs%n_procs_per_atom_z_lp, para_env%num_pe)
    2631            0 :                IF (G < G_int32_max) THEN
    2632            0 :                   G = MIN(G_int32_max, para_env%num_pe)
    2633            0 :                   IF (bs_env%unit_nr > 0) THEN
    2634            0 :                      CPWARN("RI-RS Z_lP: raised G to avoid ScaLAPACK overflow")
    2635              :                   END IF
    2636            0 :                ELSE IF (G < G_atom .AND. bs_env%unit_nr > 0) THEN
    2637            0 :                   CPWARN("RI-RS Z_lP: N_PROCS_PER_ATOM_Z_LP too small for the largest atom")
    2638              :                END IF
    2639              :             END IF
    2640              :          END IF
    2641              :       END IF
    2642              : 
    2643           42 :       CALL timestop(handle)
    2644              : 
    2645           42 :    END SUBROUTINE classify_z_lp_atoms
    2646              : 
    2647              : ! **************************************************************************************************
    2648              : !> \brief Number of AO basis functions that can be non-zero inside the RI-RS integration sphere
    2649              : !> \param bs_env ...
    2650              : !> \param atom_P ...
    2651              : !> \param cutoff_ri ...
    2652              : !> \param n_ao_used ...
    2653              : ! **************************************************************************************************
    2654          100 :    SUBROUTINE get_n_ao_in_sphere(bs_env, atom_P, cutoff_ri, n_ao_used)
    2655              : 
    2656              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2657              :       INTEGER, INTENT(IN)                                :: atom_P
    2658              :       REAL(KIND=dp), INTENT(IN)                          :: cutoff_ri
    2659              :       INTEGER, INTENT(OUT)                               :: n_ao_used
    2660              : 
    2661              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'get_n_ao_in_sphere'
    2662              : 
    2663              :       INTEGER                                            :: handle, ri_atom
    2664          100 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
    2665              : 
    2666          100 :       CALL timeset(routineN, handle)
    2667              : 
    2668          100 :       particle_set => bs_env%ri_rs%particle_set
    2669          100 :       n_ao_used = 0
    2670          348 :       DO ri_atom = 1, bs_env%n_atom
    2671          992 :          IF (NORM2(particle_set(ri_atom)%r(:) - particle_set(atom_P)%r(:)) > &
    2672              :              bs_env%ri_rs%radius_ao_per_atom(ri_atom) + cutoff_ri) CYCLE
    2673              :          n_ao_used = n_ao_used + bs_env%i_ao_end_from_atom(ri_atom) - &
    2674          348 :                      bs_env%i_ao_start_from_atom(ri_atom) + 1
    2675              :       END DO
    2676              : 
    2677          100 :       CALL timestop(handle)
    2678              : 
    2679          100 :    END SUBROUTINE get_n_ao_in_sphere
    2680              : 
    2681              : ! **************************************************************************************************
    2682              : !> \brief Builds the sphere-local AO matrix phi_local(l, μ) = ϕ_μ(r_l) for one RI atom P
    2683              : !> \param bs_env ...
    2684              : !> \param qs_kind_set ...
    2685              : !> \param ri_rs_grid_points ...
    2686              : !> \param atom_P ...
    2687              : !> \param cutoff_ri ...
    2688              : !> \param n_ao_total ...
    2689              : !> \param local_grid_idx ...
    2690              : !> \param n_local_grid ...
    2691              : !> \param phi_local ...
    2692              : !> \param ao_col_map ...
    2693              : !> \param n_ao_used ...
    2694              : !> \param center ...
    2695              : ! **************************************************************************************************
    2696           54 :    SUBROUTINE build_phi_on_sphere(bs_env, qs_kind_set, ri_rs_grid_points, &
    2697              :                                   atom_P, cutoff_ri, n_ao_total, local_grid_idx, n_local_grid, &
    2698              :                                   phi_local, ao_col_map, n_ao_used, center)
    2699              : 
    2700              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2701              :       TYPE(qs_kind_type), DIMENSION(:), POINTER          :: qs_kind_set
    2702              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: ri_rs_grid_points
    2703              :       INTEGER, INTENT(IN)                                :: atom_P
    2704              :       REAL(KIND=dp), INTENT(IN)                          :: cutoff_ri
    2705              :       INTEGER, INTENT(IN)                                :: n_ao_total
    2706              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: local_grid_idx
    2707              :       INTEGER, INTENT(OUT)                               :: n_local_grid
    2708              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
    2709              :          INTENT(OUT)                                     :: phi_local
    2710              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: ao_col_map
    2711              :       INTEGER, INTENT(OUT)                               :: n_ao_used
    2712              :       REAL(KIND=dp), DIMENSION(3), INTENT(IN), OPTIONAL  :: center
    2713              : 
    2714              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'build_phi_on_sphere'
    2715              : 
    2716              :       INTEGER                                            :: col_end, col_start, handle, j, k, l, &
    2717              :                                                             loc_idx, n_grid_total, n_keep, ri_atom
    2718              :       REAL(KIND=dp)                                      :: d_sP, dist, r2_threshold
    2719           54 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: w_pt
    2720           54 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: phi_keep, sphere_grid
    2721              :       REAL(KIND=dp), DIMENSION(3)                        :: pos_P
    2722              :       TYPE(cell_type), POINTER                           :: cell
    2723           54 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
    2724              : 
    2725           54 :       CALL timeset(routineN, handle)
    2726              : 
    2727           54 :       cell => bs_env%ri_rs%cell
    2728           54 :       particle_set => bs_env%ri_rs%particle_set
    2729              :       ! AUTO_RI may pass only the global grid points needed for the current atom block.
    2730           54 :       n_grid_total = SIZE(ri_rs_grid_points, 2)
    2731           54 :       IF (PRESENT(center)) THEN
    2732            4 :          pos_P(:) = center(:)
    2733              :       ELSE
    2734          200 :          pos_P(:) = particle_set(atom_P)%r(:)
    2735              :       END IF
    2736              : 
    2737           54 :       n_local_grid = 0
    2738        19174 :       DO l = 1, n_grid_total
    2739        76480 :          dist = NORM2(ri_rs_grid_points(1:3, l) - pos_P(1:3))
    2740        19174 :          IF (dist <= cutoff_ri) n_local_grid = n_local_grid + 1
    2741              :       END DO
    2742              : 
    2743          162 :       ALLOCATE (local_grid_idx(n_local_grid))
    2744              : 
    2745           54 :       n_local_grid = 0
    2746        19174 :       DO l = 1, n_grid_total
    2747        76480 :          dist = NORM2(ri_rs_grid_points(1:3, l) - pos_P(1:3))
    2748        19174 :          IF (dist <= cutoff_ri) THEN
    2749        18358 :             n_local_grid = n_local_grid + 1
    2750        18358 :             local_grid_idx(n_local_grid) = l
    2751              :          END IF
    2752              :       END DO
    2753              : 
    2754          162 :       ALLOCATE (sphere_grid(3, n_local_grid))
    2755        18412 :       DO loc_idx = 1, n_local_grid
    2756        73486 :          sphere_grid(:, loc_idx) = ri_rs_grid_points(:, local_grid_idx(loc_idx))
    2757              :       END DO
    2758              : 
    2759              :       ! Only AOs on atoms that reach into the sphere can be non-zero here
    2760          162 :       ALLOCATE (ao_col_map(n_ao_total))
    2761           54 :       ao_col_map(:) = 0
    2762           54 :       n_ao_used = 0
    2763          190 :       DO ri_atom = 1, bs_env%n_atom
    2764          544 :          d_sP = NORM2(particle_set(ri_atom)%r(:) - pos_P(:))
    2765          136 :          IF (d_sP > bs_env%ri_rs%radius_ao_per_atom(ri_atom) + cutoff_ri) CYCLE
    2766              : 
    2767          690 :          DO j = bs_env%i_ao_start_from_atom(ri_atom), bs_env%i_ao_end_from_atom(ri_atom)
    2768          500 :             n_ao_used = n_ao_used + 1
    2769          636 :             ao_col_map(j) = n_ao_used
    2770              :          END DO
    2771              :       END DO
    2772              : 
    2773          216 :       ALLOCATE (phi_local(n_local_grid, n_ao_used))
    2774           54 :       phi_local = 0.0_dp
    2775              : 
    2776          190 :       DO ri_atom = 1, bs_env%n_atom
    2777          544 :          d_sP = NORM2(particle_set(ri_atom)%r(:) - pos_P(:))
    2778          136 :          IF (d_sP > bs_env%ri_rs%radius_ao_per_atom(ri_atom) + cutoff_ri) CYCLE
    2779              : 
    2780          136 :          col_start = ao_col_map(bs_env%i_ao_start_from_atom(ri_atom))
    2781          136 :          col_end = ao_col_map(bs_env%i_ao_end_from_atom(ri_atom))
    2782              :          ! A positive CUTOFF_RADIUS_RI_AO overrides the per-atom Gaussian radius
    2783              :          ! with a user-defined hard cutoff.
    2784          136 :          IF (bs_env%ri_rs%cutoff_radius_ri_ao > 0.0_dp) THEN
    2785            9 :             r2_threshold = bs_env%ri_rs%cutoff_radius_ri_ao**2
    2786              :          ELSE
    2787          127 :             r2_threshold = bs_env%ri_rs%radius_ao_per_atom(ri_atom)**2
    2788              :          END IF
    2789              : 
    2790              :          CALL evaluate_ao_on_points(phi_local(:, col_start:col_end), sphere_grid, &
    2791              :                                     ri_atom, particle_set, qs_kind_set, cell, &
    2792          190 :                                     cutoff_squared=r2_threshold)
    2793              :       END DO
    2794              : 
    2795           54 :       DEALLOCATE (sphere_grid)
    2796              : 
    2797           54 :       IF (n_local_grid > 0) THEN
    2798          162 :          ALLOCATE (w_pt(n_local_grid))
    2799              :          !$OMP PARALLEL DO DEFAULT(NONE) &
    2800              :          !$OMP SHARED(n_local_grid, n_ao_used, phi_local, w_pt) &
    2801           54 :          !$OMP PRIVATE(l, j) SCHEDULE(STATIC)
    2802              :          DO l = 1, n_local_grid
    2803              :             w_pt(l) = 0.0_dp
    2804              :             DO j = 1, n_ao_used
    2805              :                w_pt(l) = MAX(w_pt(l), ABS(phi_local(l, j)))
    2806              :             END DO
    2807              :          END DO
    2808              :          !$OMP END PARALLEL DO
    2809        18412 :          n_keep = COUNT(w_pt > bs_env%eps_filter)
    2810           54 :          IF (n_keep < n_local_grid) THEN
    2811           12 :             ALLOCATE (phi_keep(n_keep, n_ao_used))
    2812            3 :             k = 0
    2813          774 :             DO l = 1, n_local_grid
    2814          774 :                IF (w_pt(l) > bs_env%eps_filter) THEN
    2815          702 :                   k = k + 1
    2816         5616 :                   phi_keep(k, :) = phi_local(l, :)
    2817          702 :                   local_grid_idx(k) = local_grid_idx(l)
    2818              :                END IF
    2819              :             END DO
    2820            3 :             CALL MOVE_ALLOC(phi_keep, phi_local)
    2821            3 :             n_local_grid = n_keep
    2822              :          END IF
    2823           54 :          DEALLOCATE (w_pt)
    2824              :       END IF
    2825              : 
    2826           54 :       CALL timestop(handle)
    2827              : 
    2828          108 :    END SUBROUTINE build_phi_on_sphere
    2829              : 
    2830              : ! **************************************************************************************************
    2831              : !> \brief Computes a three-center integral block directly in AUTO_RI contraction columns.
    2832              : !>
    2833              : !>        For each angular momentum l, the primitive RI Gaussians on the atoms contributing
    2834              : !>        to one optimized function are collected before the contraction
    2835              : !>
    2836              : !>          (μν|p) = Σ_P (μν|P) U_Pp .
    2837              : !>
    2838              : !>        This avoids constructing and retaining the complete reference-basis block (μν|P).
    2839              : !> \param int_3c ...
    2840              : !> \param ctx ...
    2841              : !> \param ws ...
    2842              : !> \param atom_j ...
    2843              : !> \param atom_k ...
    2844              : !> \param atom_i ...
    2845              : !> \param transform ...
    2846              : !> \param transform_row ...
    2847              : !> \param screened ...
    2848              : ! **************************************************************************************************
    2849          108 :    SUBROUTINE build_3c_integral_block_auto_ri_ctx(int_3c, ctx, ws, atom_j, atom_k, atom_i, &
    2850           36 :                                                   transform, transform_row, screened)
    2851              :       REAL(KIND=dp), DIMENSION(:, :, :), INTENT(INOUT)   :: int_3c
    2852              :       TYPE(gw_3c_ctx_type), INTENT(IN)                   :: ctx
    2853              :       TYPE(gw_3c_ws_type), INTENT(INOUT)                 :: ws
    2854              :       INTEGER, INTENT(IN)                                :: atom_j, atom_k, atom_i
    2855              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: transform
    2856              :       INTEGER, INTENT(IN)                                :: transform_row
    2857              :       LOGICAL, INTENT(OUT), OPTIONAL                     :: screened
    2858              : 
    2859              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'build_3c_integral_block_auto_ri_ctx'
    2860              : 
    2861              :       INTEGER :: handle_contract, handle_eri, ikind, iset, jkind, jset, kkind, kset, l, ncoi, &
    2862              :          ncoj, ncok, ncol, npgf_group, nseti, nsetj, nsetk, primitive_first, sgfi, sgfj, sgfk
    2863           36 :       INTEGER, DIMENSION(:), POINTER                     :: lmax_i, lmax_j, lmax_k, lmin_i, lmin_j, &
    2864           36 :                                                             lmin_k, npgfi, npgfj, npgfk, nsgfi, &
    2865           36 :                                                             nsgfj, nsgfk
    2866           36 :       INTEGER, DIMENSION(:, :), POINTER                  :: first_sgf_i, first_sgf_j, first_sgf_k
    2867              :       REAL(KIND=dp)                                      :: dij, dik, djk, group_radius, &
    2868              :                                                             kind_radius_i, kind_radius_j, &
    2869              :                                                             kind_radius_k, sijk_ext
    2870           36 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: rpgf_group, zet_group
    2871           36 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: spi_group
    2872           36 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: sijk, sijk_contr
    2873              :       REAL(KIND=dp), DIMENSION(3)                        :: ri, rij, rik, rj, rjk, rk
    2874           36 :       REAL(KIND=dp), DIMENSION(:), POINTER               :: set_radius_i, set_radius_j, set_radius_k
    2875           36 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: rpgf_i, rpgf_j, rpgf_k, zeti, zetj, zetk
    2876              : 
    2877           36 :       IF (PRESENT(screened)) screened = .FALSE.
    2878           36 :       ri = pbc(ctx%particle_set(atom_i)%r(1:3), ctx%cell)
    2879           36 :       rj = pbc(ctx%particle_set(atom_j)%r(1:3), ctx%cell)
    2880           36 :       rk = pbc(ctx%particle_set(atom_k)%r(1:3), ctx%cell)
    2881          144 :       rjk = rk - rj
    2882          144 :       rij = rj - ri
    2883          144 :       rik = rk - ri
    2884          144 :       djk = NORM2(rjk)
    2885          144 :       dij = NORM2(rij)
    2886          144 :       dik = NORM2(rik)
    2887              : 
    2888           36 :       ikind = ctx%kind_of(atom_i)
    2889           36 :       jkind = ctx%kind_of(atom_j)
    2890           36 :       kkind = ctx%kind_of(atom_k)
    2891              :       CALL get_gto_basis_set(ctx%basis_i(ikind)%gto_basis_set, first_sgf=first_sgf_i, &
    2892              :                              lmax=lmax_i, lmin=lmin_i, npgf=npgfi, nset=nseti, &
    2893              :                              nsgf_set=nsgfi, pgf_radius=rpgf_i, set_radius=set_radius_i, &
    2894           36 :                              zet=zeti, kind_radius=kind_radius_i)
    2895              :       CALL get_gto_basis_set(ctx%basis_j(jkind)%gto_basis_set, first_sgf=first_sgf_j, &
    2896              :                              lmax=lmax_j, lmin=lmin_j, npgf=npgfj, nset=nsetj, &
    2897              :                              nsgf_set=nsgfj, pgf_radius=rpgf_j, set_radius=set_radius_j, &
    2898           36 :                              zet=zetj, kind_radius=kind_radius_j)
    2899              :       CALL get_gto_basis_set(ctx%basis_k(kkind)%gto_basis_set, first_sgf=first_sgf_k, &
    2900              :                              lmax=lmax_k, lmin=lmin_k, npgf=npgfk, nset=nsetk, &
    2901              :                              nsgf_set=nsgfk, pgf_radius=rpgf_k, set_radius=set_radius_k, &
    2902           36 :                              zet=zetk, kind_radius=kind_radius_k)
    2903              : 
    2904              :       IF (kind_radius_j + kind_radius_i + ctx%dr_ij < dij .OR. &
    2905           36 :           kind_radius_j + kind_radius_k + ctx%dr_jk < djk .OR. &
    2906              :           kind_radius_k + kind_radius_i + ctx%dr_ik < dik) THEN
    2907            0 :          IF (PRESENT(screened)) screened = .TRUE.
    2908            0 :          RETURN
    2909              :       END IF
    2910              : 
    2911           36 :       ncol = SIZE(transform, 2)
    2912           36 :       CPASSERT(SIZE(int_3c, 3) == ncol)
    2913          252 :       DO l = 0, ctx%maxli
    2914          216 :          npgf_group = 0
    2915          216 :          group_radius = 0.0_dp
    2916         4392 :          DO iset = 1, nseti
    2917         4176 :             IF (lmin_i(iset) /= l .OR. lmax_i(iset) /= l) CYCLE
    2918          696 :             npgf_group = npgf_group + npgfi(iset)
    2919         4392 :             group_radius = MAX(group_radius, set_radius_i(iset))
    2920              :          END DO
    2921          216 :          IF (npgf_group == 0) CYCLE
    2922          192 :          ncoi = npgf_group*ncoset(l)
    2923          768 :          ALLOCATE (zet_group(npgf_group), rpgf_group(npgf_group))
    2924          768 :          ALLOCATE (spi_group(ncoi, ncol), source=0.0_dp)
    2925          192 :          primitive_first = 1
    2926         4008 :          DO iset = 1, nseti
    2927         3816 :             IF (lmin_i(iset) /= l .OR. lmax_i(iset) /= l) CYCLE
    2928              :             zet_group(primitive_first:primitive_first + npgfi(iset) - 1) = &
    2929         1392 :                zeti(1:npgfi(iset), iset)
    2930              :             rpgf_group(primitive_first:primitive_first + npgfi(iset) - 1) = &
    2931         1392 :                rpgf_i(1:npgfi(iset), iset)
    2932          696 :             sgfi = first_sgf_i(1, iset)
    2933              :             spi_group((primitive_first - 1)*ncoset(l) + 1: &
    2934              :                       (primitive_first + npgfi(iset) - 1)*ncoset(l), :) = &
    2935          696 :                MATMUL(ctx%spi(iset, ikind)%array, &
    2936              :                       transform(transform_row + sgfi - 1: &
    2937      1437180 :                                 transform_row + sgfi + nsgfi(iset) - 2, :))
    2938         4008 :             primitive_first = primitive_first + npgfi(iset)
    2939              :          END DO
    2940              : 
    2941          480 :          DO jset = 1, nsetj
    2942          288 :             IF (set_radius_j(jset) + group_radius + ctx%dr_ij < dij) CYCLE
    2943          832 :             DO kset = 1, nsetk
    2944          352 :                IF (set_radius_j(jset) + set_radius_k(kset) + ctx%dr_jk < djk) CYCLE
    2945          352 :                IF (set_radius_k(kset) + group_radius + ctx%dr_ik < dik) CYCLE
    2946          352 :                ncoj = npgfj(jset)*ncoset(lmax_j(jset))
    2947          352 :                ncok = npgfk(kset)*ncoset(lmax_k(kset))
    2948          352 :                sgfj = first_sgf_j(1, jset)
    2949          352 :                sgfk = first_sgf_k(1, kset)
    2950          352 :                IF (ncoj*ncok*ncoi <= 0) CYCLE
    2951         1760 :                ALLOCATE (sijk(ncoj, ncok, ncoi), source=0.0_dp)
    2952          352 :                CALL timeset(routineN//'_eri', handle_eri)
    2953              :                CALL eri_3center(sijk, &
    2954              :                                 lmin_j(jset), lmax_j(jset), npgfj(jset), zetj(:, jset), &
    2955              :                                 rpgf_j(:, jset), rj, &
    2956              :                                 lmin_k(kset), lmax_k(kset), npgfk(kset), zetk(:, kset), &
    2957              :                                 rpgf_k(:, kset), rk, l, l, npgf_group, zet_group, &
    2958              :                                 rpgf_group, ri, djk, dij, dik, ws%lib, ctx%potential_parameter, &
    2959          352 :                                 int_abc_ext=sijk_ext)
    2960          352 :                CALL timestop(handle_eri)
    2961         1760 :                ALLOCATE (sijk_contr(nsgfj(jset), nsgfk(kset), ncol))
    2962          352 :                CALL timeset(routineN//'_contract', handle_contract)
    2963              :                CALL abc_contract_xsmm(sijk_contr, sijk, ctx%tspj(jset, jkind)%array, &
    2964              :                                       ctx%spk(kset, kkind)%array, spi_group, ncoj, ncok, ncoi, &
    2965          352 :                                       nsgfj(jset), nsgfk(kset), ncol, ws%cpp_buffer, ws%ccp_buffer)
    2966          352 :                CALL timestop(handle_contract)
    2967          352 :                DEALLOCATE (sijk)
    2968              :                int_3c(sgfj:sgfj + nsgfj(jset) - 1, sgfk:sgfk + nsgfk(kset) - 1, :) = &
    2969              :                   int_3c(sgfj:sgfj + nsgfj(jset) - 1, sgfk:sgfk + nsgfk(kset) - 1, :) + &
    2970        14476 :                   sijk_contr
    2971         1344 :                DEALLOCATE (sijk_contr)
    2972              :             END DO
    2973              :          END DO
    2974          252 :          DEALLOCATE (zet_group, rpgf_group, spi_group)
    2975              :       END DO
    2976              : 
    2977           72 :    END SUBROUTINE build_3c_integral_block_auto_ri_ctx
    2978              : 
    2979              : ! **************************************************************************************************
    2980              : !> \brief Returns the reference RI basis size of one atom.
    2981              : !> \param bs_env ...
    2982              : !> \param iatom ...
    2983              : !> \return ...
    2984              : ! **************************************************************************************************
    2985           15 :    INTEGER FUNCTION get_ref_RI_size(bs_env, iatom) RESULT(n)
    2986              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2987              :       INTEGER, INTENT(IN)                                :: iatom
    2988              : 
    2989              :       INTEGER                                            :: ikind
    2990              : 
    2991           15 :       ikind = bs_env%ri_rs%particle_set(iatom)%atomic_kind%kind_number
    2992           15 :       n = bs_env%basis_set_RI(ikind)%gto_basis_set%nsgf
    2993              : 
    2994           15 :    END FUNCTION get_ref_RI_size
    2995              : 
    2996              : END MODULE gw_ri_rs_compute_Z_lP
        

Generated by: LCOV version 2.0-1