LCOV - code coverage report
Current view: top level - src - bse_matvec.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:9ccafda) Lines: 75.3 % 283 213
Test Date: 2026-10-15 00:55:44 Functions: 76.9 % 13 10

            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 Matrix-free application of the BSE matrices A and B to trial vectors from RI slabs that
      10              : !>        are sliced along the RI index over all MPI ranks
      11              : !> \par History
      12              : !>      09.2026 created [Maximilian Graml]
      13              : ! **************************************************************************************************
      14              : MODULE bse_matvec
      15              : 
      16              :    USE bse_util,                        ONLY: ia_of_occ_virt,&
      17              :                                               occ_of_ia,&
      18              :                                               virt_of_ia
      19              :    USE cp_blacs_env,                    ONLY: cp_blacs_env_create,&
      20              :                                               cp_blacs_env_release,&
      21              :                                               cp_blacs_env_type
      22              :    USE cp_fm_struct,                    ONLY: cp_fm_struct_create,&
      23              :                                               cp_fm_struct_release,&
      24              :                                               cp_fm_struct_type
      25              :    USE cp_fm_types,                     ONLY: cp_fm_create,&
      26              :                                               cp_fm_get_info,&
      27              :                                               cp_fm_get_submatrix,&
      28              :                                               cp_fm_release,&
      29              :                                               cp_fm_set_all,&
      30              :                                               cp_fm_to_fm_submat_general,&
      31              :                                               cp_fm_type
      32              :    USE input_constants,                 ONLY: bse_precond_full_diag
      33              :    USE kinds,                           ONLY: dp
      34              :    USE message_passing,                 ONLY: mp_mem_avail_per_rank_GB,&
      35              :                                               mp_mem_used_per_rank_GB,&
      36              :                                               mp_para_env_type
      37              : #include "./base/base_uses.f90"
      38              : 
      39              :    IMPLICIT NONE
      40              : 
      41              :    PRIVATE
      42              : 
      43              :    CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'bse_matvec'
      44              : 
      45              :    ! the detected MEMORY_PER_PROC claims this share of the free memory on top of the occupied memory,
      46              :    ! the share resolve_memory_per_proc of the GW claims; the probe counts reclaimable cache
      47              :    REAL(KIND=dp), PARAMETER :: detected_memory_fraction = 0.8_dp, &
      48              :                                fallback_memory_per_proc_GB = 2.0_dp
      49              : 
      50              :    PUBLIC :: bse_matvec_env_type, bse_matvec_create, bse_matvec_release, bse_matvec_apply, &
      51              :              bse_memory_left_GB, bse_resolve_memory_per_proc, &
      52              :              bse_matvec_diagonal, bse_matvec_subblock, bse_matvec_vector_struct, bse_matvec_selfcheck
      53              : 
      54              : ! **************************************************************************************************
      55              : !> \brief RI slabs of one spin channel, every rank holding n_ri_loc whole RI slices
      56              : !> \param alpha prefactor of the exchange term (2 singlet, 0 triplet)
      57              : !> \param w_fac prefactor of the screened term (0 switches it off)
      58              : !> \param eps_diff ε_a - ε_i at the compound index ia = (i-1)*virt + a of bse_util's ia_of_occ_virt
      59              : !> \param B_ia B^P_ia at (a, i, P)
      60              : !> \param B_bar_ia \bar{B}^P_ia at (a, i, P), only for do_abba
      61              : !> \param B_bar_ij \bar{B}^P_ij at (j, i, P)
      62              : !> \param B_ab B^P_ab at (b, a, P)
      63              : !> \param row_count rows of a trial vector owned by each rank, in rank order
      64              : !> \param row_displ first row of each rank minus one, so that row_count and row_displ describe
      65              : !>        the contiguous ia range per rank that allgatherv and sum_scatter need
      66              : !> \param blacs_env npe x 1 process grid of the slabs and of all trial vectors
      67              : !> \param block_cols trial vectors per pass in bse_matvec_apply, -1 to size it from the memory left
      68              : !> \param memory_per_proc_gb the resolved MEMORY_PER_PROC of the BSE, which block_cols -1 sizes the pass from
      69              : ! **************************************************************************************************
      70              :    TYPE bse_matvec_env_type
      71              :       INTEGER                                            :: homo = 0, virt = 0, n_ov = 0, &
      72              :                                                             n_ri_loc = 0, block_cols = -1
      73              :       REAL(KIND=dp)                                      :: alpha = 0.0_dp, w_fac = 0.0_dp, &
      74              :                                                             memory_per_proc_gb = -1.0_dp
      75              :       LOGICAL                                            :: do_abba = .FALSE.
      76              :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: row_count, row_displ
      77              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: eps_diff
      78              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: B_ia, B_bar_ia, B_bar_ij, B_ab
      79              :       TYPE(cp_blacs_env_type), POINTER                   :: blacs_env => NULL()
      80              :       TYPE(mp_para_env_type), POINTER                    :: para_env => NULL()
      81              :    END TYPE bse_matvec_env_type
      82              : 
      83              : CONTAINS
      84              : 
      85              : ! **************************************************************************************************
      86              : !> \brief MEMORY_PER_PROC of the BSE as a number: the keyword's value, or for -1 the memory the rank
      87              : !>        holds plus the share detected_memory_fraction of the free memory, read once at the first
      88              : !>        consumer; when the free memory cannot be read, the GW's fallback of 2 GB with a warning
      89              : !> \param memory_per_proc_gb the keyword's value on entry, the memory per MPI rank for the BSE on exit
      90              : !> \param para_env the ranks that share the node's memory
      91              : !> \param unit_nr output unit, positive on the writing rank only
      92              : ! **************************************************************************************************
      93          116 :    SUBROUTINE bse_resolve_memory_per_proc(memory_per_proc_gb, para_env, unit_nr)
      94              :       REAL(KIND=dp), INTENT(INOUT)                       :: memory_per_proc_gb
      95              :       TYPE(mp_para_env_type), POINTER                    :: para_env
      96              :       INTEGER, INTENT(IN)                                :: unit_nr
      97              : 
      98              :       REAL(KIND=dp)                                      :: mem_free_GB, mem_occ_GB
      99              : 
     100          116 :       IF (memory_per_proc_gb >= 0.0_dp) RETURN
     101          116 :       CALL mp_mem_avail_per_rank_GB(para_env, mem_free_GB)
     102          116 :       CALL mp_mem_used_per_rank_GB(para_env, mem_occ_GB)
     103          116 :       IF (mem_free_GB > 0.0_dp) THEN
     104          116 :          memory_per_proc_gb = mem_occ_GB + detected_memory_fraction*mem_free_GB
     105          116 :          IF (unit_nr > 0) THEN
     106           58 :             WRITE (unit_nr, '(T2,A4,T7,A,T67,F14.3)') 'BSE|', 'Memory per MPI rank for the BSE, detected (GB)', &
     107          116 :                memory_per_proc_gb
     108              :          END IF
     109              :       ELSE
     110            0 :          memory_per_proc_gb = fallback_memory_per_proc_GB
     111              :          CALL cp_warn(__LOCATION__, &
     112              :                       "BSE: the free memory per MPI rank could not be detected; MEMORY_PER_PROC falls back "// &
     113            0 :                       "to 2 GB. Set MEMORY_PER_PROC in the BSE section for a run that fits the node.")
     114              :       END IF
     115              : 
     116              :    END SUBROUTINE bse_resolve_memory_per_proc
     117              : 
     118              : ! **************************************************************************************************
     119              : !> \brief What MEMORY_PER_PROC leaves to a step: the resolved memory per MPI rank minus the memory the
     120              : !>        rank holds now, never negative; both probes reduce over the ranks, so every rank gets the same
     121              : !> \param memory_per_proc_gb the resolved MEMORY_PER_PROC
     122              : !> \param para_env the ranks that share the node's memory
     123              : !> \return memory left per MPI rank in GB
     124              : ! **************************************************************************************************
     125           70 :    FUNCTION bse_memory_left_GB(memory_per_proc_gb, para_env) RESULT(mem_left_GB)
     126              :       REAL(KIND=dp), INTENT(IN)                          :: memory_per_proc_gb
     127              :       TYPE(mp_para_env_type), POINTER                    :: para_env
     128              :       REAL(KIND=dp)                                      :: mem_left_GB
     129              : 
     130              :       REAL(KIND=dp)                                      :: mem_occ_GB
     131              : 
     132              :       ! a consumer before the resolution would size itself from the keyword's -1
     133           70 :       CPASSERT(memory_per_proc_gb >= 0.0_dp)
     134           70 :       CALL mp_mem_used_per_rank_GB(para_env, mem_occ_GB)
     135           70 :       mem_left_GB = MAX(memory_per_proc_gb - mem_occ_GB, 0.0_dp)
     136              : 
     137           70 :    END FUNCTION bse_memory_left_GB
     138              : 
     139              : ! **************************************************************************************************
     140              : !> \brief Moves the RI slabs onto an npe x 1 process grid, such that every rank owns whole RI
     141              : !>        slices, and stores them as local 3-index arrays: the contraction over each P in
     142              : !>        bse_matvec_apply is then a local DGEMM and only the final sum over P crosses the ranks
     143              : !> \param mv_env the environment created here
     144              : !> \param fm_S_ia B^P_ia, N_RI x homo*virt
     145              : !> \param fm_S_bar_ij \bar{B}^P_ij, the slab that enters W_ij,ab, N_RI x homo*homo
     146              : !> \param fm_S_ab B^P_ab, N_RI x virt*virt
     147              : !> \param fm_S_bar_ia \bar{B}^P_ia, the slab that enters W_ib,aj, N_RI x homo*virt
     148              : !> \param eps_reduced quasiparticle energies of the homo+virt active levels
     149              : !> \param homo occupied levels of the active window
     150              : !> \param virt virtual levels of the active window
     151              : !> \param alpha prefactor of the exchange term, 2 singlet, 0 triplet
     152              : !> \param w_fac prefactor of the screened term, 0 switches it off
     153              : !> \param do_abba slice \bar{B}^P_ia as well, for the application of B
     154              : !> \param unit_nr output unit, positive on the writing rank only
     155              : !> \param block_cols trial vectors per pass of bse_matvec_apply; absent or -1 sizes it from the memory left
     156              : !> \param memory_per_proc_gb the resolved MEMORY_PER_PROC of the BSE, what block_cols -1 sizes the pass from
     157              : ! **************************************************************************************************
     158            8 :    SUBROUTINE bse_matvec_create(mv_env, fm_S_ia, fm_S_bar_ij, fm_S_ab, fm_S_bar_ia, eps_reduced, &
     159              :                                 homo, virt, alpha, w_fac, do_abba, unit_nr, block_cols, memory_per_proc_gb)
     160              : 
     161              :       TYPE(bse_matvec_env_type), INTENT(OUT)             :: mv_env
     162              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_S_ia, fm_S_bar_ij, fm_S_ab, &
     163              :                                                             fm_S_bar_ia
     164              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: eps_reduced
     165              :       INTEGER, INTENT(IN)                                :: homo, virt
     166              :       REAL(KIND=dp), INTENT(IN)                          :: alpha, w_fac
     167              :       LOGICAL, INTENT(IN)                                :: do_abba
     168              :       INTEGER, INTENT(IN)                                :: unit_nr
     169              :       INTEGER, INTENT(IN), OPTIONAL                      :: block_cols
     170              :       REAL(KIND=dp), INTENT(IN), OPTIONAL                :: memory_per_proc_gb
     171              : 
     172              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'bse_matvec_create'
     173              : 
     174              :       CHARACTER(LEN=12)                                  :: n_idle_str, n_ri_str, npe_str
     175              :       INTEGER                                            :: a, handle, i, n_ri, n_ri_slab, &
     176              :                                                             n_row_block
     177              :       REAL(KIND=dp)                                      :: mem_slabs_GB, n_slab_entries
     178              : 
     179            8 :       CALL timeset(routineN, handle)
     180              : 
     181            8 :       mv_env%homo = homo
     182            8 :       mv_env%virt = virt
     183            8 :       IF (PRESENT(block_cols)) mv_env%block_cols = block_cols
     184            8 :       IF (PRESENT(memory_per_proc_gb)) mv_env%memory_per_proc_gb = memory_per_proc_gb
     185            8 :       mv_env%n_ov = homo*virt
     186            8 :       mv_env%alpha = alpha
     187            8 :       mv_env%w_fac = w_fac
     188            8 :       mv_env%do_abba = do_abba
     189            8 :       mv_env%para_env => fm_S_ia%matrix_struct%para_env
     190              : 
     191              :       CALL cp_blacs_env_create(mv_env%blacs_env, mv_env%para_env, &
     192           24 :                                grid_2d=[mv_env%para_env%num_pe, 1])
     193              : 
     194              :       ! one contiguous ia range per rank, so that a trial vector is gathered with allgatherv and
     195              :       ! its image scattered back with sum_scatter, rather than replicated by two allreduces
     196            8 :       n_row_block = contiguous_row_block(mv_env%n_ov, mv_env%para_env%num_pe)
     197           40 :       ALLOCATE (mv_env%row_count(mv_env%para_env%num_pe), mv_env%row_displ(mv_env%para_env%num_pe))
     198           24 :       DO i = 1, mv_env%para_env%num_pe
     199           16 :          mv_env%row_displ(i) = MIN(mv_env%n_ov, (i - 1)*n_row_block)
     200           24 :          mv_env%row_count(i) = MIN(mv_env%n_ov, i*n_row_block) - mv_env%row_displ(i)
     201              :       END DO
     202              : 
     203              :       ! eps_diff_ia = ε_a - ε_i at the compound index ia = (i-1)*virt + a, the row index of every trial vector
     204           24 :       ALLOCATE (mv_env%eps_diff(mv_env%n_ov))
     205           40 :       DO i = 1, homo
     206          424 :          DO a = 1, virt
     207          416 :             mv_env%eps_diff(ia_of_occ_virt(i, a, virt)) = eps_reduced(homo + a) - eps_reduced(i)
     208              :          END DO
     209              :       END DO
     210              : 
     211              :       ! every slab is sliced on the same grid, so every rank owns the same RI indices of each slab
     212            8 :       CALL slice_slab(mv_env, fm_S_ia, virt, homo, mv_env%B_ia, mv_env%n_ri_loc)
     213            8 :       IF (w_fac /= 0.0_dp) THEN
     214            8 :          CALL slice_slab(mv_env, fm_S_bar_ij, homo, homo, mv_env%B_bar_ij, n_ri_slab)
     215            8 :          CPASSERT(n_ri_slab == mv_env%n_ri_loc)
     216            8 :          CALL slice_slab(mv_env, fm_S_ab, virt, virt, mv_env%B_ab, n_ri_slab)
     217            8 :          CPASSERT(n_ri_slab == mv_env%n_ri_loc)
     218            8 :          IF (do_abba) THEN
     219            4 :             CALL slice_slab(mv_env, fm_S_bar_ia, virt, homo, mv_env%B_bar_ia, n_ri_slab)
     220            4 :             CPASSERT(n_ri_slab == mv_env%n_ri_loc)
     221              :          END IF
     222              :       END IF
     223              : 
     224            8 :       CALL cp_fm_get_info(fm_S_ia, nrow_global=n_ri)
     225            8 :       IF (mv_env%para_env%num_pe > n_ri .AND. unit_nr > 0) THEN
     226            0 :          WRITE (npe_str, '(I0)') mv_env%para_env%num_pe
     227            0 :          WRITE (n_ri_str, '(I0)') n_ri
     228            0 :          WRITE (n_idle_str, '(I0)') mv_env%para_env%num_pe - n_ri
     229              :          CALL cp_warn(__LOCATION__, &
     230              :                       "BSE iterative solver: more MPI ranks ("//TRIM(npe_str)// &
     231              :                       ") than RI basis functions ("//TRIM(n_ri_str)//"); "// &
     232            0 :                       TRIM(n_idle_str)//" ranks hold no RI slice and idle in the kernel application.")
     233              :       END IF
     234              : 
     235            8 :       IF (unit_nr > 0) THEN
     236              :          ! pair entries of the sliced slabs per RI index
     237            4 :          n_slab_entries = REAL(homo*virt, dp)
     238            4 :          IF (w_fac /= 0.0_dp) THEN
     239            4 :             n_slab_entries = n_slab_entries + REAL(homo, dp)**2 + REAL(virt, dp)**2
     240            4 :             IF (do_abba) n_slab_entries = n_slab_entries + REAL(homo*virt, dp)
     241              :          END IF
     242            4 :          mem_slabs_GB = n_slab_entries*REAL(mv_env%n_ri_loc, dp)*8.0E-9_dp
     243            4 :          WRITE (unit_nr, '(T2,A4,T7,A,T71,I10)') 'BSE|', &
     244            8 :             'Max. number of RI functions per MPI rank', mv_env%n_ri_loc
     245            4 :          WRITE (unit_nr, '(T2,A4,T7,A,T67,F14.3)') 'BSE|', &
     246            8 :             'Memory of the RI slabs per MPI rank (GB)', mem_slabs_GB
     247              :       END IF
     248              : 
     249            8 :       CALL timestop(handle)
     250              : 
     251           16 :    END SUBROUTINE bse_matvec_create
     252              : 
     253              : ! **************************************************************************************************
     254              : !> \brief Rows of a trial vector per rank, the smallest block that leaves no rank beyond the last
     255              : !> \param n_ov number of transitions, the global row count
     256              : !> \param npe ranks of the npe x 1 grid
     257              : !> \return rows of every full block; the last rank takes what remains, possibly none
     258              : ! **************************************************************************************************
     259           36 :    PURE FUNCTION contiguous_row_block(n_ov, npe) RESULT(n_row_block)
     260              : 
     261              :       INTEGER, INTENT(IN)                                :: n_ov, npe
     262              :       INTEGER                                            :: n_row_block
     263              : 
     264           36 :       n_row_block = (n_ov + npe - 1)/npe
     265              : 
     266           36 :    END FUNCTION contiguous_row_block
     267              : 
     268              : ! **************************************************************************************************
     269              : !> \brief Redistributes one N_RI x n_fast*n_slow slab to whole RI rows per rank and copies the owned
     270              : !>        rows into a contiguous (n_fast, n_slow, n_ri_loc) array, slab_loc(x, y, p) = slab(P_p, (y-1)*n_fast + x)
     271              : !>        for the p-th owned RI index P_p, so that every slice slab_loc(:, :, p) is a n_fast x n_slow DGEMM operand
     272              : !> \param mv_env process grid and communicator of the slices
     273              : !> \param fm_slab the slab on the grid of the caller, N_RI x n_fast*n_slow
     274              : !> \param n_fast fast index of the pair index of the slab
     275              : !> \param n_slow slow index of the pair index of the slab
     276              : !> \param slab_loc the owned slices, allocated here
     277              : !> \param n_ri_loc RI indices owned by this rank, the third extent of slab_loc
     278              : ! **************************************************************************************************
     279           28 :    SUBROUTINE slice_slab(mv_env, fm_slab, n_fast, n_slow, slab_loc, n_ri_loc)
     280              : 
     281              :       TYPE(bse_matvec_env_type), INTENT(IN)              :: mv_env
     282              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_slab
     283              :       INTEGER, INTENT(IN)                                :: n_fast, n_slow
     284              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :), &
     285              :          INTENT(OUT)                                     :: slab_loc
     286              :       INTEGER, INTENT(OUT)                               :: n_ri_loc
     287              : 
     288              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'slice_slab'
     289              : 
     290              :       INTEGER                                            :: handle, n_ri, npair, p
     291              :       TYPE(cp_fm_struct_type), POINTER                   :: fm_struct
     292              :       TYPE(cp_fm_type)                                   :: fm_sliced
     293              : 
     294           28 :       CALL timeset(routineN, handle)
     295              : 
     296              :       ! row count from the struct: the slabs may carry fewer rows than the full RI basis
     297           28 :       CALL cp_fm_get_info(fm_slab, nrow_global=n_ri, ncol_global=npair)
     298           28 :       CPASSERT(npair == n_fast*n_slow)
     299              : 
     300              :       ! one-row blocks on the npe x 1 grid spread the RI rows cyclically over the ranks; the copy
     301              :       ! below turns each strided local_data row into a contiguous (n_fast, n_slow) slice
     302           28 :       NULLIFY (fm_struct)
     303              :       CALL cp_fm_struct_create(fm_struct, para_env=mv_env%para_env, context=mv_env%blacs_env, &
     304              :                                nrow_global=n_ri, ncol_global=npair, nrow_block=1, &
     305           28 :                                force_block=.TRUE.)
     306           28 :       CALL cp_fm_create(fm_sliced, fm_struct, name="fm_slab_ri_sliced")
     307           28 :       CALL cp_fm_struct_release(fm_struct)
     308              :       CALL cp_fm_to_fm_submat_general(fm_slab, fm_sliced, n_ri, npair, 1, 1, 1, 1, &
     309           28 :                                       fm_slab%matrix_struct%context)
     310              : 
     311           28 :       CALL cp_fm_get_info(fm_sliced, nrow_local=n_ri_loc)
     312          140 :       ALLOCATE (slab_loc(n_fast, n_slow, n_ri_loc))
     313         1190 :       DO p = 1, n_ri_loc
     314         3514 :          slab_loc(:, :, p) = RESHAPE(fm_sliced%local_data(p, 1:npair), [n_fast, n_slow])
     315              :       END DO
     316           28 :       CALL cp_fm_release(fm_sliced)
     317              : 
     318           28 :       CALL timestop(handle)
     319              : 
     320           84 :    END SUBROUTINE slice_slab
     321              : 
     322              : ! **************************************************************************************************
     323              : !> \brief Frees the sliced slabs, the transition energies and the process grid of mv_env
     324              : !> \param mv_env the environment released
     325              : ! **************************************************************************************************
     326            8 :    SUBROUTINE bse_matvec_release(mv_env)
     327              : 
     328              :       TYPE(bse_matvec_env_type), INTENT(INOUT)           :: mv_env
     329              : 
     330            8 :       IF (ALLOCATED(mv_env%row_count)) DEALLOCATE (mv_env%row_count)
     331            8 :       IF (ALLOCATED(mv_env%row_displ)) DEALLOCATE (mv_env%row_displ)
     332            8 :       IF (ALLOCATED(mv_env%eps_diff)) DEALLOCATE (mv_env%eps_diff)
     333            8 :       IF (ALLOCATED(mv_env%B_ia)) DEALLOCATE (mv_env%B_ia)
     334            8 :       IF (ALLOCATED(mv_env%B_bar_ia)) DEALLOCATE (mv_env%B_bar_ia)
     335            8 :       IF (ALLOCATED(mv_env%B_bar_ij)) DEALLOCATE (mv_env%B_bar_ij)
     336            8 :       IF (ALLOCATED(mv_env%B_ab)) DEALLOCATE (mv_env%B_ab)
     337            8 :       IF (ASSOCIATED(mv_env%blacs_env)) CALL cp_blacs_env_release(mv_env%blacs_env)
     338            8 :       NULLIFY (mv_env%para_env)
     339              : 
     340            8 :    END SUBROUTINE bse_matvec_release
     341              : 
     342              : ! **************************************************************************************************
     343              : !> \brief Matrix structure of a block of trial vectors: rows ia distributed, all columns local
     344              : !> \param mv_env process grid and communicator of the trial vectors
     345              : !> \param ncol_global number of trial vectors
     346              : !> \param fm_struct created here, released by the caller
     347              : ! **************************************************************************************************
     348           28 :    SUBROUTINE bse_matvec_vector_struct(mv_env, ncol_global, fm_struct)
     349              : 
     350              :       TYPE(bse_matvec_env_type), INTENT(IN)              :: mv_env
     351              :       INTEGER, INTENT(IN)                                :: ncol_global
     352              :       TYPE(cp_fm_struct_type), POINTER                   :: fm_struct
     353              : 
     354           28 :       NULLIFY (fm_struct)
     355              :       ! the row block matches mv_env%row_count, which bse_matvec_apply's collectives rely on
     356              :       CALL cp_fm_struct_create(fm_struct, para_env=mv_env%para_env, context=mv_env%blacs_env, &
     357              :                                nrow_global=mv_env%n_ov, ncol_global=ncol_global, &
     358              :                                nrow_block=contiguous_row_block(mv_env%n_ov, mv_env%para_env%num_pe), &
     359           28 :                                force_block=.TRUE.)
     360              : 
     361           28 :    END SUBROUTINE bse_matvec_vector_struct
     362              : 
     363              : ! **************************************************************************************************
     364              : !> \brief Applies A (and B) to ncol trial vectors without forming an N_ov x N_ov object,
     365              : !>        (A Z)_ia = (ε_a-ε_i) Z_ia + α sum_P B^P_ia sum_jb B^P_jb Z_jb - w sum_Pjb \bar{B}^P_ij B^P_ab Z_jb
     366              : !>        (B Z)_ia =                  α sum_P B^P_ia sum_jb B^P_jb Z_jb - w sum_Pjb \bar{B}^P_ib B^P_ja Z_jb
     367              : !>        α: exchange prefactor (2 singlet, 0 triplet), w: prefactor of the screened term,
     368              : !>        \bar{B}: slab bound by the caller of bse_matvec_create (B itself for TDHF and ALPHA
     369              : !>        screening, sum_Q [1+Q(0)]^-1_PQ B^Q otherwise); the sums over P run over the local RI
     370              : !>        slices and are completed by a sum over the ranks
     371              : !> \param mv_env slabs, prefactors and transition energies
     372              : !> \param fm_Z trial vectors, columns first_col .. first_col+ncol-1 are read
     373              : !> \param first_col first trial vector read, and first column written
     374              : !> \param ncol number of trial vectors
     375              : !> \param fm_AZ receives A Z in the same columns
     376              : !> \param fm_BZ receives B Z in the same columns, or from first_col_BZ on
     377              : !> \param first_col_BZ first column of fm_BZ written, first_col by default
     378              : ! **************************************************************************************************
     379           84 :    SUBROUTINE bse_matvec_apply(mv_env, fm_Z, first_col, ncol, fm_AZ, fm_BZ, first_col_BZ)
     380              : 
     381              :       TYPE(bse_matvec_env_type), INTENT(IN), TARGET      :: mv_env
     382              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_Z
     383              :       INTEGER, INTENT(IN)                                :: first_col, ncol
     384              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_AZ
     385              :       TYPE(cp_fm_type), INTENT(IN), OPTIONAL             :: fm_BZ
     386              :       INTEGER, INTENT(IN), OPTIONAL                      :: first_col_BZ
     387              : 
     388              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'bse_matvec_apply'
     389              : 
     390              :       INTEGER                                            :: col, col_shift_B, handle, handle2, ia, &
     391              :                                                             iloc, k, me, n_work_bufs, nb, nb_max, &
     392              :                                                             nrow_local, p
     393           84 :       INTEGER, DIMENSION(:), POINTER                     :: row_indices
     394              :       LOGICAL                                            :: do_B
     395              :       REAL(KIND=dp)                                      :: mem_avail_GB
     396           84 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: R_loc
     397           84 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:), TARGET   :: BabZ_buf, RA_buf, RB_buf, Z_buf
     398           84 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: BjaZ_ab, T_Pk
     399              :       REAL(KIND=dp), CONTIGUOUS, DIMENSION(:, :), &
     400           84 :          POINTER                                         :: B_ia_2d, BabZ_wide, RA, RB, Z, Z_wide
     401              :       REAL(KIND=dp), CONTIGUOUS, DIMENSION(:, :, :), &
     402           84 :          POINTER                                         :: BabZ_3d, RA_3d, RB_3d, Z_3d
     403              : 
     404           84 :       CALL timeset(routineN, handle)
     405              : 
     406           84 :       do_B = PRESENT(fm_BZ)
     407           84 :       IF (do_B) THEN
     408              :          ! B Z needs the \bar{B}^P_ia slab, which is sliced for do_abba only
     409           66 :          CPASSERT(mv_env%do_abba)
     410              :       END IF
     411           84 :       col_shift_B = 0
     412           84 :       IF (PRESENT(first_col_BZ)) col_shift_B = first_col_BZ - first_col
     413              : 
     414           84 :       CALL cp_fm_get_info(fm_Z, nrow_local=nrow_local, row_indices=row_indices)
     415              :       ! the collectives below read the rows of one rank as one range, as bse_matvec_vector_struct
     416              :       ! lays them out; a vector built elsewhere would scatter into the wrong rows
     417           84 :       me = mv_env%para_env%mepos + 1
     418           84 :       CPASSERT(nrow_local == mv_env%row_count(me))
     419           84 :       CPASSERT(nrow_local == 0 .OR. row_indices(1) == mv_env%row_displ(me) + 1)
     420          252 :       ALLOCATE (R_loc(nrow_local))
     421              : 
     422              :       ! columns per pass from the memory of the replicated work arrays Z, B_ab Z, R^A (and R^B)
     423           84 :       IF (do_B) THEN
     424              :          n_work_bufs = 4
     425              :       ELSE
     426           18 :          n_work_bufs = 3
     427              :       END IF
     428           84 :       IF (mv_env%block_cols > 0) THEN
     429              :          ! pinned by input: the replicated work arrays are then a known n_work_bufs*n_ov*nb doubles
     430           84 :          nb_max = MIN(ncol, mv_env%block_cols)
     431              :       ELSE
     432              :          ! the pass takes what MEMORY_PER_PROC leaves after the solver's arrays
     433            0 :          mem_avail_GB = bse_memory_left_GB(mv_env%memory_per_proc_gb, mv_env%para_env)
     434              :          nb_max = INT(MIN(REAL(ncol, dp), &
     435            0 :                           mem_avail_GB*1.0E9_dp/(8.0_dp*REAL(n_work_bufs, dp)*REAL(mv_env%n_ov, dp))))
     436              :       END IF
     437           84 :       nb_max = MAX(nb_max, 1)
     438              : 
     439          420 :       ALLOCATE (Z_buf(mv_env%n_ov*nb_max), RA_buf(mv_env%n_ov*nb_max), BabZ_buf(mv_env%n_ov*nb_max))
     440           84 :       IF (do_B) THEN
     441          330 :          ALLOCATE (RB_buf(mv_env%n_ov*nb_max), BjaZ_ab(mv_env%virt, mv_env%virt))
     442              :       END IF
     443          336 :       ALLOCATE (T_Pk(mv_env%n_ri_loc, nb_max))
     444           84 :       B_ia_2d(1:mv_env%n_ov, 1:mv_env%n_ri_loc) => mv_env%B_ia
     445              : 
     446          168 :       DO col = first_col, first_col + ncol - 1, nb_max
     447           84 :          nb = MIN(nb_max, first_col + ncol - col)
     448              :          ! three views of one contiguous buffer, bounds-remapping pointer assignment (Fortran 2003):
     449              :          ! Z_ia,k for the exchange DGEMM, Z_3d(a, i, k) per column, Z_wide(a, (i k)) for the wide product
     450           84 :          Z(1:mv_env%n_ov, 1:nb) => Z_buf(1:mv_env%n_ov*nb)
     451           84 :          Z_3d(1:mv_env%virt, 1:mv_env%homo, 1:nb) => Z_buf(1:mv_env%n_ov*nb)
     452           84 :          Z_wide(1:mv_env%virt, 1:mv_env%homo*nb) => Z_buf(1:mv_env%n_ov*nb)
     453           84 :          RA(1:mv_env%n_ov, 1:nb) => RA_buf(1:mv_env%n_ov*nb)
     454           84 :          RA_3d(1:mv_env%virt, 1:mv_env%homo, 1:nb) => RA_buf(1:mv_env%n_ov*nb)
     455           84 :          BabZ_3d(1:mv_env%virt, 1:mv_env%homo, 1:nb) => BabZ_buf(1:mv_env%n_ov*nb)
     456           84 :          BabZ_wide(1:mv_env%virt, 1:mv_env%homo*nb) => BabZ_buf(1:mv_env%n_ov*nb)
     457           84 :          IF (do_B) THEN
     458           66 :             RB(1:mv_env%n_ov, 1:nb) => RB_buf(1:mv_env%n_ov*nb)
     459           66 :             RB_3d(1:mv_env%virt, 1:mv_env%homo, 1:nb) => RB_buf(1:mv_env%n_ov*nb)
     460              :          END IF
     461              : 
     462              :          ! Z_ia,k on every rank, by design: each rank contracts its whole RI slices against all of Z (header of
     463              :          ! bse_matvec_create), so the trial vectors are the one replicated buffer, n_ov x nb per matvec at any rank
     464              :          ! count; every rank owns a contiguous ia range, so the column arrives sorted
     465           84 :          CALL timeset(routineN//"_gather", handle2)
     466          348 :          DO k = 1, nb
     467              :             CALL mv_env%para_env%allgatherv(fm_Z%local_data(1:nrow_local, col + k - 1), Z(:, k), &
     468          348 :                                             mv_env%row_count, mv_env%row_displ)
     469              :          END DO
     470           84 :          CALL timestop(handle2)
     471              : 
     472              :          ! exchange, identical in A Z and B Z: R_ia,k = α sum_P B^P_ia t_Pk, t_Pk = sum_jb B^P_jb Z_jb,k
     473           84 :          CALL timeset(routineN//"_exchange", handle2)
     474           84 :          RA(:, :) = 0.0_dp
     475           84 :          IF (mv_env%alpha /= 0.0_dp .AND. mv_env%n_ri_loc > 0) THEN
     476              :             CALL DGEMM('T', 'N', mv_env%n_ri_loc, nb, mv_env%n_ov, 1.0_dp, B_ia_2d, mv_env%n_ov, Z, mv_env%n_ov, &
     477           84 :                        0.0_dp, T_Pk, mv_env%n_ri_loc)
     478              :             CALL DGEMM('N', 'N', mv_env%n_ov, nb, mv_env%n_ri_loc, mv_env%alpha, B_ia_2d, mv_env%n_ov, T_Pk, mv_env%n_ri_loc, &
     479           84 :                        0.0_dp, RA, mv_env%n_ov)
     480              :          END IF
     481        20338 :          IF (do_B) RB(:, :) = RA(:, :)
     482           84 :          CALL timestop(handle2)
     483              : 
     484           84 :          CALL timeset(routineN//"_W", handle2)
     485           84 :          IF (mv_env%w_fac /= 0.0_dp) THEN
     486         3570 :             DO p = 1, mv_env%n_ri_loc
     487              :                ! A: R_ia,k -= w sum_jb \bar{B}^P_ij B^P_ab Z_jb,k, BabZ_ib,k = sum_b B^P_ab Z_jb,k first
     488              :                CALL DGEMM('T', 'N', mv_env%virt, mv_env%homo*nb, mv_env%virt, 1.0_dp, mv_env%B_ab(:, :, p), mv_env%virt, &
     489         3486 :                           Z_wide, mv_env%virt, 0.0_dp, BabZ_wide, mv_env%virt)
     490        14442 :                DO k = 1, nb
     491              :                   CALL DGEMM('N', 'N', mv_env%virt, mv_env%homo, mv_env%homo, -mv_env%w_fac, BabZ_3d(:, :, k), mv_env%virt, &
     492        14442 :                              mv_env%B_bar_ij(:, :, p), mv_env%homo, 1.0_dp, RA_3d(:, :, k), mv_env%virt)
     493              :                END DO
     494              :                ! B: R_ia,k -= w sum_jb \bar{B}^P_ib B^P_ja Z_jb,k, BjaZ_ab = sum_j B^P_ja Z_jb,k first
     495         3570 :                IF (do_B) THEN
     496        11288 :                   DO k = 1, nb
     497              :                      CALL DGEMM('N', 'T', mv_env%virt, mv_env%virt, mv_env%homo, 1.0_dp, mv_env%B_ia(:, :, p), mv_env%virt, &
     498         8549 :                                 Z_3d(:, :, k), mv_env%virt, 0.0_dp, BjaZ_ab, mv_env%virt)
     499              :                      CALL DGEMM('N', 'N', mv_env%virt, mv_env%homo, mv_env%virt, -mv_env%w_fac, BjaZ_ab, mv_env%virt, &
     500        11288 :                                 mv_env%B_bar_ia(:, :, p), mv_env%virt, 1.0_dp, RB_3d(:, :, k), mv_env%virt)
     501              :                   END DO
     502              :                END IF
     503              :             END DO
     504              :          END IF
     505           84 :          CALL timestop(handle2)
     506              : 
     507              :          ! the sum over the ranks lands on the owner of each row, never replicated
     508           84 :          CALL timeset(routineN//"_reduce", handle2)
     509              :          ! (A Z)_ia,k = (ε_a-ε_i) Z_ia,k + R^A_ia,k and (B Z)_ia,k = R^B_ia,k on the local rows
     510          348 :          DO k = 1, nb
     511          264 :             CALL mv_env%para_env%sum_scatter(RA(:, k:k), R_loc, mv_env%row_count)
     512         6600 :             DO iloc = 1, nrow_local
     513         6336 :                ia = row_indices(iloc)
     514         6600 :                fm_AZ%local_data(iloc, col + k - 1) = R_loc(iloc) + mv_env%eps_diff(ia)*Z(ia, k)
     515              :             END DO
     516          348 :             IF (do_B) THEN
     517          206 :                CALL mv_env%para_env%sum_scatter(RB(:, k:k), R_loc, mv_env%row_count)
     518         5150 :                fm_BZ%local_data(1:nrow_local, col + col_shift_B + k - 1) = R_loc(1:nrow_local)
     519              :             END IF
     520              :          END DO
     521          504 :          CALL timestop(handle2)
     522              :       END DO
     523              : 
     524           84 :       DEALLOCATE (Z_buf, RA_buf, BabZ_buf, T_Pk)
     525           84 :       IF (do_B) THEN
     526           66 :          DEALLOCATE (RB_buf, BjaZ_ab)
     527              :       END IF
     528              : 
     529           84 :       CALL timestop(handle)
     530              : 
     531          252 :    END SUBROUTINE bse_matvec_apply
     532              : 
     533              : ! **************************************************************************************************
     534              : !> \brief Diagonal used by the Davidson correction, either ε_a-ε_i or the full diagonal
     535              : !>        A_ia,ia = ε_a-ε_i + α sum_P (B^P_ia)^2 - w sum_P \bar{B}^P_ii B^P_aa
     536              : !>        with α, w and \bar{B} as in bse_matvec_apply
     537              : !> \param mv_env slabs, prefactors and transition energies
     538              : !> \param precond_kind bse_precond_full_diag or bse_precond_qp_diff
     539              : !> \param diag replicated, size N_ov
     540              : ! **************************************************************************************************
     541            8 :    SUBROUTINE bse_matvec_diagonal(mv_env, precond_kind, diag)
     542              : 
     543              :       TYPE(bse_matvec_env_type), INTENT(IN)              :: mv_env
     544              :       INTEGER, INTENT(IN)                                :: precond_kind
     545              :       REAL(KIND=dp), DIMENSION(:), INTENT(OUT)           :: diag
     546              : 
     547              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'bse_matvec_diagonal'
     548              : 
     549              :       INTEGER                                            :: a, handle, i, ia, p
     550              : 
     551            8 :       CALL timeset(routineN, handle)
     552              : 
     553          392 :       diag(:) = 0.0_dp
     554            8 :       IF (precond_kind == bse_precond_full_diag) THEN
     555          340 :          DO p = 1, mv_env%n_ri_loc
     556         1668 :             DO i = 1, mv_env%homo
     557        17596 :                DO a = 1, mv_env%virt
     558        15936 :                   ia = ia_of_occ_virt(i, a, mv_env%virt)
     559        15936 :                   diag(ia) = diag(ia) + mv_env%alpha*mv_env%B_ia(a, i, p)**2
     560        17264 :                   IF (mv_env%w_fac /= 0.0_dp) THEN
     561        15936 :                      diag(ia) = diag(ia) - mv_env%w_fac*mv_env%B_bar_ij(i, i, p)*mv_env%B_ab(a, a, p)
     562              :                   END IF
     563              :                END DO
     564              :             END DO
     565              :          END DO
     566              :          ! one n_ov vector, summed once per solve; replicated on every rank on purpose
     567          776 :          CALL mv_env%para_env%sum(diag)
     568              :       END IF
     569          392 :       diag(:) = diag(:) + mv_env%eps_diff(:)
     570              : 
     571            8 :       CALL timestop(handle)
     572              : 
     573            8 :    END SUBROUTINE bse_matvec_diagonal
     574              : 
     575              : ! **************************************************************************************************
     576              : !> \brief Exact A (and B) on a list of transitions, replicated on every rank,
     577              : !>        A_kl = δ_kl (ε_a-ε_i) + α sum_P B^P_ia B^P_jb - w sum_P \bar{B}^P_ij B^P_ab
     578              : !>        B_kl =                  α sum_P B^P_ia B^P_jb - w sum_P \bar{B}^P_ib B^P_ja
     579              : !>        with k = (i,a), l = (j,b) and α, w, \bar{B} as in bse_matvec_apply
     580              : !> \param mv_env slabs, prefactors and transition energies
     581              : !> \param ia_list global transition indices ia = (i-1)*virt + a of the block
     582              : !> \param A_sub SIZE(ia_list) x SIZE(ia_list)
     583              : !> \param B_sub same, only formed when present
     584              : ! **************************************************************************************************
     585            2 :    SUBROUTINE bse_matvec_subblock(mv_env, ia_list, A_sub, B_sub)
     586              : 
     587              :       TYPE(bse_matvec_env_type), INTENT(IN)              :: mv_env
     588              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: ia_list
     589              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(OUT)        :: A_sub
     590              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(OUT), &
     591              :          OPTIONAL                                        :: B_sub
     592              : 
     593              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'bse_matvec_subblock'
     594              : 
     595              :       INTEGER                                            :: handle, k, l, n_sub, p
     596            2 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: a_virt, i_occ
     597            2 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: B_Pk
     598              : 
     599            2 :       CALL timeset(routineN, handle)
     600              : 
     601            2 :       n_sub = SIZE(ia_list)
     602           14 :       ALLOCATE (i_occ(n_sub), a_virt(n_sub), B_Pk(MAX(mv_env%n_ri_loc, 1), n_sub))
     603           98 :       DO k = 1, n_sub
     604           96 :          i_occ(k) = occ_of_ia(ia_list(k), mv_env%virt)
     605           98 :          a_virt(k) = virt_of_ia(ia_list(k), mv_env%virt)
     606              :       END DO
     607              : 
     608              :       ! exchange, identical in A and B: α sum_P B_Pk B_Pl with B_Pk = B^P_ia at ia = ia_list(k)
     609         4706 :       A_sub(:, :) = 0.0_dp
     610            2 :       IF (mv_env%alpha /= 0.0_dp .AND. mv_env%n_ri_loc > 0) THEN
     611              :          ! P outermost: the gathered entries of one slice lie within one contiguous (a, i) block
     612           85 :          DO p = 1, mv_env%n_ri_loc
     613         4069 :             DO k = 1, n_sub
     614         4067 :                B_Pk(p, k) = mv_env%B_ia(a_virt(k), i_occ(k), p)
     615              :             END DO
     616              :          END DO
     617              :          CALL DGEMM('T', 'N', n_sub, n_sub, mv_env%n_ri_loc, mv_env%alpha, B_Pk, SIZE(B_Pk, 1), B_Pk, SIZE(B_Pk, 1), &
     618            2 :                     0.0_dp, A_sub, n_sub)
     619              :       END IF
     620            2 :       IF (PRESENT(B_sub)) B_sub(:, :) = A_sub(:, :)
     621              : 
     622            2 :       IF (mv_env%w_fac /= 0.0_dp) THEN
     623           85 :          DO p = 1, mv_env%n_ri_loc
     624         4067 :             DO l = 1, n_sub
     625       195299 :                DO k = 1, n_sub
     626       195216 :                 A_sub(k, l) = A_sub(k, l) - mv_env%w_fac*mv_env%B_bar_ij(i_occ(l), i_occ(k), p)*mv_env%B_ab(a_virt(l), a_virt(k), p)
     627              :                END DO
     628              :             END DO
     629           85 :             IF (PRESENT(B_sub)) THEN
     630            0 :                DO l = 1, n_sub
     631            0 :                   DO k = 1, n_sub
     632            0 :                 B_sub(k, l) = B_sub(k, l) - mv_env%w_fac*mv_env%B_ia(a_virt(k), i_occ(l), p)*mv_env%B_bar_ia(a_virt(l), i_occ(k), p)
     633              :                   END DO
     634              :                END DO
     635              :             END IF
     636              :          END DO
     637              :       END IF
     638              : 
     639         9410 :       CALL mv_env%para_env%sum(A_sub)
     640            2 :       IF (PRESENT(B_sub)) CALL mv_env%para_env%sum(B_sub)
     641           98 :       DO k = 1, n_sub
     642           98 :          A_sub(k, k) = A_sub(k, k) + mv_env%eps_diff(ia_list(k))
     643              :       END DO
     644              : 
     645            2 :       DEALLOCATE (i_occ, a_virt, B_Pk)
     646              : 
     647            2 :       CALL timestop(handle)
     648              : 
     649            2 :    END SUBROUTINE bse_matvec_subblock
     650              : 
     651              : ! **************************************************************************************************
     652              : !> \brief Debug check of the matrix-free application against the explicit matrices A (and B),
     653              : !>        dev = max_ia,k |(A Z)_ia,k - sum_jb A_ia,jb Z_jb,k| over up to eight unit vectors Z_ia,k = δ_ia,k
     654              : !>        and one dense vector Z_ia = sin(ia), the same for B, and max_ia |d_ia - A_ia,ia| for the diagonal
     655              : !> \param mv_env the environment under test
     656              : !> \param fm_A_explicit A from create_A_and_B, N_ov x N_ov on the grid of the caller
     657              : !> \param unit_nr output unit, positive on the writing rank only
     658              : !> \param fm_B_explicit B, present for an ABBA run
     659              : ! **************************************************************************************************
     660            0 :    SUBROUTINE bse_matvec_selfcheck(mv_env, fm_A_explicit, unit_nr, fm_B_explicit)
     661              : 
     662              :       TYPE(bse_matvec_env_type), INTENT(IN)              :: mv_env
     663              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_A_explicit
     664              :       INTEGER, INTENT(IN)                                :: unit_nr
     665              :       TYPE(cp_fm_type), INTENT(IN), OPTIONAL             :: fm_B_explicit
     666              : 
     667              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'bse_matvec_selfcheck'
     668              : 
     669              :       INTEGER                                            :: handle, ia, iloc, k, n_ov, n_unit, ncol, &
     670              :                                                             nrow_local
     671            0 :       INTEGER, DIMENSION(:), POINTER                     :: row_indices
     672              :       REAL(KIND=dp)                                      :: dev_A, dev_B, dev_diag
     673            0 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: diag
     674            0 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: mv_result, ref_matrix, ref_result, Z
     675              :       TYPE(cp_fm_struct_type), POINTER                   :: fm_struct
     676              :       TYPE(cp_fm_type)                                   :: fm_AZ, fm_BZ, fm_Z
     677              : 
     678            0 :       CALL timeset(routineN, handle)
     679              : 
     680            0 :       n_ov = mv_env%n_ov
     681              :       ! eight unit vectors read eight columns of the explicit matrix exactly; the dense column covers the whole contraction
     682            0 :       n_unit = MIN(n_ov, 8)
     683            0 :       dev_B = 0.0_dp
     684            0 :       ncol = n_unit + 1
     685              : 
     686              :       ! trial vectors: e_k for k <= n_unit, then one dense column Z_ia = sin(ia)
     687            0 :       ALLOCATE (Z(n_ov, ncol))
     688            0 :       Z(:, :) = 0.0_dp
     689            0 :       DO k = 1, n_unit
     690            0 :          Z(k, k) = 1.0_dp
     691              :       END DO
     692            0 :       DO ia = 1, n_ov
     693            0 :          Z(ia, ncol) = SIN(REAL(ia, dp))
     694              :       END DO
     695              : 
     696            0 :       CALL bse_matvec_vector_struct(mv_env, ncol, fm_struct)
     697            0 :       CALL cp_fm_create(fm_Z, fm_struct, name="fm_Z_selfcheck")
     698            0 :       CALL cp_fm_create(fm_AZ, fm_struct, name="fm_AZ_selfcheck")
     699            0 :       CALL cp_fm_set_all(fm_AZ, 0.0_dp)
     700            0 :       IF (PRESENT(fm_B_explicit)) THEN
     701            0 :          CALL cp_fm_create(fm_BZ, fm_struct, name="fm_BZ_selfcheck")
     702            0 :          CALL cp_fm_set_all(fm_BZ, 0.0_dp)
     703              :       END IF
     704            0 :       CALL cp_fm_struct_release(fm_struct)
     705              : 
     706            0 :       CALL cp_fm_get_info(fm_Z, nrow_local=nrow_local, row_indices=row_indices)
     707            0 :       DO k = 1, ncol
     708            0 :          DO iloc = 1, nrow_local
     709            0 :             fm_Z%local_data(iloc, k) = Z(row_indices(iloc), k)
     710              :          END DO
     711              :       END DO
     712              : 
     713            0 :       IF (PRESENT(fm_B_explicit)) THEN
     714            0 :          CALL bse_matvec_apply(mv_env, fm_Z, 1, ncol, fm_AZ, fm_BZ)
     715              :       ELSE
     716            0 :          CALL bse_matvec_apply(mv_env, fm_Z, 1, ncol, fm_AZ)
     717              :       END IF
     718              : 
     719            0 :       ALLOCATE (ref_matrix(n_ov, n_ov), mv_result(n_ov, ncol), ref_result(n_ov, ncol), diag(n_ov))
     720              : 
     721            0 :       CALL cp_fm_get_submatrix(fm_A_explicit, ref_matrix)
     722            0 :       CALL cp_fm_get_submatrix(fm_AZ, mv_result)
     723            0 :       ref_result(:, :) = MATMUL(ref_matrix, Z)
     724            0 :       dev_A = MAXVAL(ABS(mv_result - ref_result))
     725            0 :       CALL bse_matvec_diagonal(mv_env, bse_precond_full_diag, diag)
     726            0 :       dev_diag = 0.0_dp
     727            0 :       DO ia = 1, n_ov
     728            0 :          dev_diag = MAX(dev_diag, ABS(diag(ia) - ref_matrix(ia, ia)))
     729              :       END DO
     730              : 
     731            0 :       IF (PRESENT(fm_B_explicit)) THEN
     732            0 :          CALL cp_fm_get_submatrix(fm_B_explicit, ref_matrix)
     733            0 :          CALL cp_fm_get_submatrix(fm_BZ, mv_result)
     734            0 :          ref_result(:, :) = MATMUL(ref_matrix, Z)
     735            0 :          dev_B = MAXVAL(ABS(mv_result - ref_result))
     736              :       END IF
     737              : 
     738            0 :       IF (unit_nr > 0) THEN
     739            0 :          WRITE (unit_nr, '(T2,A10,T13,A,T59,ES22.6)') 'BSE|DEBUG|', &
     740            0 :             'Max deviation matvec vs explicit A', dev_A
     741            0 :          IF (PRESENT(fm_B_explicit)) THEN
     742            0 :             WRITE (unit_nr, '(T2,A10,T13,A,T59,ES22.6)') 'BSE|DEBUG|', &
     743            0 :                'Max deviation matvec vs explicit B', dev_B
     744              :          END IF
     745            0 :          WRITE (unit_nr, '(T2,A10,T13,A,T59,ES22.6)') 'BSE|DEBUG|', &
     746            0 :             'Max deviation diagonal vs explicit A', dev_diag
     747              :       END IF
     748              : 
     749            0 :       DEALLOCATE (Z, ref_matrix, mv_result, ref_result, diag)
     750            0 :       CALL cp_fm_release(fm_Z)
     751            0 :       CALL cp_fm_release(fm_AZ)
     752            0 :       IF (PRESENT(fm_B_explicit)) CALL cp_fm_release(fm_BZ)
     753              : 
     754            0 :       CALL timestop(handle)
     755              : 
     756            0 :    END SUBROUTINE bse_matvec_selfcheck
     757              : 
     758            0 : END MODULE bse_matvec
        

Generated by: LCOV version 2.0-1