LCOV - code coverage report
Current view: top level - src - qs_kpoint_operators.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:9ccafda) Lines: 93.3 % 794 741
Test Date: 2026-10-15 00:55:44 Functions: 76.3 % 38 29

            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 Assembly of real and complex k-point operators from real-space DBCSR matrices.
      10              : !>        Outputs use the distribution of the k-point group; real operators transfer one part only.
      11              : !>        The module splits this work into a start step and a finish step.
      12              : !>        One process can start many operators before it finishes any of them.
      13              : !>        The STANDARD k-point driver uses this order.
      14              : !>        The routines start, finish and get are collective over the
      15              : !>        environment-wide communicator. Every rank must call the same
      16              : !>        sequence of service calls.
      17              : !>        A context references its host kpoint environment without owning it,
      18              : !>        so it must not outlive that environment.
      19              : !>        An explicitly prepared, immutable operator batch can instead be built
      20              : !>        from bounded context-owned real-space snapshots inside each group.
      21              : !>        The public round and output-ownership contracts stay the same.
      22              : !>        Start/finish/get/prepare are not thread safe: serialize them, because their collective
      23              : !>        communication tolerates no concurrent service call. A separate
      24              : !>        context per thread only keeps the scratch from aliasing.
      25              : ! **************************************************************************************************
      26              : MODULE qs_kpoint_operators
      27              :    USE cp_cfm_basic_linalg,             ONLY: cp_cfm_scale_and_add_fm
      28              :    USE cp_cfm_types,                    ONLY: cp_cfm_type
      29              :    USE cp_dbcsr_api,                    ONLY: &
      30              :         dbcsr_create, dbcsr_deallocate_matrix, dbcsr_get_info, dbcsr_get_readonly_block_p, &
      31              :         dbcsr_init_p, dbcsr_p_type, dbcsr_set, dbcsr_type, dbcsr_type_antisymmetric, &
      32              :         dbcsr_type_symmetric
      33              :    USE cp_dbcsr_cp2k_link,              ONLY: cp_dbcsr_alloc_block_from_nbl
      34              :    USE cp_dbcsr_operations,             ONLY: copy_dbcsr_to_fm,&
      35              :                                               copy_fm_to_dbcsr_bc,&
      36              :                                               dbcsr_fm_copy_plan_type
      37              :    USE cp_fm_pool_types,                ONLY: cp_fm_pool_p_type,&
      38              :                                               fm_pool_create_fm,&
      39              :                                               fm_pool_give_back_fm
      40              :    USE cp_fm_struct,                    ONLY: cp_fm_struct_equivalent,&
      41              :                                               cp_fm_struct_release,&
      42              :                                               cp_fm_struct_retain,&
      43              :                                               cp_fm_struct_type
      44              :    USE cp_fm_types,                     ONLY: copy_info_type,&
      45              :                                               cp_fm_cleanup_copy_general,&
      46              :                                               cp_fm_create,&
      47              :                                               cp_fm_finish_copy_general,&
      48              :                                               cp_fm_get_info,&
      49              :                                               cp_fm_release,&
      50              :                                               cp_fm_start_copy_general,&
      51              :                                               cp_fm_type
      52              :    USE input_cp2k_kpoints,              ONLY: lattice_fft_auto,&
      53              :                                               lattice_fft_on
      54              :    USE kinds,                           ONLY: dp,&
      55              :                                               int_8
      56              :    USE kpoint_methods,                  ONLY: kp_transform_plan_create,&
      57              :                                               kp_transform_plan_type,&
      58              :                                               rskp_grid_type,&
      59              :                                               rskp_transform,&
      60              :                                               rskp_transform_grid_extract,&
      61              :                                               rskp_transform_grid_prepare,&
      62              :                                               rskp_transform_grid_release
      63              :    USE kpoint_types,                    ONLY: kpoint_env_type,&
      64              :                                               kpoint_type
      65              :    USE mathconstants,                   ONLY: gaussi,&
      66              :                                               twopi,&
      67              :                                               z_one,&
      68              :                                               z_zero
      69              :    USE message_passing,                 ONLY: mp_para_env_type
      70              :    USE qs_matrix_pools,                 ONLY: mpools_get
      71              :    USE qs_neighbor_list_types,          ONLY: neighbor_list_set_p_type
      72              : 
      73              : !$ USE omp_lib, ONLY: omp_get_max_threads, omp_get_num_threads, omp_get_thread_num
      74              : #include "./base/base_uses.f90"
      75              : 
      76              :    IMPLICIT NONE
      77              : 
      78              :    PRIVATE
      79              : 
      80              :    CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'qs_kpoint_operators'
      81              : 
      82              :    PUBLIC :: kpoint_operator_context_create, kpoint_operator_context_release
      83              :    PUBLIC :: kpoint_operator_start, kpoint_operator_finish
      84              :    PUBLIC :: kpoint_operator_get, kpoint_operator_prepare
      85              :    PUBLIC :: kpoint_operator_evaluate, kpoint_operator_work_size
      86              :    PUBLIC :: kpoint_operator_store, kpoint_operator_get_local, kpoint_operator_get_all
      87              : 
      88              :    INTEGER, PARAMETER, PUBLIC :: kpoint_slot_ks = 1, &
      89              :                                  kpoint_slot_s = 2, &
      90              :                                  kpoint_slot_t = 3
      91              : 
      92              :    !> Row of rsmat that carries the spin-free operator image. S and T carry
      93              :    !> no spin copies: drivers launch one instance per spin of their loop and
      94              :    !> read this same row in every instance.
      95              :    INTEGER, PARAMETER, PUBLIC :: kpoint_spin_free = 1
      96              : 
      97              :    !> Number of defined slots; grows only inside this module.
      98              :    INTEGER, PARAMETER, PRIVATE :: kpoint_num_slots = kpoint_slot_t - kpoint_slot_ks + 1
      99              : 
     100              :    !> Part indices of one transfer: its real and its imaginary half.
     101              :    INTEGER, PARAMETER, PRIVATE :: kpoint_part_re = 1, &
     102              :                                   kpoint_part_im = 2
     103              : 
     104              :    !> Instance states of one (k point, spin, slot) transfer.
     105              :    !> State transitions happen only inside the service routines.
     106              :    INTEGER, PARAMETER, PRIVATE :: kpoint_slot_idle = 0, &
     107              :                                   kpoint_slot_in_flight = 1, &
     108              :                                   kpoint_slot_local = 2
     109              : 
     110              :    ! Structural route shared by all captured rows. Whole blocks go to one
     111              :    ! representative group, then only equivalent tiles are replicated.
     112              :    TYPE kpoint_operator_rs_route_type
     113              :       TYPE(kp_transform_plan_type) :: traversal = kp_transform_plan_type()
     114              :       TYPE(cp_fm_struct_type), POINTER :: matrix_struct => NULL()
     115              :       INTEGER :: nimage = 0, nvalue = 0, nrow = 0, ncol = 0
     116              :       INTEGER(KIND=int_8) :: storage = 0
     117              :       INTEGER, ALLOCATABLE :: row_size(:), col_size(:), ENTRY(:, :), first(:), tile(:, :, :)
     118              :       INTEGER, ALLOCATABLE :: send_entry(:), send_first(:), scount(:, :), rcount(:, :), &
     119              :                               sdisp(:, :), rdisp(:, :)
     120              :       REAL(KIND=dp), ALLOCATABLE :: send_values(:)
     121              :    END TYPE kpoint_operator_rs_route_type
     122              : 
     123              :    ! Immutable values, not per-k matrices. Metadata belong to the shared route;
     124              :    ! source identifies starts belonging to the explicitly prepared batch only.
     125              :    TYPE kpoint_operator_block_type
     126              :       REAL(KIND=dp), POINTER :: data(:, :) => NULL()
     127              :    END TYPE kpoint_operator_block_type
     128              : 
     129              :    TYPE kpoint_operator_rs_type
     130              :       INTEGER :: matrix_row = 0
     131              :       TYPE(dbcsr_p_type), POINTER :: source(:, :) => NULL()
     132              :       ! Values are owned only for distributed sources. Blocks always borrow:
     133              :       ! either from values below or, on one world rank, directly from DBCSR.
     134              :       REAL(KIND=dp), CONTIGUOUS, POINTER :: values(:) => NULL()
     135              :       TYPE(kpoint_operator_block_type), ALLOCATABLE :: blocks(:)
     136              :       LOGICAL, ALLOCATABLE :: nonzero(:)
     137              :    END TYPE kpoint_operator_rs_type
     138              : 
     139              :    ! Small read-ahead window per prepared row. Keep mutable output separate
     140              :    ! from the immutable snapshot; prepare invalidates only the selected slot.
     141              :    TYPE kpoint_operator_rs_batch_type
     142              :       INTEGER :: first = 0
     143              :       COMPLEX(KIND=dp), ALLOCATABLE :: data(:, :, :)
     144              :    END TYPE kpoint_operator_rs_batch_type
     145              : 
     146              :    INTEGER, PARAMETER :: rs_batch_size = 4
     147              :    ! Includes cached tiles and batch phase scratch, divided between all rows/slots.
     148              :    INTEGER(KIND=int_8), PARAMETER :: rs_batch_bytes = 32_int_8*1024_int_8**2
     149              : 
     150              : ! **************************************************************************************************
     151              : !> \brief Caller-owned assembly context for one driver call.
     152              : !>        Created and released in the same driver routine and passed to helpers
     153              : !>        as an argument; never stored on the host kpoint_type.
     154              : !>        Components are private and touched only by this module.
     155              : ! **************************************************************************************************
     156              :    TYPE, PUBLIC :: kpoint_operator_context_type
     157              :       PRIVATE
     158              :       ! Instance spin space of the owning driver call: the bound of the spin
     159              :       ! loop that start and finish key. Declared once at creation. It never
     160              :       ! comes from the operators: the first matrix dimension of a spin-free
     161              :       ! operator such as S counts derivatives, and the images live in the
     162              :       ! second, so callers address operator rows through matrix_row instead.
     163              :       INTEGER                                            :: nspin = 0
     164              :       ! Borrowed host/distribution references; the context cannot span an energy evaluation.
     165              :       TYPE(kpoint_type), POINTER                          :: kpoints => NULL()
     166              :       TYPE(cp_fm_struct_type), POINTER                    :: ao_ao_fmstruct => NULL()
     167              :       TYPE(cp_fm_pool_p_type), DIMENSION(:), POINTER      :: group_fm_pools => NULL()
     168              :       ! Identity of the neighbor lists used by scratch and prepared snapshots.
     169              :       TYPE(neighbor_list_set_p_type), DIMENSION(:), &
     170              :          POINTER                                          :: work_sab => NULL()
     171              :       ! Owned scratch, created on first use and released with the context.
     172              :       TYPE(dbcsr_type), POINTER                           :: rmatrix => NULL()
     173              :       TYPE(dbcsr_type), POINTER                           :: cmatrix => NULL()
     174              :       TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:)         :: fmwork
     175              :       TYPE(cp_fm_type), ALLOCATABLE                       :: fmlocal, fmimag
     176              :       ! Plans share the lifetime and fixed layouts of the two densify workspaces.
     177              :       TYPE(dbcsr_fm_copy_plan_type), ALLOCATABLE          :: copy_plan(:)
     178              :       ! Immutable snapshots and bounded local output batches. Prepared starts
     179              :       ! need neither per-k transfers nor outstanding MPI requests.
     180              :       TYPE(kpoint_operator_rs_type), ALLOCATABLE          :: rs(:, :)
     181              :       TYPE(kpoint_operator_rs_route_type), ALLOCATABLE    :: rs_route
     182              :       TYPE(kpoint_operator_rs_batch_type), ALLOCATABLE    :: rs_batch(:, :)
     183              :       ! Bookkeeping. info is keyed (global k point, spin, slot, part),
     184              :       ! part kpoint_part_re holding the real half and kpoint_part_im holding
     185              :       ! the imaginary half. Status is keyed (global round, spin, slot)
     186              :       ! because start and finish treat all groups of one instance
     187              :       ! atomically. Allocated on first start.
     188              :       TYPE(copy_info_type), ALLOCATABLE, DIMENSION(:, :, :, :) :: info
     189              :       INTEGER, ALLOCATABLE, DIMENSION(:, :, :)            :: status
     190              :    END TYPE kpoint_operator_context_type
     191              : 
     192              : CONTAINS
     193              : 
     194              : ! **************************************************************************************************
     195              : !> \brief Transform and densify one operator using the context-owned workspaces and routing plans.
     196              : !> \param ctx initialized transfer context
     197              : !> \param rsmat real-space operator images
     198              : !> \param matrix_row operator row, independent of the instance spin
     199              : !> \param ik global k-point index
     200              : !> \param grid caller-owned reciprocal-grid cache
     201              : !> \param use_grid extract from grid instead of a direct phase sum
     202              : ! **************************************************************************************************
     203           88 :    SUBROUTINE kpoint_operator_densify(ctx, rsmat, matrix_row, ik, grid, use_grid)
     204              : 
     205              :       TYPE(kpoint_operator_context_type), INTENT(INOUT)  :: ctx
     206              :       TYPE(dbcsr_p_type), DIMENSION(:, :), POINTER       :: rsmat
     207              :       INTEGER, INTENT(IN)                                :: matrix_row, ik
     208              :       TYPE(rskp_grid_type), INTENT(IN), OPTIONAL         :: grid
     209              :       LOGICAL, INTENT(IN)                                :: use_grid
     210              : 
     211              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_densify'
     212              : 
     213              :       INTEGER                                            :: handle
     214           88 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: xkp
     215              : 
     216           88 :       CALL timeset(routineN, handle)
     217              : 
     218              :       ! Avoid nested component array sections in GCC 16 bounds checks.
     219           88 :       xkp => ctx%kpoints%xkp
     220           88 :       IF (ctx%kpoints%use_real_wfn) THEN
     221           16 :          IF (use_grid) THEN
     222            0 :             CPASSERT(PRESENT(grid))
     223            0 :             CALL rskp_transform_grid_extract(grid, ik, ctx%rmatrix)
     224              :          ELSE
     225           16 :             CALL dbcsr_set(ctx%rmatrix, 0.0_dp)
     226              :             CALL rskp_transform(rmatrix=ctx%rmatrix, rsmat=rsmat, ispin=matrix_row, &
     227              :                                 xkp=xkp(:, ik), cell_to_index=ctx%kpoints%cell_to_index, &
     228           16 :                                 sab_nl=ctx%kpoints%sab_nl)
     229              :          END IF
     230           72 :       ELSE IF (use_grid) THEN
     231            0 :          CPASSERT(PRESENT(grid))
     232            0 :          CALL rskp_transform_grid_extract(grid, ik, ctx%rmatrix, ctx%cmatrix)
     233              :       ELSE
     234           72 :          CALL dbcsr_set(ctx%rmatrix, 0.0_dp)
     235           72 :          CALL dbcsr_set(ctx%cmatrix, 0.0_dp)
     236              :          CALL rskp_transform(rmatrix=ctx%rmatrix, cmatrix=ctx%cmatrix, rsmat=rsmat, ispin=matrix_row, &
     237              :                              xkp=xkp(:, ik), cell_to_index=ctx%kpoints%cell_to_index, &
     238           72 :                              sab_nl=ctx%kpoints%sab_nl)
     239              :       END IF
     240           88 :       CALL copy_dbcsr_to_fm(ctx%rmatrix, ctx%fmwork(kpoint_part_re), ctx%copy_plan(kpoint_part_re))
     241           88 :       IF (.NOT. ctx%kpoints%use_real_wfn) THEN
     242           72 :          CALL copy_dbcsr_to_fm(ctx%cmatrix, ctx%fmwork(kpoint_part_im), ctx%copy_plan(kpoint_part_im))
     243              :       END IF
     244              : 
     245           88 :       CALL timestop(handle)
     246              : 
     247           88 :    END SUBROUTINE kpoint_operator_densify
     248              : 
     249              : ! **************************************************************************************************
     250              : !> \brief Launch a densified operator to its owning group (only the real part for real wavefunctions).
     251              : !>        Absent explicit targets, receive both parts through fmlocal. Each copy
     252              : !>        owns its receive buffer until finish, so the targets can alias.
     253              : !>        Non-owning groups participate with an empty dummy target.
     254              : !> \param kpoints host environment, including group ownership and transfer communicator
     255              : !> \param ik global k-point index
     256              : !> \param fmwork real/imaginary source matrices on the global distribution
     257              : !> \param fmlocal default receiver on the k-point group distribution
     258              : !> \param info transfer states, real part first; real wavefunctions allocate only the first part
     259              : !> \param target_re optional caller-owned real-part target
     260              : !> \param target_im optional imaginary-part target; required with target_re for complex wavefunctions
     261              : ! **************************************************************************************************
     262           88 :    SUBROUTINE kpoint_operator_launch(kpoints, ik, fmwork, fmlocal, info, target_re, target_im)
     263              : 
     264              :       TYPE(kpoint_type), POINTER                         :: kpoints
     265              :       INTEGER, INTENT(IN)                                :: ik
     266              :       TYPE(cp_fm_type), DIMENSION(:), INTENT(IN)         :: fmwork
     267              :       TYPE(cp_fm_type), INTENT(IN)                       :: fmlocal
     268              :       TYPE(copy_info_type), DIMENSION(:)                 :: info
     269              :       TYPE(cp_fm_type), INTENT(IN), OPTIONAL             :: target_re, target_im
     270              : 
     271              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_launch'
     272              : 
     273              :       INTEGER                                            :: handle
     274              :       TYPE(cp_fm_type)                                   :: fmdummy
     275              :       TYPE(mp_para_env_type), POINTER                    :: para_env
     276              : 
     277           88 :       CALL timeset(routineN, handle)
     278           88 :       CPASSERT(.NOT. PRESENT(target_im) .OR. PRESENT(target_re))
     279           88 :       IF (.NOT. kpoints%use_real_wfn) THEN
     280           72 :          CPASSERT(PRESENT(target_re) .EQV. PRESENT(target_im))
     281              :       END IF
     282              : 
     283           88 :       para_env => kpoints%blacs_env_all%para_env
     284           88 :       IF (ik >= kpoints%kp_range(1) .AND. ik <= kpoints%kp_range(2)) THEN
     285           64 :          IF (PRESENT(target_re)) THEN
     286            0 :             CALL cp_fm_start_copy_general(fmwork(kpoint_part_re), target_re, para_env, info(kpoint_part_re))
     287            0 :             IF (.NOT. kpoints%use_real_wfn) THEN
     288            0 :                CALL cp_fm_start_copy_general(fmwork(kpoint_part_im), target_im, para_env, info(kpoint_part_im))
     289              :             END IF
     290              :          ELSE
     291           64 :             CALL cp_fm_start_copy_general(fmwork(kpoint_part_re), fmlocal, para_env, info(kpoint_part_re))
     292           64 :             IF (.NOT. kpoints%use_real_wfn) THEN
     293           48 :                CALL cp_fm_start_copy_general(fmwork(kpoint_part_im), fmlocal, para_env, info(kpoint_part_im))
     294              :             END IF
     295              :          END IF
     296              :       ELSE
     297           24 :          CALL cp_fm_start_copy_general(fmwork(kpoint_part_re), fmdummy, para_env, info(kpoint_part_re))
     298           24 :          IF (.NOT. kpoints%use_real_wfn) THEN
     299           24 :             CALL cp_fm_start_copy_general(fmwork(kpoint_part_im), fmdummy, para_env, info(kpoint_part_im))
     300              :          END IF
     301              :       END IF
     302              : 
     303           88 :       CALL timestop(handle)
     304              : 
     305           88 :    END SUBROUTINE kpoint_operator_launch
     306              : 
     307              : ! **************************************************************************************************
     308              : !> \brief Receive one group's operator and reclaim every rank's send buffers.
     309              : !>        CFM/DBCSR outputs consume the context buffer before receiving the next part;
     310              : !>        FM outputs receive directly. Inactive groups only clean up their sends.
     311              : !> \param kpoints parallel environment
     312              : !> \param fmlocal shared receive buffer, consumed before receiving the next part
     313              : !> \param ik global k-point index
     314              : !> \param info real/imaginary transfers started by kpoint_operator_launch
     315              : !> \param cmat complex output, real part overwrites and imaginary part adds
     316              : !> \param fm_re real FM output
     317              : !> \param fm_im optional imaginary FM output
     318              : !> \param mat_re real DBCSR output
     319              : !> \param mat_im imaginary DBCSR output
     320              : ! **************************************************************************************************
     321           88 :    SUBROUTINE kpoint_operator_receive(kpoints, fmlocal, ik, info, cmat, fm_re, fm_im, mat_re, mat_im)
     322              :       TYPE(kpoint_type), INTENT(IN), POINTER             :: kpoints
     323              :       TYPE(cp_fm_type)                                   :: fmlocal
     324              :       INTEGER, INTENT(IN)                                :: ik
     325              :       TYPE(copy_info_type), INTENT(INOUT)                :: info(:)
     326              :       TYPE(cp_cfm_type), OPTIONAL                        :: cmat
     327              :       TYPE(cp_fm_type), OPTIONAL                         :: fm_re, fm_im
     328              :       TYPE(dbcsr_type), INTENT(INOUT), OPTIONAL          :: mat_re, mat_im
     329              : 
     330              :       INTEGER                                            :: part
     331              : 
     332           88 :       IF (ik >= kpoints%kp_range(1) .AND. ik <= kpoints%kp_range(2)) THEN
     333           64 :          IF (PRESENT(cmat)) THEN
     334           48 :             CALL cp_fm_finish_copy_general(fmlocal, info(kpoint_part_re))
     335           48 :             CALL cp_cfm_scale_and_add_fm(z_zero, cmat, z_one, fmlocal)
     336           48 :             IF (.NOT. kpoints%use_real_wfn) THEN
     337           48 :                CALL cp_fm_finish_copy_general(fmlocal, info(kpoint_part_im))
     338           48 :                CALL cp_cfm_scale_and_add_fm(z_one, cmat, gaussi, fmlocal)
     339              :             END IF
     340           16 :          ELSE IF (PRESENT(fm_re)) THEN
     341           16 :             CALL cp_fm_finish_copy_general(fm_re, info(kpoint_part_re))
     342           16 :             IF (kpoints%use_real_wfn) THEN
     343           16 :                IF (PRESENT(fm_im)) fm_im%local_data(:, :) = 0.0_dp
     344              :             ELSE
     345            0 :                CALL cp_fm_finish_copy_general(fm_im, info(kpoint_part_im))
     346              :             END IF
     347              :          ELSE
     348            0 :             CALL cp_fm_finish_copy_general(fmlocal, info(kpoint_part_re))
     349            0 :             CALL copy_fm_to_dbcsr_bc(fmlocal, mat_re)
     350            0 :             IF (kpoints%use_real_wfn) THEN
     351            0 :                fmlocal%local_data(:, :) = 0.0_dp
     352              :             ELSE
     353            0 :                CALL cp_fm_finish_copy_general(fmlocal, info(kpoint_part_im))
     354              :             END IF
     355            0 :             CALL copy_fm_to_dbcsr_bc(fmlocal, mat_im)
     356              :          END IF
     357              :       END IF
     358          320 :       DO part = 1, MERGE(1, 2, kpoints%use_real_wfn)
     359          248 :          CALL cp_fm_cleanup_copy_general(info(part))
     360              :       END DO
     361           88 :    END SUBROUTINE kpoint_operator_receive
     362              : 
     363              : ! **************************************************************************************************
     364              : !> \brief Create an empty operator context.
     365              : !>        The context owns no buffers after this call; they are created on first
     366              : !>        use and released with the context.
     367              : !> \param ctx context to initialize
     368              : !> \param kpoints host k-point environment, referenced, not copied
     369              : !> \param ao_ao_fm any square full matrix on the environment-wide communicator;
     370              : !>        its matrix struct is the template for the context work buffers,
     371              : !>        which densify on the source side of transfers that cross into the
     372              : !>        k-point groups
     373              : !> \param nspin instance spin space of the owning driver call: the bound of
     374              : !>        the spin loop its start and finish calls iterate. Fixed for the
     375              : !>        lifetime of the context
     376              : ! **************************************************************************************************
     377        39910 :    SUBROUTINE kpoint_operator_context_create(ctx, kpoints, ao_ao_fm, nspin)
     378              : 
     379              :       TYPE(kpoint_operator_context_type), INTENT(OUT)    :: ctx
     380              :       TYPE(kpoint_type), POINTER                         :: kpoints
     381              :       TYPE(cp_fm_type), INTENT(IN)                       :: ao_ao_fm
     382              :       INTEGER, INTENT(IN)                                :: nspin
     383              : 
     384              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_context_create'
     385              : 
     386              :       INTEGER                                            :: handle
     387              : 
     388        39910 :       CALL timeset(routineN, handle)
     389              : 
     390        39910 :       CPASSERT(nspin >= 1)
     391        39910 :       ctx%nspin = nspin
     392        39910 :       CPASSERT(ASSOCIATED(kpoints))
     393        39910 :       ctx%kpoints => kpoints
     394        39910 :       CALL cp_fm_get_info(ao_ao_fm, matrix_struct=ctx%ao_ao_fmstruct)
     395        39910 :       CPASSERT(ASSOCIATED(ctx%ao_ao_fmstruct))
     396              :       ! The template must live on the transfer world. A template from
     397              :       ! another communicator fails during the transfers, far from this call.
     398        39910 :       IF (.NOT. ASSOCIATED(ctx%ao_ao_fmstruct%para_env, kpoints%blacs_env_all%para_env)) THEN
     399            0 :          CALL cp_abort(__LOCATION__, "Operator context requires the global parallel environment")
     400              :       END IF
     401              : 
     402        39910 :       CALL timestop(handle)
     403              : 
     404        39910 :    END SUBROUTINE kpoint_operator_context_create
     405              : 
     406              : ! **************************************************************************************************
     407              : !> \brief Release the context together with every buffer it created.
     408              : !>        Call once, at the exit of the routine that created the context.
     409              : !>        Every started instance must be idle at this point. The call aborts
     410              : !>        when a transfer is still in flight, because nothing would ever wait
     411              : !>        for its MPI requests.
     412              : !> \param ctx context to release
     413              : ! **************************************************************************************************
     414        39910 :    SUBROUTINE kpoint_operator_context_release(ctx)
     415              : 
     416              :       TYPE(kpoint_operator_context_type), INTENT(INOUT)  :: ctx
     417              : 
     418              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_context_release'
     419              : 
     420              :       INTEGER                                            :: handle, ispin, iwork, slot
     421              : 
     422        39910 :       CALL timeset(routineN, handle)
     423              : 
     424              :       ! Every started instance must have been finished: an unfinished start
     425              :       ! holds MPI requests that nothing would ever wait for.
     426        39910 :       IF (ALLOCATED(ctx%status)) THEN
     427       153964 :          CPASSERT(ALL(ctx%status == kpoint_slot_idle))
     428              :       END IF
     429              : 
     430        39910 :       IF (ASSOCIATED(ctx%rmatrix)) CALL dbcsr_deallocate_matrix(ctx%rmatrix)
     431        39910 :       IF (ASSOCIATED(ctx%cmatrix)) CALL dbcsr_deallocate_matrix(ctx%cmatrix)
     432        39910 :       NULLIFY (ctx%rmatrix, ctx%cmatrix, ctx%work_sab)
     433        39974 :       IF (ALLOCATED(ctx%copy_plan)) DEALLOCATE (ctx%copy_plan)
     434        39910 :       IF (ALLOCATED(ctx%rs)) THEN
     435       159608 :          DO slot = 1, kpoint_num_slots
     436       287660 :             DO ispin = 1, ctx%nspin
     437       247758 :                CALL kpoint_operator_rs_clear(ctx%rs(ispin, slot))
     438              :             END DO
     439              :          END DO
     440       296006 :          DEALLOCATE (ctx%rs, ctx%rs_batch)
     441              :       END IF
     442        39910 :       IF (ALLOCATED(ctx%rs_route)) THEN
     443        39890 :          CALL cp_fm_struct_release(ctx%rs_route%matrix_struct)
     444        39890 :          DEALLOCATE (ctx%rs_route)
     445              :       END IF
     446        39910 :       IF (ALLOCATED(ctx%fmwork)) THEN
     447           52 :          DO iwork = 1, SIZE(ctx%fmwork)
     448           52 :             CALL cp_fm_release(ctx%fmwork(iwork))
     449              :          END DO
     450           20 :          DEALLOCATE (ctx%fmwork)
     451              :       END IF
     452        39910 :       IF (ALLOCATED(ctx%fmimag)) THEN
     453           58 :          CALL cp_fm_release(ctx%fmimag)
     454           58 :          DEALLOCATE (ctx%fmimag)
     455              :       END IF
     456        39910 :       IF (ALLOCATED(ctx%fmlocal)) THEN
     457        39910 :          CALL fm_pool_give_back_fm(ctx%group_fm_pools(1)%pool, ctx%fmlocal)
     458        39910 :          DEALLOCATE (ctx%fmlocal)
     459              :       END IF
     460        40030 :       IF (ALLOCATED(ctx%info)) DEALLOCATE (ctx%info)
     461        39910 :       IF (ALLOCATED(ctx%status)) DEALLOCATE (ctx%status)
     462        39910 :       NULLIFY (ctx%kpoints, ctx%ao_ao_fmstruct, ctx%group_fm_pools)
     463              : 
     464        39910 :       CALL timestop(handle)
     465              : 
     466        39910 :    END SUBROUTINE kpoint_operator_context_release
     467              : 
     468              : ! **************************************************************************************************
     469              : !> \brief Create the context scratch that the first service call needs: the
     470              : !>        DBCSR work pair with blocks from the current neighbor lists, two
     471              : !>        densify buffers on the environment-wide struct, and the group
     472              : !>        transfer target from the group pools.
     473              : !>        The per-instance slot bookkeeping is not scratch: start creates it
     474              : !>        on its first call, because the synchronous get call never touches it.
     475              : !>        The buffers assume the context lifetime of one driver call: the
     476              : !>        host neighbor lists do not change within it, and every buffer use
     477              : !>        asserts this identity. Buffers from an earlier call stay in place.
     478              : !>        Called from the service routines only.
     479              : !> \param ctx context that receives the buffers
     480              : !> \param rsmat real-space image matrices of one operator. The call uses the
     481              : !>        first matrix as the DBCSR template of the work pair. It reads no
     482              : !>        matrix data.
     483              : ! **************************************************************************************************
     484           64 :    SUBROUTINE kpoint_operator_ensure(ctx, rsmat)
     485              : 
     486              :       TYPE(kpoint_operator_context_type), INTENT(INOUT)  :: ctx
     487              :       TYPE(dbcsr_p_type), DIMENSION(:, :), POINTER       :: rsmat
     488              : 
     489              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_ensure'
     490              : 
     491              :       INTEGER                                            :: handle
     492              : 
     493           64 :       CALL timeset(routineN, handle)
     494              : 
     495              :       ! The pair must still match the host neighbor lists: they are
     496              :       ! reallocated at every energy evaluation, while the context spans one
     497              :       ! driver call within it.
     498           64 :       IF (ASSOCIATED(ctx%work_sab)) THEN
     499           44 :          CPASSERT(ASSOCIATED(ctx%work_sab, ctx%kpoints%sab_nl))
     500              :       END IF
     501           64 :       IF (.NOT. ASSOCIATED(ctx%rmatrix)) THEN
     502           20 :          ALLOCATE (ctx%rmatrix)
     503           20 :          CALL dbcsr_create(ctx%rmatrix, template=rsmat(1, 1)%matrix, matrix_type=dbcsr_type_symmetric)
     504           20 :          CALL cp_dbcsr_alloc_block_from_nbl(ctx%rmatrix, ctx%kpoints%sab_nl)
     505           20 :          IF (.NOT. ctx%kpoints%use_real_wfn) THEN
     506           12 :             ALLOCATE (ctx%cmatrix)
     507           12 :             CALL dbcsr_create(ctx%cmatrix, template=rsmat(1, 1)%matrix, matrix_type=dbcsr_type_antisymmetric)
     508           12 :             CALL cp_dbcsr_alloc_block_from_nbl(ctx%cmatrix, ctx%kpoints%sab_nl)
     509              :          END IF
     510           20 :          ctx%work_sab => ctx%kpoints%sab_nl
     511              :       END IF
     512              : 
     513           64 :       IF (.NOT. ALLOCATED(ctx%fmwork)) THEN
     514           84 :          ALLOCATE (ctx%fmwork(MERGE(1, 2, ctx%kpoints%use_real_wfn)))
     515           92 :          ALLOCATE (ctx%copy_plan(SIZE(ctx%fmwork)))
     516              :          CALL cp_fm_create(ctx%fmwork(kpoint_part_re), ctx%ao_ao_fmstruct, &
     517           20 :                            name="KPOINT-OPERATOR-WORK-RE")
     518           20 :          IF (.NOT. ctx%kpoints%use_real_wfn) THEN
     519              :             CALL cp_fm_create(ctx%fmwork(kpoint_part_im), ctx%ao_ao_fmstruct, &
     520           12 :                               name="KPOINT-OPERATOR-WORK-IM")
     521              :          END IF
     522              :       END IF
     523              : 
     524           64 :       CALL kpoint_operator_ensure_group(ctx)
     525              : 
     526           64 :       CALL timestop(handle)
     527              : 
     528           64 :    END SUBROUTINE kpoint_operator_ensure
     529              : 
     530              : ! **************************************************************************************************
     531              : !> \brief Acquire only the group-local output workspace; both backends share it.
     532              : !> \param ctx operator context
     533              : ! **************************************************************************************************
     534        49280 :    SUBROUTINE kpoint_operator_ensure_group(ctx)
     535              : 
     536              :       TYPE(kpoint_operator_context_type), INTENT(INOUT)  :: ctx
     537              : 
     538        49280 :       IF (.NOT. ALLOCATED(ctx%fmlocal)) THEN
     539        39910 :          CALL mpools_get(ctx%kpoints%mpools, ao_ao_fm_pools=ctx%group_fm_pools)
     540        39910 :          ALLOCATE (ctx%fmlocal)
     541        39910 :          CALL fm_pool_create_fm(ctx%group_fm_pools(1)%pool, ctx%fmlocal)
     542              :       END IF
     543              : 
     544        49280 :    END SUBROUTINE kpoint_operator_ensure_group
     545              : 
     546              : ! **************************************************************************************************
     547              : !> \brief Prepare an immutable operator slot for group-local assembly.
     548              : !>        Refresh after the source values change, with every instance idle.
     549              : !>        Other prepared slots and structural routing remain valid. Without
     550              : !>        matrix_row, capture one row per instance spin; with matrix_row, store
     551              : !>        one row shared by all instance spins. Failure clears this slot on
     552              : !>        all ranks and retains the existing transfer fallback.
     553              : !> \param ctx context spanning the current initialized k-point environment
     554              : !> \param rsmat current real-space operator images
     555              : !> \param slot slot to replace (KS, S or T)
     556              : !> \param prepared optional collective success flag
     557              : !> \param matrix_row optional explicit spin-free operator row
     558              : !> \param TARGET optional group-local FM layout for prepared get outputs;
     559              : !>        otherwise use the host AO/AO pool layout. One layout per context
     560              : ! **************************************************************************************************
     561        47216 :    SUBROUTINE kpoint_operator_prepare(ctx, rsmat, slot, prepared, matrix_row, TARGET)
     562              : 
     563              :       TYPE(kpoint_operator_context_type), INTENT(INOUT)  :: ctx
     564              :       TYPE(dbcsr_p_type), POINTER                        :: rsmat(:, :)
     565              :       INTEGER, INTENT(IN)                                :: slot
     566              :       LOGICAL, INTENT(OUT), OPTIONAL                     :: prepared
     567              :       INTEGER, INTENT(IN), OPTIONAL                      :: matrix_row
     568              :       TYPE(cp_fm_type), INTENT(IN), OPTIONAL             :: target
     569              : 
     570              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_prepare'
     571              :       INTEGER(KIND=int_8), PARAMETER :: max_bytes = 64_int_8*1024_int_8**2
     572              : 
     573              :       INTEGER                                            :: compatible, handle, ispin, jslot, nrow, &
     574              :                                                             row
     575              :       INTEGER(KIND=int_8)                                :: retained, storage
     576              :       LOGICAL                                            :: ready
     577              :       TYPE(cp_fm_struct_type), POINTER                   :: fms
     578              :       TYPE(mp_para_env_type), POINTER                    :: para_env
     579              : 
     580        47216 :       CALL timeset(routineN, handle)
     581        47216 :       IF (PRESENT(prepared)) prepared = .FALSE.
     582        47216 :       CPASSERT(ASSOCIATED(ctx%kpoints))
     583        47216 :       CPASSERT(slot >= kpoint_slot_ks .AND. slot <= kpoint_slot_t)
     584        47216 :       CPASSERT(ASSOCIATED(ctx%kpoints%sab_nl))
     585        47216 :       IF (ASSOCIATED(ctx%work_sab)) THEN
     586         7304 :          CPASSERT(ASSOCIATED(ctx%work_sab, ctx%kpoints%sab_nl))
     587              :       END IF
     588        47216 :       IF (ALLOCATED(ctx%status)) THEN
     589            0 :          CPASSERT(ALL(ctx%status == kpoint_slot_idle))
     590              :       END IF
     591        47216 :       IF (.NOT. ALLOCATED(ctx%rs)) THEN
     592       695026 :          ALLOCATE (ctx%rs(ctx%nspin, kpoint_num_slots), ctx%rs_batch(ctx%nspin, kpoint_num_slots))
     593              :       END IF
     594        98250 :       DO ispin = 1, ctx%nspin
     595        51034 :          CALL kpoint_operator_rs_clear(ctx%rs(ispin, slot))
     596        51034 :          IF (ALLOCATED(ctx%rs_batch(ispin, slot)%data)) DEALLOCATE (ctx%rs_batch(ispin, slot)%data)
     597        98250 :          ctx%rs_batch(ispin, slot)%first = 0
     598              :       END DO
     599        47216 :       nrow = ctx%nspin
     600        47216 :       IF (PRESENT(matrix_row)) THEN
     601         8596 :          CPASSERT(matrix_row >= 1 .AND. matrix_row <= SIZE(rsmat, 1))
     602              :          nrow = 1
     603              :       ELSE
     604        38620 :          CPASSERT(SIZE(rsmat, 1) == ctx%nspin)
     605              :       END IF
     606        47216 :       para_env => ctx%kpoints%blacs_env_all%para_env
     607        47216 :       CALL kpoint_operator_ensure_group(ctx)
     608        47216 :       fms => ctx%fmlocal%matrix_struct
     609        47216 :       IF (PRESENT(TARGET)) fms => target%matrix_struct
     610        47216 :       CPASSERT(ASSOCIATED(fms))
     611              :       ! Routing uses the host group-rank numbering. Different BLACS layouts
     612              :       ! on that communicator are supported, foreign communicators are not.
     613        47216 :       compatible = MERGE(1, 0, ASSOCIATED(fms%para_env, ctx%kpoints%para_env_kp))
     614        47216 :       CALL para_env%min(compatible)
     615        47216 :       IF (compatible == 0) THEN
     616            0 :          CALL timestop(handle)
     617           22 :          RETURN
     618              :       END IF
     619        47216 :       IF (.NOT. ALLOCATED(ctx%rs_route)) THEN
     620        39912 :          ALLOCATE (ctx%rs_route)
     621        39912 :          CALL kpoint_operator_rs_plan_create(ctx%kpoints, rsmat, fms, max_bytes, ctx%rs_route, ready)
     622        39912 :          IF (.NOT. ready) THEN
     623           22 :             DEALLOCATE (ctx%rs_route)
     624           22 :             CALL timestop(handle)
     625           22 :             RETURN
     626              :          END IF
     627        39890 :          ctx%work_sab => ctx%kpoints%sab_nl
     628              :       END IF
     629        47194 :       compatible = MERGE(1, 0, cp_fm_struct_equivalent(fms, ctx%rs_route%matrix_struct))
     630        97182 :       DO ispin = 1, nrow
     631        49988 :          row = ispin
     632        49988 :          IF (PRESENT(matrix_row)) row = matrix_row
     633        97182 :          IF (.NOT. kpoint_operator_rs_compatible(ctx%rs_route, rsmat, row)) compatible = 0
     634              :       END DO
     635        47194 :       CALL para_env%min(compatible)
     636              :       ! Keep the single-k phase allowance in the snapshot budget for the uncached fallback.
     637        47194 :       retained = 0_int_8
     638       188776 :       DO jslot = kpoint_slot_ks, kpoint_slot_t
     639       341812 :          DO ispin = 1, ctx%nspin
     640       153036 :             IF (.NOT. ALLOCATED(ctx%rs(ispin, jslot)%blocks)) CYCLE
     641         8562 :             retained = retained + 128_int_8*SIZE(ctx%rs(ispin, jslot)%blocks, KIND=int_8)
     642       150144 :             IF (ASSOCIATED(ctx%rs(ispin, jslot)%values)) THEN
     643         8562 :                retained = retained + 8_int_8*SIZE(ctx%rs(ispin, jslot)%values, KIND=int_8)
     644              :             END IF
     645              :          END DO
     646              :       END DO
     647              :       storage = ctx%rs_route%storage + retained + INT(nrow, int_8)* &
     648              :                 (MERGE(0_int_8, 8_int_8*ctx%rs_route%nvalue, para_env%num_pe == 1) + &
     649        47194 :                  128_int_8*SIZE(ctx%rs_route%entry, 2))
     650        47194 :       CALL para_env%max(storage)
     651        47194 :       ready = compatible == 1 .AND. storage <= max_bytes
     652        47194 :       IF (ready) THEN
     653        97182 :          DO ispin = 1, nrow
     654        49988 :             row = ispin
     655        49988 :             IF (PRESENT(matrix_row)) row = matrix_row
     656        97182 :             CALL kpoint_operator_rs_capture(rsmat, row, ctx%rs_route, ctx%kpoints, ctx%rs(ispin, slot))
     657              :          END DO
     658              :       END IF
     659        47194 :       IF (PRESENT(prepared)) prepared = ready
     660        47194 :       CALL timestop(handle)
     661              : 
     662              :    END SUBROUTINE kpoint_operator_prepare
     663              : 
     664              : ! **************************************************************************************************
     665              : !> \brief Release one captured row without invalidating other slots or routing.
     666              : !> \param snapshot row to clear
     667              : ! **************************************************************************************************
     668       179086 :    SUBROUTINE kpoint_operator_rs_clear(snapshot)
     669              : 
     670              :       TYPE(kpoint_operator_rs_type), INTENT(INOUT)       :: snapshot
     671              : 
     672       179086 :       IF (ALLOCATED(snapshot%blocks)) DEALLOCATE (snapshot%blocks, snapshot%nonzero)
     673       179086 :       IF (ASSOCIATED(snapshot%values)) DEALLOCATE (snapshot%values)
     674       179086 :       NULLIFY (snapshot%source)
     675       179086 :       snapshot%matrix_row = 0
     676              : 
     677       179086 :    END SUBROUTINE kpoint_operator_rs_clear
     678              : 
     679              : ! **************************************************************************************************
     680              : !> \brief Match an explicitly prepared row, sharing spin-free rows across spins.
     681              : !> \param ctx operator context
     682              : !> \param source live array identity identifying the prepared operation
     683              : !> \param ispin instance spin
     684              : !> \param slot prepared slot
     685              : !> \param matrix_row operator row
     686              : !> \return snapshot spin index, or zero for the transfer backend
     687              : ! **************************************************************************************************
     688        34624 :    FUNCTION kpoint_operator_rs_match(ctx, source, ispin, slot, matrix_row) RESULT(irs)
     689              : 
     690              :       TYPE(kpoint_operator_context_type), INTENT(IN)     :: ctx
     691              :       TYPE(dbcsr_p_type), POINTER                        :: source(:, :)
     692              :       INTEGER, INTENT(IN)                                :: ispin, slot, matrix_row
     693              :       INTEGER                                            :: irs
     694              : 
     695              :       INTEGER                                            :: i
     696              : 
     697        34624 :       irs = 0
     698        34624 :       IF (.NOT. ALLOCATED(ctx%rs)) RETURN
     699        34624 :       i = ispin
     700        34624 :       IF (.NOT. ASSOCIATED(ctx%rs(i, slot)%source)) i = 1
     701        34624 :       IF (ASSOCIATED(ctx%rs(i, slot)%source, source) .AND. ctx%rs(i, slot)%matrix_row == matrix_row) irs = i
     702              : 
     703              :    END FUNCTION kpoint_operator_rs_match
     704              : 
     705              : ! **************************************************************************************************
     706              : !> \brief Cache whole-block routing to the FM tiles of one representative group.
     707              : !>        Equivalent ranks in the other groups receive only their tile's blocks.
     708              : !>        Boundary blocks may overlap several tiles. Missing blocks are packed
     709              : !>        as zero, permitting different operator sparsities on a fixed AO layout.
     710              : !> \param kpoints initialized parallel environment and neighbor lists
     711              : !> \param source operator layout template
     712              : !> \param matrix_struct target group layout
     713              : !> \param max_bytes per-rank budget for routing and capture buffers
     714              : !> \param route context-owned structural route
     715              : !> \param ready false collectively if unsupported or over budget
     716              : ! **************************************************************************************************
     717        39912 :    SUBROUTINE kpoint_operator_rs_plan_create(kpoints, source, matrix_struct, max_bytes, route, ready)
     718              : 
     719              :       TYPE(kpoint_type), POINTER                         :: kpoints
     720              :       TYPE(dbcsr_p_type), POINTER                        :: source(:, :)
     721              :       TYPE(cp_fm_struct_type), POINTER                   :: matrix_struct
     722              :       INTEGER(KIND=int_8), INTENT(IN)                    :: max_bytes
     723              :       TYPE(kpoint_operator_rs_route_type), INTENT(OUT)   :: route
     724              :       LOGICAL, INTENT(OUT)                               :: ready
     725              : 
     726              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_rs_plan_create'
     727              : 
     728              :       INTEGER                                            :: compatible, dest, handle, i, iblock, &
     729              :                                                             nblock, ncol, nrow, p, pos, rank
     730              :       INTEGER(KIND=int_8)                                :: nr(2), ns(2), storage
     731        39912 :       INTEGER(KIND=int_8), ALLOCATABLE                   :: recv_size(:, :), send_size(:, :)
     732        39912 :       INTEGER, ALLOCATABLE                               :: identities(:, :), owner(:), &
     733        39912 :                                                             position(:, :), send_metadata(:, :)
     734        39912 :       INTEGER, POINTER                                   :: col_size(:), row_size(:)
     735        39912 :       LOGICAL, ALLOCATABLE                               :: needed(:)
     736              :       TYPE(mp_para_env_type), POINTER                    :: inter, world
     737              : 
     738        39912 :       CALL timeset(routineN, handle)
     739        39912 :       ready = .FALSE.
     740        39912 :       world => kpoints%blacs_env_all%para_env
     741        39912 :       inter => kpoints%para_env_inter_kp
     742        39912 :       CALL dbcsr_get_info(source(1, 1)%matrix, row_blk_size=row_size, col_blk_size=col_size)
     743              :       CALL kp_transform_plan_create(route%traversal, kpoints%sab_nl, kpoints%cell_to_index, &
     744        39912 :                                     SIZE(source, 2), block_template=source(1, 1)%matrix)
     745        39912 :       compatible = MERGE(1, 0, route%traversal%symmetric)
     746       373504 :       IF (SUM(row_size) /= matrix_struct%nrow_global .OR. &
     747            0 :           SUM(col_size) /= matrix_struct%ncol_global) compatible = 0
     748        39912 :       IF (SIZE(row_size) == SIZE(col_size)) THEN
     749       206708 :          IF (ANY(row_size /= col_size)) compatible = 0
     750              :       ELSE
     751            0 :          compatible = 0
     752              :       END IF
     753        39912 :       CALL world%min(compatible)
     754        39912 :       IF (compatible == 0) THEN
     755            0 :          CALL timestop(handle)
     756            0 :          RETURN
     757              :       END IF
     758        39912 :       route%nimage = SIZE(source, 2)
     759       199560 :       ALLOCATE (route%row_size(SIZE(row_size)), route%col_size(SIZE(col_size)))
     760       206708 :       route%row_size(:) = row_size
     761       206708 :       route%col_size(:) = col_size
     762              :       ALLOCATE (identities(2, 0:world%num_pe - 1), owner(0:kpoints%para_env_kp%num_pe - 1), &
     763       239472 :                 needed(0:kpoints%para_env_kp%num_pe - 1))
     764       119736 :       CALL world%allgather([kpoints%para_env_kp%mepos, inter%mepos], identities)
     765        90932 :       owner(:) = -1
     766       119736 :       DO rank = 0, world%num_pe - 1
     767       119736 :          IF (identities(2, rank) == 0) owner(identities(1, rank)) = rank
     768              :       END DO
     769        90932 :       CPASSERT(ALL(owner >= 0))
     770       159648 :       ALLOCATE (send_size(2, 0:world%num_pe - 1), recv_size(2, 0:world%num_pe - 1))
     771        39912 :       send_size(:, :) = 0_int_8
     772      9039650 :       DO i = 1, route%traversal%nentry
     773      8999738 :          nrow = row_size(route%traversal%row(i))
     774      8999738 :          ncol = col_size(route%traversal%col(i))
     775      8999738 :          CALL kpoint_operator_rs_owners(route, matrix_struct, i, needed)
     776     21982963 :          DO p = 0, SIZE(owner) - 1
     777     12943313 :             IF (.NOT. needed(p)) CYCLE
     778     10835687 :             dest = owner(p)
     779     10835687 :             send_size(1, dest) = send_size(1, dest) + 1_int_8
     780     21943051 :             send_size(2, dest) = send_size(2, dest) + INT(nrow, int_8)*ncol
     781              :          END DO
     782              :       END DO
     783        39912 :       CALL world%alltoall(send_size, recv_size, 2)
     784              :       ! Only inter-rank zero receives from the world. The other groups hold
     785              :       ! equivalent FM tiles, as required by kpoint_env_initialize.
     786       279384 :       ns = SUM(send_size, DIM=2)
     787       279384 :       nr = SUM(recv_size, DIM=2)
     788        39912 :       CALL inter%bcast(nr, 0)
     789              :       ! Conservative peak: traversal, retained route, temporary metadata and
     790              :       ! one snapshot. The full batch budget is checked again by prepare.
     791              :       storage = 40_int_8*route%traversal%nentry + &
     792              :                 4_int_8*(SIZE(row_size) + SIZE(col_size) + matrix_struct%nrow_global + matrix_struct%ncol_global) + &
     793              :                 48_int_8*ns(1) + MERGE(0_int_8, 8_int_8*ns(2), world%num_pe == 1) + 68_int_8*nr(1) + &
     794        39912 :                 128_int_8*world%num_pe + 8_int_8*SIZE(owner) + 8_int_8
     795        39912 :       route%storage = storage
     796        79824 :       storage = storage + MERGE(0_int_8, 8_int_8*nr(2), world%num_pe == 1) + 128_int_8*nr(1)
     797              :       ! Borrowed values do not consume the snapshot budget; still bound integer route offsets.
     798       199560 :       IF (MAXVAL(ns) >= INT(HUGE(0), int_8) .OR. MAXVAL(nr) >= INT(HUGE(0), int_8)) THEN
     799            0 :          storage = MAX(storage, max_bytes + 1_int_8)
     800              :       END IF
     801        39912 :       CALL world%max(storage)
     802        39912 :       IF (storage > max_bytes) THEN
     803           22 :          CALL timestop(handle)
     804           22 :          RETURN
     805              :       END IF
     806              :       ! The byte bound also keeps all counts/displacements within default INTEGER.
     807        39890 :       nblock = INT(nr(1))
     808        39890 :       route%nvalue = INT(nr(2))
     809            0 :       ALLOCATE (route%scount(2, 0:world%num_pe - 1), route%rcount(2, 0:world%num_pe - 1), &
     810       239340 :                 route%sdisp(2, 0:world%num_pe - 1), route%rdisp(2, 0:world%num_pe - 1))
     811       279230 :       route%scount(:, :) = INT(send_size)
     812       279230 :       route%rcount(:, :) = INT(recv_size)
     813        39890 :       IF (SIZE(owner) == 1) CALL inter%bcast(route%rcount, 0)
     814       119670 :       route%sdisp(:, 0) = 0
     815       119670 :       route%rdisp(:, 0) = 0
     816        79780 :       DO rank = 1, world%num_pe - 1
     817       119670 :          route%sdisp(:, rank) = route%sdisp(:, rank - 1) + route%scount(:, rank - 1)
     818       159560 :          route%rdisp(:, rank) = route%rdisp(:, rank - 1) + route%rcount(:, rank - 1)
     819              :       END DO
     820            0 :       ALLOCATE (route%send_entry(INT(ns(1))), route%send_first(INT(ns(1)) + 1), &
     821            0 :                 route%send_values(MERGE(0, INT(ns(2)), world%num_pe == 1)), send_metadata(8, INT(ns(1))), &
     822            0 :                 position(2, 0:world%num_pe - 1), route%entry(8, nblock), &
     823       635390 :                 route%first(nblock + 1))
     824       279230 :       position(:, :) = route%sdisp
     825      8919508 :       DO i = 1, route%traversal%nentry
     826      8879618 :          nrow = row_size(route%traversal%row(i))
     827      8879618 :          ncol = col_size(route%traversal%col(i))
     828      8879618 :          CALL kpoint_operator_rs_owners(route, matrix_struct, i, needed)
     829     21622581 :          DO p = 0, SIZE(owner) - 1
     830     12703073 :             IF (.NOT. needed(p)) CYCLE
     831     10638952 :             dest = owner(p)
     832     10638952 :             iblock = position(1, dest) + 1
     833     10638952 :             pos = position(2, dest) + 1
     834     10638952 :             route%send_entry(iblock) = i
     835     10638952 :             route%send_first(iblock) = pos
     836     53194760 :             send_metadata(1:4, iblock) = [route%traversal%row_offset(i), route%traversal%col_offset(i), nrow, ncol]
     837     42555808 :             send_metadata(5:7, iblock) = NINT(route%traversal%symmetry_sign(i))*route%traversal%cell(:, i)
     838     10638952 :             send_metadata(8, iblock) = MERGE(1, 0, route%traversal%row(i) /= route%traversal%col(i))
     839     42860595 :             position(:, dest) = position(:, dest) + [1, nrow*ncol]
     840              :          END DO
     841              :       END DO
     842        39890 :       route%send_first(INT(ns(1)) + 1) = INT(ns(2)) + 1
     843        39890 :       IF (SIZE(owner) == 1) THEN
     844       144020 :          CALL world%allgatherv(send_metadata, route%entry, 8*route%rcount(1, :), 8*route%rdisp(1, :))
     845              :       ELSE
     846              :          CALL world%alltoall(send_metadata, 8*route%scount(1, :), 8*route%sdisp(1, :), &
     847        99774 :                              route%entry, 8*route%rcount(1, :), 8*route%rdisp(1, :))
     848        11086 :          CALL inter%bcast(route%entry, 0)
     849              :       END IF
     850        39890 :       route%first(1) = 1
     851     15735005 :       DO i = 1, nblock
     852     15735005 :          route%first(i + 1) = route%first(i) + route%entry(3, i)*route%entry(4, i)
     853              :       END DO
     854        39890 :       CPASSERT(route%first(nblock + 1) == route%nvalue + 1)
     855        39890 :       route%matrix_struct => matrix_struct
     856        39890 :       CALL cp_fm_struct_retain(route%matrix_struct)
     857        39890 :       CALL kpoint_operator_rs_tile_map(route)
     858        39890 :       ready = .TRUE.
     859        39890 :       CALL timestop(handle)
     860              : 
     861       119736 :    END SUBROUTINE kpoint_operator_rs_plan_create
     862              : 
     863              : ! **************************************************************************************************
     864              : !> \brief Cache local intervals intersecting each routed block and its Hermitian mirror.
     865              : !>        FM indices are ordered globally; dummy indices on empty tiles lie outside the matrix.
     866              : !> \param route fixed routing and target distribution
     867              : ! **************************************************************************************************
     868        39890 :    SUBROUTINE kpoint_operator_rs_tile_map(route)
     869              :       TYPE(kpoint_operator_rs_route_type), INTENT(INOUT) :: route
     870              : 
     871        79780 :       INTEGER :: c0, i, nc, nr, r0, rows(0:route%matrix_struct%nrow_global), &
     872        39890 :          cols(0:route%matrix_struct%ncol_global)
     873              : 
     874      1028052 :       rows(:) = 0
     875      1028052 :       cols(:) = 0
     876       879401 :       DO i = 1, SIZE(route%matrix_struct%row_indices)
     877       839511 :          r0 = route%matrix_struct%row_indices(i)
     878       879401 :          IF (r0 >= 1 .AND. r0 <= UBOUND(rows, 1)) rows(r0) = 1
     879              :       END DO
     880       988162 :       DO i = 1, SIZE(route%matrix_struct%col_indices)
     881       948272 :          c0 = route%matrix_struct%col_indices(i)
     882       988162 :          IF (c0 >= 1 .AND. c0 <= UBOUND(cols, 1)) cols(c0) = 1
     883              :       END DO
     884       988162 :       DO i = 1, UBOUND(rows, 1)
     885       988162 :          rows(i) = rows(i) + rows(i - 1)
     886              :       END DO
     887       988162 :       DO i = 1, UBOUND(cols, 1)
     888       988162 :          cols(i) = cols(i) + cols(i - 1)
     889              :       END DO
     890        39890 :       route%nrow = rows(UBOUND(rows, 1))
     891        39890 :       route%ncol = cols(UBOUND(cols, 1))
     892       119670 :       ALLOCATE (route%tile(4, 2, SIZE(route%entry, 2)))
     893     15735005 :       DO i = 1, SIZE(route%entry, 2)
     894     15695115 :          r0 = route%entry(1, i)
     895     15695115 :          c0 = route%entry(2, i)
     896     15695115 :          nr = route%entry(3, i)
     897     15695115 :          nc = route%entry(4, i)
     898     78475575 :          route%tile(:, 1, i) = [rows(r0 - 1) + 1, rows(r0 + nr - 1), cols(c0 - 1) + 1, cols(c0 + nc - 1)]
     899     78515465 :          route%tile(:, 2, i) = [rows(c0 - 1) + 1, rows(c0 + nc - 1), cols(r0 - 1) + 1, cols(r0 + nr - 1)]
     900              :       END DO
     901        39890 :    END SUBROUTINE kpoint_operator_rs_tile_map
     902              : 
     903              : ! **************************************************************************************************
     904              : !> \brief Find group-local FM owners intersecting a stored block or its mirror.
     905              : !> \param route source AO block layout and neighbor traversal
     906              : !> \param fms target FM layout within each group
     907              : !> \param i traversal entry
     908              : !> \param needed recipient group-local ranks, without duplicate mirror sends
     909              : ! **************************************************************************************************
     910     17879356 :    SUBROUTINE kpoint_operator_rs_owners(route, fms, i, needed)
     911              : 
     912              :       TYPE(kpoint_operator_rs_route_type), INTENT(IN)    :: route
     913              :       TYPE(cp_fm_struct_type), INTENT(IN)                :: fms
     914              :       INTEGER, INTENT(IN)                                :: i
     915              :       LOGICAL, INTENT(OUT)                               :: needed(0:)
     916              : 
     917              :       INTEGER                                            :: c0, j, nc, nr, pc, pr, r0
     918     35758712 :       LOGICAL :: rows(2, 0:fms%context%num_pe(1) - 1), cols(2, 0:fms%context%num_pe(2) - 1)
     919              : 
     920     17879356 :       r0 = route%traversal%row_offset(i)
     921     17879356 :       c0 = route%traversal%col_offset(i)
     922     17879356 :       nr = route%row_size(route%traversal%row(i))
     923     17879356 :       nc = route%col_size(route%traversal%col(i))
     924     94818514 :       rows(:, :) = .FALSE.
     925     71517424 :       cols(:, :) = .FALSE.
     926    134239314 :       DO j = r0, r0 + nr - 1
     927    116359958 :          rows(1, fms%g2p_row(j)) = .TRUE.
     928    134239314 :          cols(2, fms%g2p_col(j)) = .TRUE.
     929              :       END DO
     930    130343882 :       DO j = c0, c0 + nc - 1
     931    112464526 :          rows(2, fms%g2p_row(j)) = .TRUE.
     932    130343882 :          cols(1, fms%g2p_col(j)) = .TRUE.
     933              :       END DO
     934     29976574 :       IF (route%traversal%row(i) == route%traversal%col(i)) rows(2, :) = .FALSE.
     935     43525742 :       needed(:) = .FALSE.
     936     35758712 :       DO pc = 0, fms%context%num_pe(2) - 1
     937     61405098 :          DO pr = 0, fms%context%num_pe(1) - 1
     938              :             needed(fms%context%blacs2mpi(pr, pc)) = &
     939     47697489 :                (rows(1, pr) .AND. cols(1, pc)) .OR. (rows(2, pr) .AND. cols(2, pc))
     940              :          END DO
     941              :       END DO
     942              : 
     943     17879356 :    END SUBROUTINE kpoint_operator_rs_owners
     944              : 
     945              : ! **************************************************************************************************
     946              : !> \brief Check the source AO layout, not its values or sparse block presence.
     947              : !> \param route fixed context route
     948              : !> \param source current operator images
     949              : !> \param matrix_row row to capture
     950              : !> \return whether the row has the route's image count and AO layout
     951              : ! **************************************************************************************************
     952        49988 :    FUNCTION kpoint_operator_rs_compatible(route, source, matrix_row) RESULT(compatible)
     953              : 
     954              :       TYPE(kpoint_operator_rs_route_type), INTENT(IN)    :: route
     955              :       TYPE(dbcsr_p_type), POINTER                        :: source(:, :)
     956              :       INTEGER, INTENT(IN)                                :: matrix_row
     957              :       LOGICAL                                            :: compatible
     958              : 
     959              :       INTEGER                                            :: image
     960        49988 :       INTEGER, POINTER                                   :: col_size(:), row_size(:)
     961              : 
     962        49988 :       compatible = .FALSE.
     963        49988 :       IF (SIZE(source, 2) /= route%nimage) RETURN
     964        49988 :       IF (matrix_row < 1 .OR. matrix_row > SIZE(source, 1)) RETURN
     965      3013040 :       DO image = 1, SIZE(source, 2)
     966      2963052 :          CALL dbcsr_get_info(source(matrix_row, image)%matrix, row_blk_size=row_size, col_blk_size=col_size)
     967      2963052 :          IF (SIZE(row_size) /= SIZE(route%row_size) .OR. SIZE(col_size) /= SIZE(route%col_size)) RETURN
     968     29446292 :          IF (ANY(row_size /= route%row_size) .OR. ANY(col_size /= route%col_size)) RETURN
     969              :       END DO
     970        49988 :       compatible = .TRUE.
     971              : 
     972        49988 :    END FUNCTION kpoint_operator_rs_compatible
     973              : 
     974              : ! **************************************************************************************************
     975              : !> \brief Capture current values using cached routing, with no per-k communication.
     976              : !> \param source current real-space operator images
     977              : !> \param matrix_row row to snapshot
     978              : !> \param route context-owned metadata, counts and reusable packing buffer
     979              : !> \param kpoints initialized host parallel environment
     980              : !> \param snapshot immutable tile-local values
     981              : ! **************************************************************************************************
     982        49988 :    SUBROUTINE kpoint_operator_rs_capture(source, matrix_row, route, kpoints, snapshot)
     983              : 
     984              :       TYPE(dbcsr_p_type), POINTER                        :: source(:, :)
     985              :       INTEGER, INTENT(IN)                                :: matrix_row
     986              :       TYPE(kpoint_operator_rs_route_type), INTENT(INOUT) :: route
     987              :       TYPE(kpoint_type), POINTER                         :: kpoints
     988              :       TYPE(kpoint_operator_rs_type), INTENT(OUT)         :: snapshot
     989              : 
     990              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_rs_capture'
     991              : 
     992              :       INTEGER                                            :: col, handle, i, iblock, ncol, nrow, pos, &
     993              :                                                             row
     994              :       LOGICAL                                            :: found
     995        49988 :       REAL(KIND=dp), POINTER                             :: block(:, :)
     996              :       TYPE(mp_para_env_type), POINTER                    :: inter, world
     997              : 
     998        49988 :       CALL timeset(routineN, handle)
     999        49988 :       world => kpoints%blacs_env_all%para_env
    1000        49988 :       inter => kpoints%para_env_inter_kp
    1001     20948379 :       ALLOCATE (snapshot%blocks(SIZE(route%entry, 2)), snapshot%nonzero(SIZE(route%entry, 2)))
    1002        49988 :       snapshot%source => source
    1003        49988 :       snapshot%matrix_row = matrix_row
    1004        49988 :       IF (world%num_pe == 1) THEN
    1005              :          ! No value copy on a single world rank. Lookups happen before workers start.
    1006            0 :          DO iblock = 1, SIZE(route%send_entry)
    1007            0 :             i = route%send_entry(iblock)
    1008              :             CALL dbcsr_get_readonly_block_p(source(matrix_row, route%traversal%image(i))%matrix, &
    1009            0 :                                             route%traversal%row(i), route%traversal%col(i), block, found)
    1010            0 :             snapshot%nonzero(iblock) = found
    1011            0 :             IF (found) THEN
    1012            0 :                CPASSERT(SIZE(block, 1) == route%entry(3, iblock))
    1013            0 :                CPASSERT(SIZE(block, 2) == route%entry(4, iblock))
    1014            0 :                snapshot%blocks(iblock)%data => block
    1015              :             END IF
    1016              :          END DO
    1017            0 :          CALL timestop(handle)
    1018            0 :          RETURN
    1019              :       END IF
    1020       149964 :       ALLOCATE (snapshot%values(route%nvalue))
    1021    798662097 :       route%send_values(:) = 0.0_dp
    1022              : !$OMP PARALLEL DO DEFAULT(NONE) SHARED(source, matrix_row, route) &
    1023        49988 : !$OMP PRIVATE(iblock, i, block, found, nrow, ncol, pos, row, col) SCHEDULE(STATIC)
    1024              :       DO iblock = 1, SIZE(route%send_entry)
    1025              :          i = route%send_entry(iblock)
    1026              :          CALL dbcsr_get_readonly_block_p(source(matrix_row, route%traversal%image(i))%matrix, &
    1027              :                                          route%traversal%row(i), route%traversal%col(i), block, found)
    1028              :          IF (.NOT. found) CYCLE
    1029              :          nrow = route%row_size(route%traversal%row(i))
    1030              :          ncol = route%col_size(route%traversal%col(i))
    1031              :          CPASSERT(SIZE(block, 1) == nrow .AND. SIZE(block, 2) == ncol)
    1032              :          pos = route%send_first(iblock)
    1033              :          DO col = 1, ncol
    1034              :             DO row = 1, nrow
    1035              :                route%send_values(pos) = block(row, col)
    1036              :                pos = pos + 1
    1037              :             END DO
    1038              :          END DO
    1039              :       END DO
    1040              : !$OMP END PARALLEL DO
    1041        49988 :       IF (kpoints%para_env_kp%num_pe == 1) THEN
    1042       176390 :          CALL world%allgatherv(route%send_values, snapshot%values, route%rcount(2, :), route%rdisp(2, :))
    1043              :       ELSE
    1044              :          CALL world%alltoall(route%send_values, route%scount(2, :), route%sdisp(2, :), &
    1045       132390 :                              snapshot%values, route%rcount(2, :), route%rdisp(2, :))
    1046        14710 :          CALL inter%bcast(snapshot%values, 0)
    1047              :       END IF
    1048     20748427 :       DO iblock = 1, SIZE(route%entry, 2)
    1049     98887875 :          snapshot%nonzero(iblock) = ANY(snapshot%values(route%first(iblock):route%first(iblock + 1) - 1) /= 0.0_dp)
    1050              :          snapshot%blocks(iblock)%data(1:route%entry(3, iblock), 1:route%entry(4, iblock)) => &
    1051     20748427 :             snapshot%values(route%first(iblock):route%first(iblock + 1) - 1)
    1052              :       END DO
    1053        49988 :       snapshot%source => source
    1054        49988 :       snapshot%matrix_row = matrix_row
    1055        49988 :       CALL timestop(handle)
    1056              : 
    1057        99976 :    END SUBROUTINE kpoint_operator_rs_capture
    1058              : 
    1059              : ! **************************************************************************************************
    1060              : !> \brief Deliver one prepared operator, building a bounded window of local k points on a miss.
    1061              : !>        The callers retain their round/spin/slot ordering. Each row has its own
    1062              : !>        window so interleaved spins and H/S/T do not evict one another.
    1063              : !>        Only prepared values are read ahead; transfer and grid paths are unchanged.
    1064              : !> \param ctx serialized operator context
    1065              : !> \param irs captured row, possibly shared by spin-free instances
    1066              : !> \param slot prepared operator slot
    1067              : !> \param ik global k-point index owned by this group
    1068              : !> \param TARGET optional complex output on the prepared distribution
    1069              : !> \param fm_re optional real output instead of target; fm_im may be omitted for a real operator
    1070              : !> \param fm_im optional imaginary output on the same distribution
    1071              : ! **************************************************************************************************
    1072        34582 :    SUBROUTINE kpoint_operator_rs_build(ctx, irs, slot, ik, TARGET, fm_re, fm_im)
    1073              : 
    1074              :       TYPE(kpoint_operator_context_type), INTENT(INOUT)  :: ctx
    1075              :       INTEGER, INTENT(IN)                                :: irs, slot, ik
    1076              :       TYPE(cp_cfm_type), INTENT(INOUT), OPTIONAL         :: target
    1077              :       TYPE(cp_fm_type), INTENT(INOUT), OPTIONAL          :: fm_re, fm_im
    1078              : 
    1079              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_rs_build'
    1080              : 
    1081              :       INTEGER                                            :: first, handle, k, last, ncol, nrow, width
    1082              :       INTEGER(KIND=int_8)                                :: bytes_per_k
    1083              :       LOGICAL                                            :: layout_ok
    1084        34582 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: xkp
    1085              : 
    1086        34582 :       CALL timeset(routineN, handle)
    1087        34582 :       IF (PRESENT(TARGET)) THEN
    1088        33008 :          layout_ok = cp_fm_struct_equivalent(target%matrix_struct, ctx%rs_route%matrix_struct)
    1089              :       ELSE
    1090         1574 :          CPASSERT(PRESENT(fm_re))
    1091         1574 :          layout_ok = cp_fm_struct_equivalent(fm_re%matrix_struct, ctx%rs_route%matrix_struct)
    1092         1574 :          IF (PRESENT(fm_im)) THEN
    1093         1278 :             layout_ok = layout_ok .AND. cp_fm_struct_equivalent(fm_im%matrix_struct, ctx%rs_route%matrix_struct)
    1094              :          END IF
    1095              :       END IF
    1096        33304 :       CPASSERT(layout_ok)
    1097              :       ! Avoid nested component array sections in GCC 16 bounds checks.
    1098        34582 :       xkp => ctx%kpoints%xkp
    1099        34582 :       first = ctx%kpoints%kp_range(1)
    1100        34582 :       last = ctx%kpoints%kp_range(2)
    1101        34582 :       CPASSERT(ik >= first .AND. ik <= last)
    1102        34582 :       nrow = ctx%rs_route%nrow
    1103        34582 :       ncol = ctx%rs_route%ncol
    1104        34582 :       bytes_per_k = 16_int_8*(INT(nrow, int_8)*ncol + SIZE(ctx%rs_route%entry, 2, KIND=int_8))
    1105              :       width = MIN(rs_batch_size, last - first + 1, &
    1106        34582 :                   INT(rs_batch_bytes/(INT(ctx%nspin*kpoint_num_slots, int_8)*MAX(1_int_8, bytes_per_k))))
    1107        34582 :       IF (width < 2 .OR. nrow == 0 .OR. ncol == 0) THEN
    1108              :          ! No extra dense tile for a large/empty layout or a single local k point.
    1109              :          CALL kpoint_operator_rs_build_batch(ctx%rs(irs, slot), ctx%rs_route, xkp(:, ik:ik), &
    1110        10772 :                                              TARGET=TARGET, fm_re=fm_re, fm_im=fm_im)
    1111              :       ELSE
    1112              :          ASSOCIATE (work => ctx%rs_batch(irs, slot))
    1113        26980 :             IF (.NOT. ALLOCATED(work%data)) ALLOCATE (work%data(width, nrow, ncol))
    1114        23810 :             IF (work%first == 0 .OR. ik < work%first .OR. ik >= work%first + width) THEN
    1115         6838 :                work%first = first + ((ik - first)/width)*width
    1116              :                CALL kpoint_operator_rs_build_batch(ctx%rs(irs, slot), ctx%rs_route, &
    1117         6838 :                                                    xkp(:, work%first:MIN(work%first + width - 1, last)), batch=work%data)
    1118              :             END IF
    1119        23810 :             k = ik - work%first + 1
    1120              :             ! Deliver a work copy: eigensolvers may overwrite H/S without corrupting the window.
    1121        47620 :             IF (PRESENT(TARGET)) THEN
    1122     29460640 :                target%local_data(1:nrow, 1:ncol) = work%data(k, :, :)
    1123       713430 :                target%local_data(nrow + 1:, :) = z_zero
    1124        22870 :                target%local_data(:, ncol + 1:) = z_zero
    1125              :             ELSE
    1126          940 :                CPASSERT(PRESENT(fm_re))
    1127       103882 :                fm_re%local_data(1:nrow, 1:ncol) = REAL(work%data(k, :, :), dp)
    1128       103882 :                IF (PRESENT(fm_im)) fm_im%local_data(1:nrow, 1:ncol) = AIMAG(work%data(k, :, :))
    1129         9552 :                fm_re%local_data(nrow + 1:, :) = 0.0_dp
    1130          940 :                fm_re%local_data(:, ncol + 1:) = 0.0_dp
    1131         9552 :                IF (PRESENT(fm_im)) fm_im%local_data(nrow + 1:, :) = 0.0_dp
    1132          940 :                IF (PRESENT(fm_im)) fm_im%local_data(:, ncol + 1:) = 0.0_dp
    1133              :             END IF
    1134              :          END ASSOCIATE
    1135              :       END IF
    1136        34582 :       IF (ctx%kpoints%use_real_wfn) THEN
    1137          296 :          IF (PRESENT(TARGET)) target%local_data(:, :) = CMPLX(REAL(target%local_data, dp), 0.0_dp, dp)
    1138          296 :          IF (PRESENT(fm_im)) fm_im%local_data(:, :) = 0.0_dp
    1139              :       END IF
    1140        34582 :       CALL timestop(handle)
    1141              : 
    1142        34582 :    END SUBROUTINE kpoint_operator_rs_build
    1143              : 
    1144              : ! **************************************************************************************************
    1145              : !> \brief Accumulate several k points in one route/value traversal, without MPI or BLACS.
    1146              : !>        The leading batch dimension makes updates to different k points contiguous.
    1147              : !>        Columns remain independent OpenMP work items; each target retains the
    1148              : !>        original normal/mirrored contribution order. Phase scratch is call-local.
    1149              : !>        The singleton fallback writes directly to the supplied CFM or FM pair.
    1150              : !> \param snapshot immutable real-space contributions
    1151              : !> \param route structural block routing and target tile map
    1152              : !> \param xkp actual coordinates, one column per k point
    1153              : !> \param batch optional packed complex output (k point, local row, local column)
    1154              : !> \param TARGET optional single complex output instead of batch
    1155              : !> \param fm_re optional single real output instead of batch/target
    1156              : !> \param fm_im optional single imaginary output
    1157              : !> \param phase_work optional private phase storage supplied by a worker
    1158              : !> \param parallel_columns split columns across threads, disabled inside k-point workers
    1159              : ! **************************************************************************************************
    1160        80725 :    SUBROUTINE kpoint_operator_rs_build_batch(snapshot, route, xkp, batch, TARGET, fm_re, fm_im, phase_work, parallel_columns)
    1161              : 
    1162              :       TYPE(kpoint_operator_rs_type), INTENT(IN)          :: snapshot
    1163              :       TYPE(kpoint_operator_rs_route_type), INTENT(IN)    :: route
    1164              :       REAL(KIND=dp), INTENT(IN)                          :: xkp(:, :)
    1165              :       COMPLEX(KIND=dp), CONTIGUOUS, INTENT(INOUT), &
    1166              :          OPTIONAL, TARGET                                :: batch(:, :, :)
    1167              :       TYPE(cp_cfm_type), INTENT(INOUT), OPTIONAL         :: target
    1168              :       TYPE(cp_fm_type), INTENT(INOUT), OPTIONAL          :: fm_re, fm_im
    1169              :       COMPLEX(KIND=dp), INTENT(INOUT), OPTIONAL, TARGET  :: phase_work(:, :)
    1170              :       LOGICAL, INTENT(IN), OPTIONAL                      :: parallel_columns
    1171              : 
    1172        80725 :       COMPLEX(KIND=dp), ALLOCATABLE, TARGET              :: owned_phase(:, :)
    1173        80725 :       COMPLEX(KIND=dp), CONTIGUOUS, POINTER              :: output(:, :, :)
    1174        80725 :       COMPLEX(KIND=dp), POINTER                          :: phase(:, :)
    1175              :       INTEGER                                            :: first, iblock, k, last, nbatch, ncol, &
    1176              :                                                             nthreads, thread
    1177              :       LOGICAL                                            :: parallel
    1178              :       REAL(KIND=dp)                                      :: arg
    1179              : 
    1180        80725 :       nbatch = SIZE(xkp, 2)
    1181        80725 :       CPASSERT(nbatch >= 1 .AND. nbatch <= rs_batch_size .AND. SIZE(xkp, 1) == 3)
    1182       322900 :       CPASSERT(COUNT([PRESENT(batch), PRESENT(TARGET), PRESENT(fm_re)]) == 1)
    1183        80725 :       NULLIFY (output)
    1184        80725 :       IF (PRESENT(batch)) THEN
    1185         6838 :          CPASSERT(SIZE(batch, 1) >= nbatch)
    1186         6838 :          output => batch
    1187              :       ELSE
    1188        73887 :          CPASSERT(nbatch == 1)
    1189        73887 :          IF (PRESENT(TARGET)) THEN
    1190        66187 :             output(1:1, 1:SIZE(target%local_data, 1), 1:SIZE(target%local_data, 2)) => target%local_data
    1191              :          ELSE
    1192         7700 :             CPASSERT(PRESENT(fm_re))
    1193     13033775 :             fm_re%local_data(:, :) = 0.0_dp
    1194     11937273 :             IF (PRESENT(fm_im)) fm_im%local_data(:, :) = 0.0_dp
    1195              :          END IF
    1196              :       END IF
    1197        80725 :       IF (ASSOCIATED(output)) output(:, :, :) = z_zero
    1198        80725 :       IF (route%nrow == 0 .OR. route%ncol == 0) RETURN
    1199        80725 :       IF (PRESENT(phase_work)) THEN
    1200        63115 :          CPASSERT(SIZE(phase_work, 1) >= nbatch)
    1201        63115 :          CPASSERT(SIZE(phase_work, 2) == SIZE(route%entry, 2))
    1202        63115 :          phase => phase_work(1:nbatch, :)
    1203              :       ELSE
    1204        70440 :          ALLOCATE (owned_phase(nbatch, SIZE(route%entry, 2)))
    1205        17610 :          phase => owned_phase
    1206              :       END IF
    1207     39968782 :       DO iblock = 1, SIZE(route%entry, 2)
    1208     39888057 :          IF (.NOT. snapshot%nonzero(iblock)) CYCLE
    1209     94494777 :          DO k = 1, nbatch
    1210    218823748 :             arg = twopi*DOT_PRODUCT(REAL(route%entry(5:7, iblock), dp), xkp(:, k))
    1211     94593994 :             phase(k, iblock) = CMPLX(COS(arg), SIN(arg), KIND=dp)
    1212              :          END DO
    1213              :       END DO
    1214        80725 :       ncol = route%ncol
    1215        80725 :       parallel = .TRUE.
    1216        80725 :       IF (PRESENT(parallel_columns)) parallel = parallel_columns
    1217        80725 :       nthreads = 1
    1218        80725 : !$    IF (parallel) nthreads = MIN(omp_get_max_threads(), ncol)
    1219        17610 :       IF (nthreads > 1) THEN
    1220              : !$OMP PARALLEL DEFAULT(NONE) NUM_THREADS(nthreads) SHARED(snapshot,route,phase,output,fm_re,fm_im,ncol) &
    1221              : !$OMP PRIVATE(first,last,thread,nthreads)
    1222              :          thread = 0
    1223              :          nthreads = 1
    1224              : !$       thread = omp_get_thread_num()
    1225              : !$       nthreads = omp_get_num_threads()
    1226              :          first = ncol*thread/nthreads + 1
    1227              :          last = ncol*(thread + 1)/nthreads
    1228            0 :          CALL kpoint_operator_rs_accumulate(snapshot, route, phase, first, last, output, fm_re, fm_im)
    1229              : !$OMP END PARALLEL
    1230              :       ELSE
    1231              :          ! Called inside a k-point worker: no nested parallel region, timer or allocation.
    1232        80725 :          CALL kpoint_operator_rs_accumulate(snapshot, route, phase, 1, ncol, output, fm_re, fm_im)
    1233              :       END IF
    1234              : 
    1235       161450 :    END SUBROUTINE kpoint_operator_rs_build_batch
    1236              : 
    1237              : ! **************************************************************************************************
    1238              : !> \brief Shared borrowed/packed phase-sum kernel; each caller owns a disjoint column interval.
    1239              : !> \param snapshot read-only block views
    1240              : !> \param route structural layout
    1241              : !> \param phase precomputed phases (k, block)
    1242              : !> \param first first local column owned by this call
    1243              : !> \param last last local column owned by this call
    1244              : !> \param output packed complex output, or disassociated for the FM pair
    1245              : !> \param fm_re real output
    1246              : !> \param fm_im imaginary output
    1247              : ! **************************************************************************************************
    1248        80725 :    SUBROUTINE kpoint_operator_rs_accumulate(snapshot, route, phase, first, last, output, fm_re, fm_im)
    1249              :       TYPE(kpoint_operator_rs_type), INTENT(IN)          :: snapshot
    1250              :       TYPE(kpoint_operator_rs_route_type), INTENT(IN)    :: route
    1251              :       COMPLEX(KIND=dp), INTENT(IN)                       :: phase(:, :)
    1252              :       INTEGER, INTENT(IN)                                :: first, last
    1253              :       COMPLEX(KIND=dp), CONTIGUOUS, INTENT(IN), POINTER  :: output(:, :, :)
    1254              :       TYPE(cp_fm_type), INTENT(INOUT), OPTIONAL          :: fm_re, fm_im
    1255              : 
    1256              :       COMPLEX(KIND=dp)                                   :: factors(rs_batch_size), value
    1257        80725 :       COMPLEX(KIND=dp), CONTIGUOUS, POINTER              :: full(:, :)
    1258              :       INTEGER                                            :: c0, col, hi, iblock, j, k, lo, nc, nr, &
    1259              :                                                             part, r0, row, source_col, source_row
    1260              :       REAL(KIND=dp)                                      :: element
    1261              : 
    1262            0 :       IF (first > last) RETURN
    1263        80725 :       NULLIFY (full)
    1264        80725 :       IF (ASSOCIATED(output)) THEN
    1265              :          IF (SIZE(output, 1) == 1 .AND. &
    1266        73025 :              route%nrow == route%matrix_struct%nrow_global .AND. &
    1267              :              route%ncol == route%matrix_struct%ncol_global) THEN
    1268        56209 :             full(1:SIZE(output, 2), 1:SIZE(output, 3)) => output
    1269              :          END IF
    1270              :       END IF
    1271     39968782 :       DO iblock = 1, SIZE(route%entry, 2)
    1272     39888057 :          IF (.NOT. snapshot%nonzero(iblock)) CYCLE
    1273     39708115 :          r0 = route%entry(1, iblock)
    1274     39708115 :          c0 = route%entry(2, iblock)
    1275     39708115 :          nr = route%entry(3, iblock)
    1276     39708115 :          nc = route%entry(4, iblock)
    1277     39708115 :          IF (ASSOCIATED(full)) THEN
    1278              :             ! Update whole contiguous blocks, clipped to this worker's columns.
    1279     24506398 :             lo = MAX(first, c0)
    1280     24506398 :             hi = MIN(last, c0 + nc - 1)
    1281     24506398 :             IF (lo <= hi) THEN
    1282              :                full(r0:r0 + nr - 1, lo:hi) = full(r0:r0 + nr - 1, lo:hi) + &
    1283   1490971466 :                                              phase(1, iblock)*snapshot%blocks(iblock)%data(:, lo - c0 + 1:hi - c0 + 1)
    1284              :             END IF
    1285     24506398 :             IF (route%entry(8, iblock) /= 0) THEN
    1286     16695415 :                lo = MAX(first, r0)
    1287     16695415 :                hi = MIN(last, r0 + nr - 1)
    1288     16695415 :                IF (lo <= hi) THEN
    1289              :                   full(c0:c0 + nc - 1, lo:hi) = full(c0:c0 + nc - 1, lo:hi) + &
    1290              :                                                 CONJG(phase(1, iblock))* &
    1291    959146563 :                                                 TRANSPOSE(snapshot%blocks(iblock)%data(lo - r0 + 1:hi - r0 + 1, :))
    1292              :                END IF
    1293              :             END IF
    1294              :             CYCLE
    1295              :          END IF
    1296     45685876 :          DO part = 1, 2
    1297     30403434 :             IF (part == 2 .AND. route%entry(8, iblock) == 0) CYCLE
    1298     26346336 :             lo = MAX(first, route%tile(3, part, iblock))
    1299     26346336 :             hi = MIN(last, route%tile(4, part, iblock))
    1300     26346336 :             IF (lo > hi) CYCLE
    1301     79100564 :             factors(1:SIZE(phase, 1)) = phase(:, iblock)
    1302     48901025 :             IF (part == 2) factors(1:SIZE(phase, 1)) = CONJG(factors(1:SIZE(phase, 1)))
    1303    242187495 :             DO j = lo, hi
    1304    200459500 :                col = route%matrix_struct%col_indices(j)
    1305   1692662068 :                DO row = route%tile(1, part, iblock), route%tile(2, part, iblock)
    1306   1461799134 :                   source_row = route%matrix_struct%row_indices(row)
    1307   1461799134 :                   source_col = col
    1308   1461799134 :                   IF (part == 2) THEN
    1309    589833213 :                      source_col = source_row
    1310    589833213 :                      source_row = col
    1311              :                   END IF
    1312   1461799134 :                   element = snapshot%blocks(iblock)%data(source_row - r0 + 1, source_col - c0 + 1)
    1313   1662258634 :                   IF (ASSOCIATED(output)) THEN
    1314              : !$OMP SIMD
    1315              :                      DO k = 1, SIZE(phase, 1)
    1316   1956400455 :                         output(k, row, j) = output(k, row, j) + factors(k)*element
    1317              :                      END DO
    1318              :                   ELSE
    1319    858714117 :                      value = factors(1)*element
    1320    858714117 :                      fm_re%local_data(row, j) = fm_re%local_data(row, j) + REAL(value, dp)
    1321    858714117 :                      IF (PRESENT(fm_im)) fm_im%local_data(row, j) = fm_im%local_data(row, j) + AIMAG(value)
    1322              :                   END IF
    1323              :                END DO
    1324              :             END DO
    1325              :          END DO
    1326              :       END DO
    1327        80725 :    END SUBROUTINE kpoint_operator_rs_accumulate
    1328              : 
    1329              : ! **************************************************************************************************
    1330              : !> \brief Size per-worker phase scratch for direct assembly into a solver matrix.
    1331              : !> \param ctx prepared context
    1332              : !> \param nentry number of route entries
    1333              : ! **************************************************************************************************
    1334        30008 :    SUBROUTINE kpoint_operator_work_size(ctx, nentry)
    1335              :       TYPE(kpoint_operator_context_type), INTENT(IN)     :: ctx
    1336              :       INTEGER, INTENT(OUT)                               :: nentry
    1337              : 
    1338        30008 :       CPASSERT(ALLOCATED(ctx%rs_route))
    1339        30008 :       nentry = SIZE(ctx%rs_route%entry, 2)
    1340        30008 :    END SUBROUTINE kpoint_operator_work_size
    1341              : 
    1342              : ! **************************************************************************************************
    1343              : !> \brief Evaluate an immutable prepared operator with exclusively caller-owned output and scratch.
    1344              : !>        Prepare/refresh/release remain collective and serialized. Evaluate contains no MPI,
    1345              : !>        DBCSR lookup, window update or nested OpenMP region. Source blocks must stay unchanged.
    1346              : !> \param ctx prepared read-only context
    1347              : !> \param ispin instance spin
    1348              : !> \param slot operator slot
    1349              : !> \param ik global k index owned by this group
    1350              : !> \param phase private phase scratch, sized with work_size
    1351              : !> \param TARGET optional private complex singleton output
    1352              : !> \param fm_re optional private real output instead of TARGET
    1353              : !> \param fm_im optional private imaginary output, accompanying fm_re for a complex operator
    1354              : ! **************************************************************************************************
    1355        63115 :    SUBROUTINE kpoint_operator_evaluate(ctx, ispin, slot, ik, phase, TARGET, fm_re, fm_im)
    1356              :       TYPE(kpoint_operator_context_type), INTENT(IN)     :: ctx
    1357              :       INTEGER, INTENT(IN)                                :: ispin, slot, ik
    1358              :       TYPE(cp_cfm_type), INTENT(INOUT), OPTIONAL         :: target
    1359              :       COMPLEX(KIND=dp), INTENT(INOUT), TARGET            :: phase(:, :)
    1360              :       TYPE(cp_fm_type), INTENT(INOUT), OPTIONAL          :: fm_re, fm_im
    1361              : 
    1362        63115 :       REAL(KIND=dp), POINTER                             :: xkp(:, :)
    1363              :       INTEGER                                            :: row
    1364              : 
    1365            0 :       CPASSERT(ik >= ctx%kpoints%kp_range(1))
    1366        63115 :       CPASSERT(ik <= ctx%kpoints%kp_range(2))
    1367        63115 :       row = ispin
    1368        63115 :       IF (.NOT. ASSOCIATED(ctx%rs(row, slot)%source)) row = 1
    1369        63115 :       CPASSERT(ASSOCIATED(ctx%rs(row, slot)%source))
    1370        63115 :       xkp => ctx%kpoints%xkp
    1371              :       CALL kpoint_operator_rs_build_batch(ctx%rs(row, slot), ctx%rs_route, xkp(:, ik:ik), &
    1372              :                                           TARGET=TARGET, fm_re=fm_re, fm_im=fm_im, &
    1373        63115 :                                           phase_work=phase, parallel_columns=.FALSE.)
    1374        63115 :       IF (ctx%kpoints%use_real_wfn) THEN
    1375            0 :          IF (PRESENT(TARGET)) target%local_data(:, :) = CMPLX(REAL(target%local_data, dp), 0.0_dp, dp)
    1376            0 :          IF (PRESENT(fm_im)) fm_im%local_data(:, :) = 0.0_dp
    1377              :       END IF
    1378        63115 :    END SUBROUTINE kpoint_operator_evaluate
    1379              : 
    1380              : ! **************************************************************************************************
    1381              : !> \brief Start one operator for one global round and spin. The service walks
    1382              : !>        all groups with a k point in this round; groups without one still
    1383              : !>        participate in the transfers. Every rank must iterate through the
    1384              : !>        maximum local k-point count. Finish every started instance with
    1385              : !>        kpoint_operator_finish. The first start also creates the slot
    1386              : !>        bookkeeping of the context.
    1387              : !>        Collective over the environment-wide communicator: every rank must
    1388              : !>        call the same sequence of service calls.
    1389              : !> \param ctx context created by kpoint_operator_context_create
    1390              : !> \param ikp global round, up to the maximum k-point count across groups
    1391              : !> \param ispin instance spin: keys the bookkeeping
    1392              : !> \param rsmat real-space image matrices of the operator. A prepared operator
    1393              : !>        batch uses the values captured by prepare; otherwise start reads
    1394              : !>        the current values. The matching finish never reads rsmat
    1395              : !> \param slot instance slot, optionally backed by a prepared snapshot
    1396              : !> \param grid prepared reciprocal-grid cache of the caller
    1397              : !> \param use_grid extract from grid instead of a direct phase sum
    1398              : !> \param matrix_row row of rsmat to transform. Spin-free operators such
    1399              : !>        as S and T must pass kpoint_spin_free: their first matrix
    1400              : !>        dimension counts derivatives, not spins. A spin index is out of
    1401              : !>        bounds in plain SCF, but with derivatives stored it reads another
    1402              : !>        valid row silently
    1403              : ! **************************************************************************************************
    1404        29138 :    SUBROUTINE kpoint_operator_start(ctx, ikp, ispin, rsmat, slot, grid, use_grid, matrix_row)
    1405              : 
    1406              :       TYPE(kpoint_operator_context_type), INTENT(INOUT)  :: ctx
    1407              :       INTEGER, INTENT(IN)                                :: ikp, ispin
    1408              :       TYPE(dbcsr_p_type), DIMENSION(:, :), POINTER       :: rsmat
    1409              :       INTEGER, INTENT(IN)                                :: slot
    1410              :       TYPE(rskp_grid_type), INTENT(IN), OPTIONAL         :: grid
    1411              :       LOGICAL, INTENT(IN)                                :: use_grid
    1412              :       INTEGER, INTENT(IN)                                :: matrix_row
    1413              : 
    1414              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_start'
    1415              : 
    1416              :       INTEGER                                            :: handle, igroup, ik, irs, kpmax
    1417        29138 :       INTEGER, DIMENSION(:, :), POINTER                  :: kp_dist
    1418              : 
    1419        29138 :       CALL timeset(routineN, handle)
    1420              : 
    1421        29138 :       CPASSERT(ASSOCIATED(ctx%kpoints))
    1422        29138 :       CPASSERT(ASSOCIATED(ctx%kpoints%sab_nl))
    1423              :       ! The instance spin is bounded by the spin space declared at creation;
    1424              :       ! the matrix row by this operator's own rows. The two bounds differ for
    1425              :       ! spin-free operators such as S, whose first dimension counts
    1426              :       ! real-space blocks.
    1427        29138 :       CPASSERT(ispin >= 1 .AND. ispin <= ctx%nspin)
    1428        29138 :       CPASSERT(matrix_row >= 1 .AND. matrix_row <= SIZE(rsmat, 1))
    1429        58524 :       kpmax = MAXVAL(ctx%kpoints%kp_dist(2, :) - ctx%kpoints%kp_dist(1, :) + 1)
    1430        29138 :       CPASSERT(ikp >= 1 .AND. ikp <= kpmax)
    1431        29138 :       CPASSERT(slot >= kpoint_slot_ks .AND. slot <= kpoint_slot_t)
    1432              :       ! State is per round; allocate expensive copy descriptors only when
    1433              :       ! an actual distributed transfer is launched.
    1434        29138 :       IF (.NOT. ALLOCATED(ctx%status)) THEN
    1435        52550 :          ALLOCATE (ctx%status(kpmax, ctx%nspin, kpoint_num_slots))
    1436       153964 :          ctx%status(:, :, :) = kpoint_slot_idle
    1437              :       END IF
    1438        29138 :       CPASSERT(ctx%status(ikp, ispin, slot) == kpoint_slot_idle)
    1439        29138 :       IF (.NOT. use_grid) THEN
    1440        29138 :          irs = kpoint_operator_rs_match(ctx, rsmat, ispin, slot, matrix_row)
    1441        29138 :          IF (irs > 0) THEN
    1442        29098 :             CPASSERT(ASSOCIATED(ctx%work_sab, ctx%kpoints%sab_nl))
    1443        29098 :             ctx%status(ikp, ispin, slot) = kpoint_slot_local
    1444        29098 :             CALL timestop(handle)
    1445        29098 :             RETURN
    1446              :          END IF
    1447              :       END IF
    1448           40 :       CALL kpoint_operator_ensure(ctx, rsmat)
    1449              : 
    1450              :       ! Only the transfer backend needs copy descriptors. A prepared-only
    1451              :       ! or get-only context never carries them. Size by the declared spin
    1452              :       ! space, not by the possibly spin-free operator's row count.
    1453           40 :       IF (.NOT. ALLOCATED(ctx%info)) THEN
    1454              :          ! One copy_info per in-flight part: start takes it INTENT(OUT),
    1455              :          ! so two concurrent starts cannot share one instance.
    1456            0 :          ALLOCATE (ctx%info(SIZE(ctx%kpoints%xkp, 2), ctx%nspin, &
    1457          380 :                             kpoint_num_slots, MERGE(1, 2, ctx%kpoints%use_real_wfn)))
    1458              :       END IF
    1459              : 
    1460           40 :       kp_dist => ctx%kpoints%kp_dist
    1461           80 :       DO igroup = 1, ctx%kpoints%nkp_groups
    1462           40 :          ik = kp_dist(1, igroup) + ikp - 1
    1463           40 :          IF (ik > kp_dist(2, igroup)) CYCLE
    1464           40 :          CALL kpoint_operator_densify(ctx, rsmat, matrix_row, ik, grid, use_grid)
    1465              :          CALL kpoint_operator_launch(ctx%kpoints, ik, ctx%fmwork, ctx%fmlocal, &
    1466           80 :                                      ctx%info(ik, ispin, slot, :))
    1467              :       END DO
    1468           40 :       ctx%status(ikp, ispin, slot) = kpoint_slot_in_flight
    1469              : 
    1470           40 :       CALL timestop(handle)
    1471              : 
    1472        29138 :    END SUBROUTINE kpoint_operator_start
    1473              : 
    1474              : ! **************************************************************************************************
    1475              : !> \brief Finish one started instance: build its prepared group-local operator,
    1476              : !>        or merge received parts into cmat (real part first, overwrite then add)
    1477              : !>        and reclaim the send side on every group.
    1478              : !>        The slot returns to idle.
    1479              : !>        Collective over the environment-wide communicator: every rank must
    1480              : !>        call the same sequence of service calls.
    1481              : !> \param ctx context that started the instance
    1482              : !> \param ikp global round of the start call
    1483              : !> \param ispin spin component of the start call
    1484              : !> \param slot slot of the start call; the slot must be in flight
    1485              : !> \param cmat caller-allocated complex matrix that receives the operator.
    1486              : !>        It must share the distribution of the k-point group. Unchanged on
    1487              : !>        groups without a k point in this round
    1488              : !> \param fm_re optional real FM output instead of cmat for real wavefunctions, on the group layout
    1489              : ! **************************************************************************************************
    1490        29138 :    SUBROUTINE kpoint_operator_finish(ctx, ikp, ispin, slot, cmat, fm_re)
    1491              : 
    1492              :       TYPE(kpoint_operator_context_type), INTENT(INOUT)  :: ctx
    1493              :       INTEGER, INTENT(IN)                                :: ikp, ispin, slot
    1494              :       TYPE(cp_cfm_type), OPTIONAL                        :: cmat
    1495              :       TYPE(cp_fm_type), OPTIONAL                         :: fm_re
    1496              : 
    1497              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_finish'
    1498              : 
    1499              :       INTEGER                                            :: handle, igroup, ik, irs, kpmax
    1500              :       INTEGER, DIMENSION(2)                              :: kp_range
    1501        29138 :       INTEGER, DIMENSION(:, :), POINTER                  :: kp_dist
    1502              :       LOGICAL                                            :: layout_ok
    1503              : 
    1504        29138 :       CALL timeset(routineN, handle)
    1505              : 
    1506        29138 :       CPASSERT(ASSOCIATED(ctx%kpoints))
    1507        29138 :       CPASSERT(ALLOCATED(ctx%status))
    1508        29138 :       kpmax = SIZE(ctx%status, 1)
    1509        29138 :       CPASSERT(ikp >= 1 .AND. ikp <= kpmax)
    1510        29138 :       CPASSERT(ispin >= 1 .AND. ispin <= ctx%nspin)
    1511        29138 :       CPASSERT(slot >= kpoint_slot_ks .AND. slot <= kpoint_slot_t)
    1512        29138 :       CPASSERT(ctx%status(ikp, ispin, slot) /= kpoint_slot_idle)
    1513              :       ! The merge writes cmat in place. A foreign distribution would fail
    1514              :       ! inside the BLACS calls, far from this argument.
    1515              : 
    1516        29138 :       CPASSERT(PRESENT(cmat) .NEQV. PRESENT(fm_re))
    1517        29138 :       CPASSERT(.NOT. PRESENT(fm_re) .OR. ctx%kpoints%use_real_wfn)
    1518        87414 :       kp_range = ctx%kpoints%kp_range
    1519        29138 :       IF (ctx%status(ikp, ispin, slot) == kpoint_slot_local) THEN
    1520        29098 :          irs = ispin
    1521        29098 :          IF (.NOT. ASSOCIATED(ctx%rs(irs, slot)%source)) irs = 1
    1522        29098 :          ik = kp_range(1) + ikp - 1
    1523        29098 :          IF (ik <= kp_range(2)) THEN
    1524        29098 :             CALL kpoint_operator_rs_build(ctx, irs, slot, ik, cmat, fm_re=fm_re)
    1525              :          END IF
    1526        29098 :          ctx%status(ikp, ispin, slot) = kpoint_slot_idle
    1527        29098 :          CALL timestop(handle)
    1528        29098 :          RETURN
    1529              :       END IF
    1530           40 :       CPASSERT(ctx%status(ikp, ispin, slot) == kpoint_slot_in_flight)
    1531           40 :       IF (PRESENT(cmat)) THEN
    1532           24 :          layout_ok = cp_fm_struct_equivalent(cmat%matrix_struct, ctx%fmlocal%matrix_struct)
    1533              :       ELSE
    1534           16 :          layout_ok = cp_fm_struct_equivalent(fm_re%matrix_struct, ctx%fmlocal%matrix_struct)
    1535              :       END IF
    1536           40 :       CPASSERT(layout_ok)
    1537           40 :       kp_dist => ctx%kpoints%kp_dist
    1538           80 :       DO igroup = 1, ctx%kpoints%nkp_groups
    1539           40 :          ik = kp_dist(1, igroup) + ikp - 1
    1540           40 :          IF (ik > kp_dist(2, igroup)) CYCLE
    1541              :          CALL kpoint_operator_receive(ctx%kpoints, ctx%fmlocal, ik, ctx%info(ik, ispin, slot, :), &
    1542           80 :                                       cmat=cmat, fm_re=fm_re)
    1543              :       END DO
    1544           40 :       ctx%status(ikp, ispin, slot) = kpoint_slot_idle
    1545              : 
    1546           40 :       CALL timestop(handle)
    1547              : 
    1548        29138 :    END SUBROUTINE kpoint_operator_finish
    1549              : 
    1550              : ! **************************************************************************************************
    1551              : !> \brief Assemble one operator for one global round and spin in one call:
    1552              : !>        run the single-group transfer of the service for every group and
    1553              : !>        finish it immediately, through transfer states local to this call.
    1554              : !>        The persistent slot machinery stays with start and finish.
    1555              : !>        Exactly one output flavor must be present: cmat receives the complex
    1556              : !>        operator (real part overwrites, imaginary part adds) through the
    1557              : !>        context buffer; the fm pair receives the two parts separately,
    1558              : !>        transferred directly into fm_re and fm_im on the owning group (any
    1559              : !>        distribution: the general copy redistributes); or the dbcsr pair
    1560              : !>        receives the two parts as block-cyclic matrices built by the service,
    1561              : !>        replacing the caller-side copy_fm_to_dbcsr_bc detour. The dbcsr
    1562              : !>        matrices must be freshly initialized (dbcsr_init_p) and the cmat
    1563              : !>        flavor must share the distribution of the k-point group.
    1564              : !>        Collective over the environment-wide communicator: every rank must
    1565              : !>        call the same sequence of service calls.
    1566              : !> \param ctx context created by kpoint_operator_context_create
    1567              : !> \param ikp global round, up to the maximum k-point count across groups
    1568              : !> \param ispin instance spin: bounds-checked against the context spin space
    1569              : !> \param rsmat real-space image matrices of the operator
    1570              : !> \param cmat complex matrix output
    1571              : !> \param fm_re real-part matrix output
    1572              : !> \param fm_im imaginary-part output; may be omitted for real wavefunctions
    1573              : !> \param mat_re real-part block-cyclic dbcsr output
    1574              : !> \param mat_im imaginary-part block-cyclic dbcsr output
    1575              : !> \param matrix_row row of rsmat to transform. Spin-free operators such
    1576              : !>        as S and T must pass kpoint_spin_free: their first matrix
    1577              : !>        dimension counts derivatives, not spins. A spin index is out of
    1578              : !>        bounds in plain SCF, but with derivatives stored it reads another
    1579              : !>        valid row silently
    1580              : !> \param slot optional prepared slot: opt in to its immutable snapshot.
    1581              : !>        Without slot, get always reads current source values. A missing or
    1582              : !>        mismatched snapshot retains the original transfer path. Prepared FM
    1583              : !>        targets must match the layout selected at prepare; inactive groups
    1584              : !>        do not inspect or modify their output arguments.
    1585              : !> \param grid optional lattice-FFT grid, overriding a prepared snapshot for this request
    1586              : ! **************************************************************************************************
    1587         5510 :    SUBROUTINE kpoint_operator_get(ctx, ikp, ispin, rsmat, cmat, fm_re, fm_im, &
    1588              :                                   mat_re, mat_im, matrix_row, slot, grid)
    1589              : 
    1590              :       TYPE(kpoint_operator_context_type), INTENT(INOUT)  :: ctx
    1591              :       INTEGER, INTENT(IN)                                :: ikp, ispin
    1592              :       TYPE(dbcsr_p_type), DIMENSION(:, :), POINTER       :: rsmat
    1593              :       TYPE(cp_cfm_type), OPTIONAL                        :: cmat
    1594              :       TYPE(cp_fm_type), OPTIONAL                         :: fm_re, fm_im
    1595              :       TYPE(dbcsr_type), INTENT(INOUT), OPTIONAL          :: mat_re, mat_im
    1596              :       INTEGER, INTENT(IN)                                :: matrix_row
    1597              :       INTEGER, INTENT(IN), OPTIONAL                      :: slot
    1598              :       TYPE(rskp_grid_type), INTENT(IN), OPTIONAL         :: grid
    1599              : 
    1600              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_get'
    1601              : 
    1602              :       INTEGER                                            :: handle, igroup, ik, irs, kpmax
    1603         5510 :       INTEGER, DIMENSION(:, :), POINTER                  :: kp_dist
    1604              :       LOGICAL                                            :: layout_ok
    1605        66120 :       TYPE(copy_info_type)                               :: info(2)
    1606              : 
    1607         5510 :       CALL timeset(routineN, handle)
    1608              : 
    1609        22040 :       CPASSERT(COUNT([PRESENT(cmat), PRESENT(fm_re), PRESENT(mat_re)]) == 1)
    1610         5510 :       IF (PRESENT(fm_re) .OR. PRESENT(fm_im)) THEN
    1611         1042 :          CPASSERT(PRESENT(fm_re))
    1612         1042 :          CPASSERT(PRESENT(fm_im) .OR. ctx%kpoints%use_real_wfn)
    1613              :       END IF
    1614         5510 :       IF (PRESENT(mat_re) .OR. PRESENT(mat_im)) THEN
    1615          236 :          CPASSERT(PRESENT(mat_re) .AND. PRESENT(mat_im))
    1616              :       END IF
    1617              : 
    1618         5510 :       CPASSERT(ASSOCIATED(ctx%kpoints))
    1619         5510 :       CPASSERT(ASSOCIATED(ctx%kpoints%sab_nl))
    1620         5510 :       CPASSERT(ispin >= 1 .AND. ispin <= ctx%nspin)
    1621         5510 :       CPASSERT(matrix_row >= 1 .AND. matrix_row <= SIZE(rsmat, 1))
    1622        13452 :       kpmax = MAXVAL(ctx%kpoints%kp_dist(2, :) - ctx%kpoints%kp_dist(1, :) + 1)
    1623         5510 :       CPASSERT(ikp >= 1 .AND. ikp <= kpmax)
    1624         5510 :       IF (PRESENT(slot) .AND. .NOT. PRESENT(grid)) THEN
    1625         5486 :          CPASSERT(slot >= kpoint_slot_ks .AND. slot <= kpoint_slot_t)
    1626         5486 :          irs = kpoint_operator_rs_match(ctx, rsmat, ispin, slot, matrix_row)
    1627         5486 :          IF (irs > 0) THEN
    1628         5486 :             ik = ctx%kpoints%kp_range(1) + ikp - 1
    1629         5486 :             IF (ik <= ctx%kpoints%kp_range(2)) THEN
    1630         5484 :                IF (PRESENT(mat_re)) THEN
    1631          236 :                   IF (.NOT. ALLOCATED(ctx%fmimag)) THEN
    1632           58 :                      ALLOCATE (ctx%fmimag)
    1633           58 :                      CALL cp_fm_create(ctx%fmimag, ctx%fmlocal%matrix_struct)
    1634              :                   END IF
    1635          236 :                   CALL kpoint_operator_rs_build(ctx, irs, slot, ik, fm_re=ctx%fmlocal, fm_im=ctx%fmimag)
    1636          236 :                   CALL copy_fm_to_dbcsr_bc(ctx%fmlocal, mat_re)
    1637          236 :                   CALL copy_fm_to_dbcsr_bc(ctx%fmimag, mat_im)
    1638              :                ELSE
    1639         5248 :                   CALL kpoint_operator_rs_build(ctx, irs, slot, ik, TARGET=cmat, fm_re=fm_re, fm_im=fm_im)
    1640              :                END IF
    1641              :             END IF
    1642         5486 :             CALL timestop(handle)
    1643         5486 :             RETURN
    1644              :          END IF
    1645              :       END IF
    1646           24 :       CALL kpoint_operator_ensure(ctx, rsmat)
    1647           24 :       IF (PRESENT(cmat)) THEN
    1648              :          ! The merge writes cmat in place. A foreign distribution would fail
    1649              :          ! inside the BLACS calls, far from this argument.
    1650           24 :          layout_ok = cp_fm_struct_equivalent(cmat%matrix_struct, ctx%fmlocal%matrix_struct)
    1651           24 :          CPASSERT(layout_ok)
    1652              :       END IF
    1653              : 
    1654           24 :       kp_dist => ctx%kpoints%kp_dist
    1655           72 :       DO igroup = 1, ctx%kpoints%nkp_groups
    1656           48 :          ik = kp_dist(1, igroup) + ikp - 1
    1657           48 :          IF (ik > kp_dist(2, igroup)) CYCLE
    1658              :          ! Absent fm targets are forwarded as absent optional arguments. Both
    1659              :          ! CFM and DBCSR outputs use the same context-owned receive buffer.
    1660           48 :          CALL kpoint_operator_densify(ctx, rsmat, matrix_row, ik, grid=grid, use_grid=PRESENT(grid))
    1661              :          CALL kpoint_operator_launch(ctx%kpoints, ik, ctx%fmwork, ctx%fmlocal, info, &
    1662           48 :                                      target_re=fm_re, target_im=fm_im)
    1663              :          CALL kpoint_operator_receive(ctx%kpoints, ctx%fmlocal, ik, info, cmat=cmat, fm_re=fm_re, fm_im=fm_im, &
    1664           72 :                                       mat_re=mat_re, mat_im=mat_im)
    1665              :       END DO
    1666              : 
    1667           24 :       CALL timestop(handle)
    1668              : 
    1669         5510 :    END SUBROUTINE kpoint_operator_get
    1670              : 
    1671              : ! **************************************************************************************************
    1672              : !> \brief Read an operator inside a group-local OT loop, after collective cache preparation.
    1673              : !>        One group retains the sparse transform and distribution required by OT's DBCSR kernels.
    1674              : !>        Multiple groups read their owned cache; no world collective may occur in this loop.
    1675              : !> \param matrix_rs current real-space operator
    1676              : !> \param kpoints host environment
    1677              : !> \param kp local k-point state
    1678              : !> \param ispin source row (one for spin-free S/T)
    1679              : !> \param cache_re owned real part, used with multiple groups
    1680              : !> \param cache_im owned imaginary part, used with multiple groups
    1681              : !> \param matrix_re newly initialized sparse real output
    1682              : !> \param matrix_im newly initialized sparse imaginary output
    1683              : ! **************************************************************************************************
    1684        12846 :    SUBROUTINE kpoint_operator_get_local(matrix_rs, kpoints, kp, ispin, cache_re, cache_im, &
    1685              :                                         matrix_re, matrix_im)
    1686              :       TYPE(dbcsr_p_type), DIMENSION(:, :), POINTER       :: matrix_rs
    1687              :       TYPE(kpoint_type), POINTER                         :: kpoints
    1688              :       TYPE(kpoint_env_type), POINTER                     :: kp
    1689              :       INTEGER, INTENT(IN)                                :: ispin
    1690              :       TYPE(cp_fm_type), INTENT(IN)                       :: cache_re, cache_im
    1691              :       TYPE(dbcsr_type), POINTER                          :: matrix_re, matrix_im
    1692              : 
    1693              :       INTEGER                                            :: nkp_groups
    1694        12846 :       INTEGER, DIMENSION(:, :, :), POINTER               :: cell_to_index
    1695        12846 :       REAL(dp), DIMENSION(:, :), POINTER                 :: xkp
    1696              :       TYPE(neighbor_list_set_p_type), DIMENSION(:), &
    1697        12846 :          POINTER                                         :: sab_nl
    1698              : 
    1699        12846 :       NULLIFY (cell_to_index, sab_nl, xkp)
    1700            0 :       CPASSERT(ASSOCIATED(matrix_rs))
    1701        12846 :       CPASSERT(ispin >= 1 .AND. ispin <= SIZE(matrix_rs, 1))
    1702        12846 :       CPASSERT(ASSOCIATED(kpoints))
    1703        12846 :       CPASSERT(ASSOCIATED(kp))
    1704              : 
    1705        12846 :       CALL dbcsr_init_p(matrix_re)
    1706        12846 :       CALL dbcsr_init_p(matrix_im)
    1707        12846 :       nkp_groups = kpoints%nkp_groups
    1708        12846 :       xkp => kpoints%xkp
    1709        12846 :       cell_to_index => kpoints%cell_to_index
    1710        12846 :       sab_nl => kpoints%sab_nl
    1711        12846 :       IF (nkp_groups == 1) THEN
    1712         5836 :          CPASSERT(ASSOCIATED(xkp))
    1713         5836 :          CPASSERT(ASSOCIATED(cell_to_index))
    1714         5836 :          CPASSERT(ASSOCIATED(sab_nl))
    1715              :          CALL dbcsr_create(matrix_re, template=matrix_rs(1, 1)%matrix, &
    1716         5836 :                            matrix_type=dbcsr_type_symmetric)
    1717              :          CALL dbcsr_create(matrix_im, template=matrix_rs(1, 1)%matrix, &
    1718         5836 :                            matrix_type=dbcsr_type_antisymmetric)
    1719         5836 :          CALL cp_dbcsr_alloc_block_from_nbl(matrix_re, sab_nl)
    1720         5836 :          CALL cp_dbcsr_alloc_block_from_nbl(matrix_im, sab_nl)
    1721         5836 :          CALL dbcsr_set(matrix_re, 0.0_dp)
    1722         5836 :          CALL dbcsr_set(matrix_im, 0.0_dp)
    1723              :          CALL rskp_transform(rmatrix=matrix_re, cmatrix=matrix_im, rsmat=matrix_rs, ispin=ispin, &
    1724         5836 :                              xkp=xkp(1:3, kp%nkpoint), cell_to_index=cell_to_index, sab_nl=sab_nl)
    1725              :       ELSE
    1726         7010 :          CALL copy_fm_to_dbcsr_bc(cache_re, matrix_re)
    1727         7010 :          CALL copy_fm_to_dbcsr_bc(cache_im, matrix_im)
    1728              :       END IF
    1729        12846 :    END SUBROUTINE kpoint_operator_get_local
    1730              : 
    1731              : ! **************************************************************************************************
    1732              : !> \brief Fill caller-owned matrices for one operator row, including independent history snapshots.
    1733              : !>        Prepare and fallback transfers are collective. Prepared one-rank groups evaluate k points
    1734              : !>        concurrently, with no MPI or matrix allocation/refcounts inside the worker loop.
    1735              : !> \param kpoints host environment
    1736              : !> \param ao_ao_fm global-layout template
    1737              : !> \param rsmat current real-space images
    1738              : !> \param slot operator slot
    1739              : !> \param cmat allocated group-local outputs, one per locally owned k point
    1740              : !> \param matrix_row source row; S/T use kpoint_spin_free
    1741              : ! **************************************************************************************************
    1742         1190 :    SUBROUTINE kpoint_operator_get_all(kpoints, ao_ao_fm, rsmat, slot, cmat, matrix_row)
    1743              :       TYPE(kpoint_type), POINTER                         :: kpoints
    1744              :       TYPE(cp_fm_type), INTENT(IN)                       :: ao_ao_fm
    1745              :       TYPE(dbcsr_p_type), DIMENSION(:, :), POINTER       :: rsmat
    1746              :       INTEGER, INTENT(IN)                                :: slot
    1747              :       TYPE(cp_cfm_type), DIMENSION(:), INTENT(INOUT)     :: cmat
    1748              :       INTEGER, INTENT(IN)                                :: matrix_row
    1749              : 
    1750              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_get_all'
    1751              : 
    1752         1190 :       COMPLEX(KIND=dp), ALLOCATABLE                      :: phase(:, :)
    1753              :       INTEGER                                            :: handle, ik, ikp, kplocal, kpmax, nentry, &
    1754              :                                                             nworkers
    1755              :       LOGICAL                                            :: layout_ok, prepared
    1756         1190 :       TYPE(kpoint_operator_context_type)                 :: ctx
    1757              : 
    1758         1190 :       CALL timeset(routineN, handle)
    1759         1190 :       kplocal = SIZE(kpoints%kp_env)
    1760         1190 :       CPASSERT(SIZE(cmat) == kplocal .AND. kplocal > 0)
    1761         1190 :       CALL kpoint_operator_context_create(ctx, kpoints, ao_ao_fm, 1)
    1762         1190 :       CALL kpoint_operator_prepare(ctx, rsmat, slot, prepared, matrix_row=matrix_row)
    1763         1190 :       IF (prepared .AND. kpoints%para_env_kp%num_pe == 1) THEN
    1764         3372 :          DO ikp = 1, kplocal
    1765         2336 :             layout_ok = cp_fm_struct_equivalent(cmat(ikp)%matrix_struct, ctx%rs_route%matrix_struct)
    1766         3372 :             CPASSERT(layout_ok)
    1767              :          END DO
    1768         1036 :          CALL kpoint_operator_work_size(ctx, nentry)
    1769         1036 :          nworkers = 1
    1770         1036 : !$       nworkers = MIN(omp_get_max_threads(), kplocal)
    1771              : !$OMP PARALLEL DEFAULT(NONE) NUM_THREADS(nworkers) SHARED(ctx,kpoints,cmat,slot,kplocal,nentry) &
    1772         1036 : !$OMP PRIVATE(phase,ikp,ik)
    1773              :          ALLOCATE (phase(1, nentry))
    1774              : !$OMP DO SCHEDULE(DYNAMIC, 1)
    1775              :          DO ikp = 1, kplocal
    1776              :             ik = kpoints%kp_range(1) + ikp - 1
    1777              :             CALL kpoint_operator_evaluate(ctx, 1, slot, ik, phase, cmat(ikp))
    1778              :          END DO
    1779              : !$OMP END DO
    1780         1036 :          DEALLOCATE (phase)
    1781              : !$OMP END PARALLEL
    1782              :       ELSE
    1783          308 :          kpmax = MAXVAL(kpoints%kp_dist(2, :) - kpoints%kp_dist(1, :) + 1)
    1784          398 :          DO ikp = 1, kpmax
    1785              :             CALL kpoint_operator_get(ctx, ikp, 1, rsmat, cmat=cmat(MIN(ikp, kplocal)), &
    1786          398 :                                      matrix_row=matrix_row, slot=slot)
    1787              :          END DO
    1788              :       END IF
    1789         1190 :       CALL kpoint_operator_context_release(ctx)
    1790         1190 :       CALL timestop(handle)
    1791         1190 :    END SUBROUTINE kpoint_operator_get_all
    1792              : 
    1793              : ! **************************************************************************************************
    1794              : !> \brief Prepare owned split-FM operators for OT without entering an SCF eigensolver.
    1795              : !>        H (or the initialization density) retains all source spins; S/T are built only once.
    1796              : !>        Outputs survive the transient context. Compatible cache storage is reused, values refreshed.
    1797              : !> \param kpoints host environment and owned OT caches
    1798              : !> \param ao_ao_fm global-layout template
    1799              : !> \param matrix_ks Hamiltonian or initialization density
    1800              : !> \param matrix_s spin-free overlap
    1801              : !> \param matrix_t optional spin-free kinetic operator; absent leaves the existing kinetic cache alone
    1802              : ! **************************************************************************************************
    1803         2000 :    SUBROUTINE kpoint_operator_store(kpoints, ao_ao_fm, matrix_ks, matrix_s, matrix_t)
    1804              :       TYPE(kpoint_type), POINTER                         :: kpoints
    1805              :       TYPE(cp_fm_type), INTENT(IN)                       :: ao_ao_fm
    1806              :       TYPE(dbcsr_p_type), DIMENSION(:, :), POINTER       :: matrix_ks, matrix_s
    1807              :       TYPE(dbcsr_p_type), DIMENSION(:, :), OPTIONAL, &
    1808              :          POINTER                                         :: matrix_t
    1809              : 
    1810              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_store'
    1811              :       INTEGER                                            :: handle, ic, ikp, ispin, ncomp, nspin
    1812              :       LOGICAL                                            :: kinetic, reuse
    1813              :       TYPE(cp_fm_struct_type), POINTER                   :: matrix_struct
    1814              :       TYPE(kpoint_env_type), POINTER                     :: kp
    1815         2000 :       TYPE(kpoint_operator_context_type)                 :: ctx
    1816              : 
    1817         2000 :       CALL timeset(routineN, handle)
    1818         2000 :       nspin = SIZE(matrix_ks, 1)
    1819         2000 :       ncomp = MERGE(1, 2, kpoints%use_real_wfn)
    1820         2000 :       kinetic = PRESENT(matrix_t)
    1821         2000 :       IF (kinetic) kinetic = ASSOCIATED(matrix_t)
    1822         2000 :       CALL kpoint_operator_context_create(ctx, kpoints, ao_ao_fm, nspin)
    1823         2000 :       CALL kpoint_operator_ensure_group(ctx)
    1824         2000 :       matrix_struct => ctx%fmlocal%matrix_struct
    1825         5425 :       DO ikp = 1, SIZE(kpoints%kp_env)
    1826         3425 :          kp => kpoints%kp_env(ikp)%kpoint_env
    1827         3425 :          reuse = .FALSE.
    1828         3425 :          IF (ASSOCIATED(kp%ot_hmat)) THEN
    1829         9606 :             IF (ALL(SHAPE(kp%ot_hmat) == [ncomp, nspin])) THEN
    1830         3202 :                reuse = cp_fm_struct_equivalent(kp%ot_hmat(1, 1)%matrix_struct, matrix_struct)
    1831              :             END IF
    1832              :          END IF
    1833         3202 :          IF (.NOT. reuse) THEN
    1834          223 :             CALL cp_fm_release(kp%ot_hmat)
    1835         1690 :             ALLOCATE (kp%ot_hmat(ncomp, nspin))
    1836          489 :             DO ispin = 1, nspin
    1837         1021 :                DO ic = 1, ncomp
    1838          798 :                   CALL cp_fm_create(kp%ot_hmat(ic, ispin), matrix_struct)
    1839              :                END DO
    1840              :             END DO
    1841              :          END IF
    1842         3425 :          CALL allocate_spin_free(kp%ot_smat)
    1843         5425 :          IF (kinetic) CALL allocate_spin_free(kp%ot_tmat)
    1844              :       END DO
    1845         2000 :       CALL store_slot(matrix_ks, kpoint_slot_ks, nspin)
    1846         2000 :       CALL store_slot(matrix_s, kpoint_slot_s, 1)
    1847         2000 :       IF (kinetic) CALL store_slot(matrix_t, kpoint_slot_t, 1)
    1848         2000 :       CALL kpoint_operator_context_release(ctx)
    1849         2000 :       CALL timestop(handle)
    1850              : 
    1851              :    CONTAINS
    1852              : 
    1853              : ! **************************************************************************************************
    1854              : !> \brief ...
    1855              : !> \param cache ...
    1856              : ! **************************************************************************************************
    1857         3888 :       SUBROUTINE allocate_spin_free(cache)
    1858              :       TYPE(cp_fm_type), DIMENSION(:), POINTER            :: cache
    1859              : 
    1860              :       INTEGER                                            :: ic
    1861              :       LOGICAL                                            :: reuse
    1862              : 
    1863         3888 :          reuse = .FALSE.
    1864         3888 :          IF (ASSOCIATED(cache)) THEN
    1865         3482 :             IF (SIZE(cache) == ncomp) reuse = cp_fm_struct_equivalent(cache(1)%matrix_struct, matrix_struct)
    1866              :          END IF
    1867         3482 :          IF (reuse) RETURN
    1868          406 :          CALL cp_fm_release(cache)
    1869         2030 :          ALLOCATE (cache(ncomp))
    1870         1218 :          DO ic = 1, ncomp
    1871         1218 :             CALL cp_fm_create(cache(ic), matrix_struct)
    1872              :          END DO
    1873              :       END SUBROUTINE allocate_spin_free
    1874              : 
    1875              : ! **************************************************************************************************
    1876              : !> \brief ...
    1877              : !> \param rsmat ...
    1878              : !> \param slot ...
    1879              : !> \param nrows ...
    1880              : ! **************************************************************************************************
    1881         4246 :       SUBROUTINE store_slot(rsmat, slot, nrows)
    1882              :       TYPE(dbcsr_p_type), DIMENSION(:, :), POINTER       :: rsmat
    1883              :       INTEGER, INTENT(IN)                                :: slot, nrows
    1884              : 
    1885         4246 :       COMPLEX(KIND=dp), ALLOCATABLE                      :: phase(:, :)
    1886              :       INTEGER                                            :: ikp, ispin, kplocal, kpmax, nentry, &
    1887              :                                                             nworkers
    1888              :       LOGICAL                                            :: prepared, ready, use_grid
    1889         4246 :       TYPE(rskp_grid_type), ALLOCATABLE                  :: grids(:)
    1890              : 
    1891         4246 :          nworkers = 1
    1892         4246 : !$       nworkers = omp_get_max_threads()
    1893              :          use_grid = kpoints%lattice_fft == lattice_fft_on .OR. &
    1894         4246 :                     (kpoints%lattice_fft == lattice_fft_auto .AND. SIZE(kpoints%xkp, 2) >= 27)
    1895         4246 :          prepared = .FALSE.
    1896              :          ! Match STANDARD's single-world-rank, single-thread preference for the forward lattice FFT.
    1897         4246 :          IF (.NOT. use_grid .OR. nworkers > 1 .OR. kpoints%blacs_env_all%para_env%num_pe > 1) THEN
    1898         4246 :             IF (slot == kpoint_slot_ks) THEN
    1899         2000 :                CALL kpoint_operator_prepare(ctx, rsmat, slot, prepared)
    1900              :             ELSE
    1901         2246 :                CALL kpoint_operator_prepare(ctx, rsmat, slot, prepared, matrix_row=kpoint_spin_free)
    1902              :             END IF
    1903              :          END IF
    1904         4246 :          kplocal = SIZE(kpoints%kp_env)
    1905         4246 :          IF (prepared .AND. kpoints%para_env_kp%num_pe == 1) THEN
    1906         3756 :             CALL kpoint_operator_work_size(ctx, nentry)
    1907         3756 :             nworkers = MIN(nworkers, kplocal*nrows)
    1908              : !$OMP PARALLEL DEFAULT(NONE) NUM_THREADS(nworkers) SHARED(ctx,kpoints,rsmat,slot,nrows,kplocal,nentry) &
    1909         3756 : !$OMP PRIVATE(phase,ikp,ispin)
    1910              :             ALLOCATE (phase(1, nentry))
    1911              : !$OMP DO COLLAPSE(2) SCHEDULE(DYNAMIC, 1)
    1912              :             DO ikp = 1, kplocal
    1913              :                DO ispin = 1, nrows
    1914              :                   CALL store_round(rsmat, slot, ikp, ispin, phase)
    1915              :                END DO
    1916              :             END DO
    1917              : !$OMP END DO
    1918         3756 :             DEALLOCATE (phase)
    1919              : !$OMP END PARALLEL
    1920              :          ELSE
    1921          980 :             kpmax = MAXVAL(kpoints%kp_dist(2, :) - kpoints%kp_dist(1, :) + 1)
    1922              :             ! Keep the existing forward lattice-FFT choice when distributed snapshots cannot be prepared.
    1923          490 :             use_grid = use_grid .AND. .NOT. prepared
    1924              :             IF (use_grid) THEN
    1925            0 :                CALL kpoint_operator_ensure(ctx, rsmat)
    1926            0 :                ALLOCATE (grids(nrows))
    1927            0 :                DO ispin = 1, nrows
    1928              :                   CALL rskp_transform_grid_prepare(grids(ispin), ctx%rmatrix, rsmat, ispin, &
    1929              :                                                    kpoints%xkp, kpoints%nkp_grid, kpoints%cell_to_index, &
    1930              :                                                    kpoints%sab_nl, ready, &
    1931            0 :                                                    max_storage_bytes=512_int_8*1024_int_8**2/INT(nspin + 1, int_8))
    1932            0 :                   use_grid = use_grid .AND. ready
    1933              :                END DO
    1934              :             END IF
    1935         1432 :             DO ikp = 1, kpmax
    1936         2420 :                DO ispin = 1, nrows
    1937         1930 :                   IF (use_grid) THEN
    1938            0 :                      CALL store_round(rsmat, slot, ikp, ispin, grid=grids(ispin))
    1939              :                   ELSE
    1940          988 :                      CALL store_round(rsmat, slot, ikp, ispin)
    1941              :                   END IF
    1942              :                END DO
    1943              :             END DO
    1944          490 :             IF (ALLOCATED(grids)) THEN
    1945            0 :                DO ispin = 1, nrows
    1946            0 :                   CALL rskp_transform_grid_release(grids(ispin))
    1947              :                END DO
    1948            0 :                DEALLOCATE (grids)
    1949              :             END IF
    1950              :          END IF
    1951         4246 :       END SUBROUTINE store_slot
    1952              : 
    1953              : ! **************************************************************************************************
    1954              : !> \brief ...
    1955              : !> \param rsmat ...
    1956              : !> \param slot ...
    1957              : !> \param ikp ...
    1958              : !> \param ispin ...
    1959              : !> \param phase ...
    1960              : !> \param grid ...
    1961              : ! **************************************************************************************************
    1962         8054 :       SUBROUTINE store_round(rsmat, slot, ikp, ispin, phase, grid)
    1963              :       TYPE(dbcsr_p_type), DIMENSION(:, :), POINTER       :: rsmat
    1964              :       INTEGER, INTENT(IN)                                :: slot, ikp, ispin
    1965              :       COMPLEX(KIND=dp), INTENT(INOUT), OPTIONAL          :: phase(:, :)
    1966              :       TYPE(rskp_grid_type), INTENT(IN), OPTIONAL         :: grid
    1967              : 
    1968              :       INTEGER                                            :: ik
    1969         8054 :       TYPE(cp_fm_type), DIMENSION(:), POINTER            :: cache
    1970              :       TYPE(cp_fm_type), POINTER                          :: imag
    1971              :       TYPE(kpoint_env_type), POINTER                     :: kp
    1972              : 
    1973              :          ! A valid dummy cache is passed on inactive tail groups; get leaves it untouched.
    1974         8054 :          kp => kpoints%kp_env(MIN(ikp, SIZE(kpoints%kp_env)))%kpoint_env
    1975        12220 :          SELECT CASE (slot)
    1976              :          CASE (kpoint_slot_ks)
    1977         4166 :             cache => kp%ot_hmat(:, ispin)
    1978              :          CASE (kpoint_slot_s)
    1979         3425 :             cache => kp%ot_smat
    1980              :          CASE (kpoint_slot_t)
    1981          463 :             cache => kp%ot_tmat
    1982              :          END SELECT
    1983         8054 :          NULLIFY (imag)
    1984         8054 :          IF (SIZE(cache) == 2) imag => cache(2)
    1985         8054 :          IF (PRESENT(phase)) THEN
    1986         7066 :             ik = kpoints%kp_range(1) + ikp - 1
    1987         7066 :             CALL kpoint_operator_evaluate(ctx, ispin, slot, ik, phase, fm_re=cache(1), fm_im=imag)
    1988              :          ELSE
    1989              :             CALL kpoint_operator_get(ctx, ikp, ispin, rsmat, fm_re=cache(1), fm_im=imag, &
    1990          988 :                                      matrix_row=ispin, slot=slot, grid=grid)
    1991              :          END IF
    1992         8054 :       END SUBROUTINE store_round
    1993              :    END SUBROUTINE kpoint_operator_store
    1994              : 
    1995            0 : END MODULE qs_kpoint_operators
        

Generated by: LCOV version 2.0-1