LCOV - code coverage report
Current view: top level - src - bse_davidson.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:9ccafda) Lines: 78.2 % 1230 962
Test Date: 2026-10-15 00:55:44 Functions: 87.2 % 47 41

            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 Block Davidson solvers for the lowest excitations of the Bethe-Salpeter equation on top
      10              : !>        of the matrix-free application of A and B: the TDA problem sum_jb A_ia,jb X_jb = Ω X_ia
      11              : !>        (bse_davidson_tda) and the full problem, either as sum_jb [(A+B)(A-B)]_ia,jb x_jb = Ω^2 x_ia
      12              : !>        (bse_davidson_abba_mk) or as the pair sum_jb (A+B)_ia,jb y_jb = Ω x_ia, sum_jb (A-B)_ia,jb x_jb = Ω y_ia
      13              : !>        (bse_davidson_abba_os). Every solver projects its problem onto an orthonormal basis, takes the
      14              : !>        lowest Ritz pairs, extends the basis by the preconditioned residuals of the roots still in need
      15              : !>        and restarts thick at the subspace ceiling; the helpers below are shared by the three drivers
      16              : !> \par History
      17              : !>      09.2026 created [Maximilian Graml]
      18              : ! **************************************************************************************************
      19              : MODULE bse_davidson
      20              : 
      21              :    USE bibliography,                    ONLY: Bai2012,&
      22              :                                               Davidson1975,&
      23              :                                               Fukaya2014,&
      24              :                                               Olsen1988,&
      25              :                                               Stathopoulos1998,&
      26              :                                               Stratmann1998,&
      27              :                                               Vecharynski2017,&
      28              :                                               cite_reference
      29              :    USE bse_full_diag,                   ONLY: create_hermitian_form_of_ABBA
      30              :    USE bse_matvec,                      ONLY: bse_matvec_apply,&
      31              :                                               bse_matvec_diagonal,&
      32              :                                               bse_matvec_env_type,&
      33              :                                               bse_matvec_subblock,&
      34              :                                               bse_matvec_vector_struct,&
      35              :                                               bse_memory_left_GB
      36              :    USE bse_types,                       ONLY: bse_env_type
      37              :    USE cp_blacs_env,                    ONLY: cp_blacs_env_create,&
      38              :                                               cp_blacs_env_release,&
      39              :                                               cp_blacs_env_type
      40              :    USE cp_fm_basic_linalg,              ONLY: cp_fm_column_scale,&
      41              :                                               cp_fm_scale_and_add,&
      42              :                                               cp_fm_transpose
      43              :    USE cp_fm_diag,                      ONLY: choose_eigv_solver
      44              :    USE cp_fm_struct,                    ONLY: cp_fm_struct_create,&
      45              :                                               cp_fm_struct_release,&
      46              :                                               cp_fm_struct_type
      47              :    USE cp_fm_types,                     ONLY: &
      48              :         cp_fm_create, cp_fm_get_info, cp_fm_get_submatrix, cp_fm_release, cp_fm_set_all, &
      49              :         cp_fm_set_submatrix, cp_fm_to_fm, cp_fm_to_fm_submat, cp_fm_type
      50              :    USE input_constants,                 ONLY: bse_iter_and_cond,&
      51              :                                               bse_iter_en_cond,&
      52              :                                               bse_iter_or_cond,&
      53              :                                               bse_iter_res_cond
      54              :    USE kinds,                           ONLY: dp
      55              :    USE machine,                         ONLY: m_walltime
      56              :    USE mathlib,                         ONLY: diamat_all
      57              :    USE message_passing,                 ONLY: mp_para_env_type
      58              :    USE parallel_gemm_api,               ONLY: parallel_gemm
      59              :    USE physcon,                         ONLY: evolt
      60              :    USE util,                            ONLY: sort
      61              : #include "./base/base_uses.f90"
      62              : 
      63              :    IMPLICIT NONE
      64              : 
      65              :    PRIVATE
      66              : 
      67              :    CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'bse_davidson'
      68              : 
      69              :    ! energies closer than this belong to one multiplet (Hartree)
      70              :    REAL(KIND=dp), PARAMETER, PRIVATE :: deg_thresh = 1.0E-6_dp
      71              :    ! a basis that is not orthonormal to this accuracy makes the residuals meaningless
      72              :    REAL(KIND=dp), PARAMETER, PRIVATE :: ortho_tol = 1.0E-8_dp
      73              : 
      74              :    ! outcome of an ABBA solver: solved, or A-B not positive definite on the trial space
      75              :    INTEGER, PARAMETER, PUBLIC :: abba_ok = 0, abba_indefinite = 1
      76              : 
      77              :    ! the three drivers, for the memory estimate
      78              :    INTEGER, PARAMETER, PRIVATE :: driver_tda = 1, driver_mk = 2, driver_os = 3
      79              : 
      80              :    PUBLIC :: bse_davidson_tda, bse_davidson_abba_mk, bse_davidson_abba_os, &
      81              :              bse_davidson_refcheck
      82              : 
      83              : CONTAINS
      84              : 
      85              : ! **************************************************************************************************
      86              : !> \brief Block Davidson with thick restart for the NUM_EXC_EN lowest solutions of
      87              : !>        sum_jb A_ia,jb X_jb^n = Ω^n X_ia^n. With the orthonormal basis Z_ia,m the reduced matrix is
      88              : !>        Ar_mn = sum_ia Z_ia,m (A Z)_ia,n with eigenpairs (θ_k, c_mk), X_ia^k = sum_m Z_ia,m c_mk,
      89              : !>        and the basis grows by the corrections t_ia,k = r_ia,k/(d_ia - θ_k) of the residuals
      90              : !>        r_ia,k = sum_m (A Z)_ia,m c_mk - θ_k X_ia^k, d_ia being the diagonal chosen by PRECONDITIONER.
      91              : !>        Arrays: Ar_mn (the reduced A) in fm_red_A on the process grid, c_mk in coef_ritz, θ_k c_mk in
      92              : !>        coef_ritz_theta
      93              : !> \param mv_env slabs, prefactors and transition energies of the matrix-free A
      94              : !> \param bse_env bse_env carries the BSE_ITERAT settings and BSE_DEBUG_PRINT
      95              : !> \param unit_nr output unit, positive on the writing rank only
      96              : !> \param exc_ens Ω^n in Hartree
      97              : !> \param fm_X X_ia^n on the process grid of mv_env, column n
      98              : ! **************************************************************************************************
      99            4 :    SUBROUTINE bse_davidson_tda(mv_env, bse_env, unit_nr, exc_ens, fm_X)
     100              : 
     101              :       TYPE(bse_matvec_env_type), INTENT(IN)              :: mv_env
     102              :       TYPE(bse_env_type), INTENT(IN)                     :: bse_env
     103              :       INTEGER, INTENT(IN)                                :: unit_nr
     104              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:), &
     105              :          INTENT(OUT)                                     :: exc_ens
     106              :       TYPE(cp_fm_type), INTENT(OUT)                      :: fm_X
     107              : 
     108              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'bse_davidson_tda'
     109              : 
     110              :       INTEGER                                            :: block_size, handle, iter, k, m, m_max, &
     111              :                                                             n_act, n_cand, n_dependent, n_kernel, &
     112              :                                                             n_ov, n_req, n_restart, n_want, nt
     113              :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: selected
     114              :       LOGICAL                                            :: all_conv, has_prev, stalled
     115              :       LOGICAL, ALLOCATABLE, DIMENSION(:)                 :: conv, need
     116              :       REAL(KIND=dp)                                      :: dev, t_iter
     117              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: dE, diag, res, theta, theta_prev, &
     118            4 :                                                             theta_sub
     119              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: coef_restart, coef_restart_cand, &
     120            4 :                                                             coef_ritz, coef_ritz_theta, red_block
     121              :       TYPE(cp_blacs_env_type), POINTER                   :: diag_blacs_env
     122              :       TYPE(cp_fm_struct_type), POINTER                   :: fm_struct
     123              :       TYPE(cp_fm_type)                                   :: fm_AZ, fm_red_A, fm_work, fm_Z
     124              :       TYPE(mp_para_env_type), POINTER                    :: para_env
     125              : 
     126            4 :       CALL timeset(routineN, handle)
     127            4 :       CALL cite_reference(Davidson1975)
     128            4 :       CALL cite_reference(Stathopoulos1998)
     129            4 :       CALL cite_reference(Fukaya2014)
     130              : 
     131            4 :       para_env => mv_env%para_env
     132              :       ! every local_data use below takes whole rows per rank: the npe x 1 grid of bse_matvec_create
     133            4 :       CPASSERT(mv_env%blacs_env%num_pe(2) == 1)
     134              :       ! the trial vectors live on an npe x 1 grid, which is a bad one to diagonalize on
     135            4 :       NULLIFY (diag_blacs_env)
     136            4 :       CALL cp_blacs_env_create(diag_blacs_env, para_env)
     137            4 :       n_ov = mv_env%n_ov
     138            4 :       CALL davidson_sizes(bse_env, n_ov, unit_nr, n_want, n_act, block_size)
     139              : 
     140           12 :       ALLOCATE (diag(n_ov))
     141            4 :       CALL bse_matvec_diagonal(mv_env, bse_env%preconditioner, diag)
     142              : 
     143            4 :       CALL subspace_ceiling(bse_env, mv_env, driver_tda, n_act, block_size, unit_nr, m_max)
     144              : 
     145            4 :       CALL bse_matvec_vector_struct(mv_env, m_max, fm_struct)
     146            4 :       CALL cp_fm_create(fm_Z, fm_struct, name="fm_Z_davidson", set_zero=.TRUE.)
     147            4 :       CALL cp_fm_create(fm_AZ, fm_struct, name="fm_AZ_davidson", set_zero=.TRUE.)
     148            4 :       CALL cp_fm_struct_release(fm_struct)
     149            4 :       CALL bse_matvec_vector_struct(mv_env, MIN(2*n_act, m_max), fm_struct)
     150            4 :       CALL cp_fm_create(fm_work, fm_struct, name="fm_work_davidson", set_zero=.TRUE.)
     151            4 :       CALL cp_fm_struct_release(fm_struct)
     152            4 :       NULLIFY (fm_struct)
     153              :       CALL cp_fm_struct_create(fm_struct, para_env=para_env, context=diag_blacs_env, &
     154            4 :                                nrow_global=m_max, ncol_global=m_max)
     155            4 :       CALL cp_fm_create(fm_red_A, fm_struct, name="fm_red_A_davidson", set_zero=.TRUE.)
     156            4 :       CALL cp_fm_struct_release(fm_struct)
     157              : 
     158              :       ! c_mk and θ_k c_mk, the restart candidates (current and previous Ritz vectors) and the restart basis Q_pk
     159           40 :       ALLOCATE (coef_ritz(m_max, n_act), coef_restart_cand(m_max, 2*n_act), coef_ritz_theta(m_max, n_act))
     160           12 :       ALLOCATE (coef_restart(m_max, 2*n_act))
     161              :       ALLOCATE (theta(m_max), theta_prev(n_act), res(n_act), dE(n_act), conv(n_act), need(n_act), &
     162           44 :                 selected(n_act))
     163            4 :       coef_restart_cand(:, :) = 0.0_dp
     164            4 :       theta_prev(:) = 0.0_dp
     165            4 :       has_prev = .FALSE.
     166            4 :       stalled = .FALSE.
     167            4 :       n_restart = 0
     168            4 :       n_dependent = 0
     169              : 
     170            4 :       IF (bse_env%num_guess_transitions > 0) THEN
     171              :          CALL initial_guess_subblock(mv_env, bse_env, diag, n_act, m_max, deg_thresh, fm_Z, m, theta_sub, &
     172            2 :                                      unit_nr)
     173              :       ELSE
     174            2 :          CALL initial_guess(diag, n_act, m_max, deg_thresh, fm_Z, m)
     175              :       END IF
     176              :       ! both guess routines return orthonormal columns by construction; one Gram matrix checks it
     177            4 :       IF (extension_deviation(fm_Z, fm_Z, 0, m, para_env) > ortho_tol) THEN
     178            0 :          CPABORT("BSE Davidson: the initial guess is not orthonormal")
     179              :       END IF
     180              :       ! (A Z)_ia,m of the guess and Ar_mn = sum_ia Z_ia,m (A Z)_ia,n
     181            4 :       CALL bse_matvec_apply(mv_env, fm_Z, 1, m, fm_AZ)
     182            4 :       n_kernel = m
     183            4 :       CALL reduced_gram_blocks(fm_Z, m, fm_AZ, 1, m, block_size, para_env, fm_red_A)
     184              : 
     185            4 :       CALL print_iteration_header('Block Davidson iterations within the TDA:', unit_nr)
     186              : 
     187            4 :       all_conv = .FALSE.
     188           20 :       DO iter = 1, bse_env%max_iter
     189           20 :          t_iter = m_walltime()
     190              : 
     191              :          ! sum_n Ar_mn c_nk = θ_k c_mk, θ ascending
     192           20 :          CALL solve_reduced(fm_red_A, m, n_act, para_env, diag_blacs_env, theta, coef_ritz)
     193              : 
     194              :          ! thick restart onto the current and the previous Ritz vectors, no kernel application; with the
     195              :          ! restart basis Q_pk in coef_restart, Z_ia,k <- sum_p Z_ia,p Q_pk, A Z alike, Ar_kl <- sum_pq Q_pk Ar_pq Q_ql
     196           20 :          IF (m + block_size > m_max .AND. m > n_act .AND. m < n_ov) THEN
     197          706 :             coef_restart_cand(1:m, 1:n_act) = coef_ritz(1:m, 1:n_act)
     198            6 :             n_cand = n_act
     199            6 :             IF (has_prev) n_cand = 2*n_act
     200            6 :             CALL restart_basis(coef_restart_cand, m, n_cand, 2*n_act, coef_restart, k)
     201            6 :             CALL rotate_in_place(fm_Z, m, coef_restart, k, fm_work, fm_AZ)
     202            6 :             CALL reduced_rotate(fm_red_A, m, coef_restart, k, para_env, diag_blacs_env)
     203            6 :             m = k
     204            6 :             n_restart = n_restart + 1
     205            6 :             CALL solve_reduced(fm_red_A, m, n_act, para_env, diag_blacs_env, theta, coef_ritz)
     206              :          END IF
     207              : 
     208              :          ! r_ia,k = sum_m (A Z)_ia,m c_mk - θ_k sum_m Z_ia,m c_mk for all tracked roots k
     209          164 :          DO k = 1, n_act
     210         1736 :             coef_ritz_theta(1:m, k) = coef_ritz(1:m, k)*theta(k)
     211              :          END DO
     212         1736 :          CALL subspace_rotate(fm_AZ, m, coef_ritz(1:m, 1:n_act), n_act, fm_work, 1.0_dp, 0.0_dp)
     213         1736 :          CALL subspace_rotate(fm_Z, m, coef_ritz_theta(1:m, 1:n_act), n_act, fm_work, -1.0_dp, 1.0_dp)
     214           20 :          CALL column_norms(fm_work, 1, n_act, para_env, res)
     215              : 
     216              :          CALL convergence_step(bse_env, theta, theta_prev, res, m, n_ov, n_want, iter, t_iter, unit_nr, &
     217           20 :                                dE, conv, n_req, need)
     218           62 :          all_conv = .NOT. ANY(need)
     219           20 :          IF (all_conv) EXIT
     220              : 
     221           16 :          IF (iter == bse_env%max_iter) EXIT
     222              : 
     223              :          ! corrections for the lowest unconverged roots, t_ia,k = r_ia,k/(d_ia - θ_k)
     224           16 :          CALL select_roots(bse_env, need, res, MIN(block_size, m_max - m), selected, nt)
     225           16 :          CALL davidson_corrections(fm_work, selected(1:nt), theta, diag, fm_Z, m + 1)
     226           16 :          CALL extend_orthonormal(fm_Z, m, nt, para_env, n_dependent)
     227              :          ! no new vector: with an unchanged basis the next iteration finds dE = 0, which lets the roots that
     228              :          ! only wait for the energy criterion converge; a second empty pass means the residuals cannot improve
     229           16 :          IF (nt == 0 .AND. stalled) THEN
     230            0 :             CPABORT("BSE Davidson: correction vectors vanished before convergence")
     231              :          END IF
     232           16 :          stalled = nt == 0
     233              :          ! post-condition of the extension, one Gram matrix; the residual norms below rely on it
     234           16 :          IF (extension_deviation(fm_Z, fm_Z, m, nt, para_env) > ortho_tol) THEN
     235            0 :             CPABORT("BSE Davidson: the basis lost orthonormality")
     236              :          END IF
     237              : 
     238           16 :          IF (nt > 0) THEN
     239           14 :             CALL bse_matvec_apply(mv_env, fm_Z, m + 1, nt, fm_AZ)
     240           14 :             n_kernel = n_kernel + nt
     241              : 
     242              :             ! new columns Ar_mn = sum_ia Z_ia,m (A Z)_ia,n of the symmetric reduced matrix
     243           56 :             ALLOCATE (red_block(m + nt, nt))
     244           14 :             CALL subspace_gram(fm_Z, 1, m + nt, fm_AZ, m + 1, nt, para_env, red_block)
     245           14 :             CALL reduced_extend(fm_red_A, red_block, m, nt)
     246           14 :             DEALLOCATE (red_block)
     247              :          END IF
     248              : 
     249              :          ! the Ritz vectors of this iteration are the second block of restart candidates
     250         2956 :          coef_restart_cand(:, n_act + 1:2*n_act) = 0.0_dp
     251         1406 :          coef_restart_cand(1:m, n_act + 1:2*n_act) = coef_ritz(1:m, 1:n_act)
     252          130 :          has_prev = .TRUE.
     253          130 :          theta_prev(:) = theta(1:n_act)
     254           40 :          m = m + nt
     255              :       END DO
     256              : 
     257            4 :       IF (.NOT. all_conv) CALL abort_unconverged(theta, res, conv, need, n_req, unit_nr)
     258            4 :       IF (bse_env%bse_debug_print) CALL print_tracked_roots(theta, res, n_want, n_act, unit_nr)
     259              : 
     260            4 :       IF (bse_env%bse_debug_print) THEN
     261            0 :          dev = orthonormality_deviation(fm_Z, fm_Z, m, block_size, para_env)
     262            0 :          IF (unit_nr > 0) THEN
     263            0 :             WRITE (unit_nr, '(T2,A10,T13,A,T63,ES18.6)') 'BSE|DEBUG|', &
     264            0 :                'Max deviation of the basis from orthonormality', dev
     265              :          END IF
     266              :       END IF
     267              : 
     268            4 :       IF (ALLOCATED(theta_sub)) CALL check_guess_bound(theta_sub, theta, n_req, unit_nr)
     269            4 :       CALL print_summary(iter, n_kernel, n_restart, n_want, n_req, n_act, n_ov, unit_nr, n_dependent=n_dependent)
     270              : 
     271           12 :       ALLOCATE (exc_ens(n_want))
     272           18 :       exc_ens(:) = theta(1:n_want)
     273            4 :       CALL bse_matvec_vector_struct(mv_env, n_want, fm_struct)
     274            4 :       CALL cp_fm_create(fm_X, fm_struct, name="fm_X_davidson", set_zero=.TRUE.)
     275            4 :       CALL cp_fm_struct_release(fm_struct)
     276              :       ! X_ia^n = sum_m Z_ia,m c_mn
     277          154 :       CALL subspace_rotate(fm_Z, m, coef_ritz(1:m, 1:n_want), n_want, fm_X, 1.0_dp, 0.0_dp)
     278              : 
     279            4 :       CALL cp_fm_release(fm_Z)
     280            4 :       CALL cp_fm_release(fm_AZ)
     281            4 :       CALL cp_fm_release(fm_work)
     282            4 :       CALL cp_fm_release(fm_red_A)
     283            4 :       CALL cp_blacs_env_release(diag_blacs_env)
     284            0 :       DEALLOCATE (coef_ritz, coef_restart_cand, coef_ritz_theta, coef_restart, theta, theta_prev, res, dE, conv, &
     285            4 :                   need, selected, diag)
     286            4 :       IF (ALLOCATED(theta_sub)) DEALLOCATE (theta_sub)
     287              : 
     288            4 :       CALL timestop(handle)
     289              : 
     290           12 :    END SUBROUTINE bse_davidson_tda
     291              : 
     292              : ! **************************************************************************************************
     293              : !> \brief Block Davidson with thick restart for the NUM_EXC_EN lowest solutions of the full problem,
     294              : !>        written as sum_jb [(A+B)(A-B)]_ia,jb x_jb^n = (Ω^n)^2 x_ia^n with x^n = X^n - Y^n, which is
     295              : !>        symmetric in the inner product <u,v> = sum_ia,jb u_ia (A-B)_ia,jb v_jb. The basis V_ia,m is
     296              : !>        orthonormal in that inner product, W_ia,m = sum_jb (A-B)_ia,jb V_jb,m, and the reduced matrix
     297              : !>        Hr_mn = sum_ia,jb W_ia,m (A+B)_ia,jb W_jb,n has the eigenpairs ((Ω^k)^2, c_mk). Then
     298              : !>        x_ia^k = (Ω^k)^1/2 sum_m V_ia,m c_mk and y_ia^k = (X^k+Y^k)_ia = (Ω^k)^-1/2 sum_m W_ia,m c_mk
     299              : !>        obey sum_ia x_ia^k y_ia^k = 1, the residual is r_ia,k = sum_jb (A+B)_ia,jb y_jb^k - Ω^k x_ia^k
     300              : !>        and the correction t_ia,k = r_ia,k/(d_ia^2 - (Ω^k)^2), d_ia chosen by PRECONDITIONER.
     301              : !>        Arrays: Hr_mn in fm_red_M on the process grid, c_mk in coef_ritz, (Ω^k)^1/2 c_mk in coef_x,
     302              : !>        (Ω^k)^-1/2 c_mk in coef_y, Ω^k (Ω^k)^1/2 c_mk in coef_x_omega
     303              : !> \param mv_env slabs, prefactors and transition energies of the matrix-free A and B
     304              : !> \param bse_env bse_env carries the BSE_ITERAT settings and BSE_DEBUG_PRINT
     305              : !> \param unit_nr output unit, positive on the writing rank only
     306              : !> \param exc_ens Ω^n in Hartree
     307              : !> \param fm_X X_ia^n on the process grid of mv_env, column n; created only for abba_ok
     308              : !> \param fm_Y Y_ia^n, as fm_X
     309              : !> \param abba_status abba_indefinite if A-B is not positive definite on the trial space
     310              : !> \param ab_margin smallest eigenvalue of A-B on the trial space in Hartree
     311              : !> \param fm_X_tda converged TDA vectors as initial guess
     312              : ! **************************************************************************************************
     313            2 :    SUBROUTINE bse_davidson_abba_mk(mv_env, bse_env, unit_nr, exc_ens, fm_X, fm_Y, abba_status, &
     314              :                                    ab_margin, fm_X_tda)
     315              : 
     316              :       TYPE(bse_matvec_env_type), INTENT(IN)              :: mv_env
     317              :       TYPE(bse_env_type), INTENT(IN)                     :: bse_env
     318              :       INTEGER, INTENT(IN)                                :: unit_nr
     319              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:), &
     320              :          INTENT(OUT)                                     :: exc_ens
     321              :       TYPE(cp_fm_type), INTENT(OUT)                      :: fm_X, fm_Y
     322              :       INTEGER, INTENT(OUT)                               :: abba_status
     323              :       REAL(KIND=dp), INTENT(OUT)                         :: ab_margin
     324              :       TYPE(cp_fm_type), INTENT(IN), OPTIONAL             :: fm_X_tda
     325              : 
     326              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'bse_davidson_abba_mk'
     327              : 
     328              :       INTEGER :: block_size, guess_col, handle, iter, k, m, m_max, n_act, n_cand, n_dependent, &
     329              :          n_guess_cols, n_kernel, n_ov, n_req, n_restart, n_seed, n_want, nrow_local, nt, nt_guess
     330            2 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: selected
     331              :       LOGICAL                                            :: all_conv, has_prev, stalled
     332            2 :       LOGICAL, ALLOCATABLE, DIMENSION(:)                 :: conv, need
     333              :       REAL(KIND=dp)                                      :: dev, t_iter
     334            2 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: dE, diag, diag_sq, omega, omega_prev, &
     335            2 :                                                             omega_sq, res, theta, theta_sub
     336              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: coef_restart, coef_restart_cand, &
     337            2 :                                                             coef_ritz, coef_x, coef_x_omega, &
     338            2 :                                                             coef_y, red_block
     339              :       TYPE(cp_blacs_env_type), POINTER                   :: diag_blacs_env
     340              :       TYPE(cp_fm_struct_type), POINTER                   :: fm_struct
     341              :       TYPE(cp_fm_type)                                   :: fm_metric, fm_metric_vec, fm_MW, &
     342              :                                                             fm_red_M, fm_scratch, fm_V, fm_W, &
     343              :                                                             fm_work
     344              :       TYPE(mp_para_env_type), POINTER                    :: para_env
     345              : 
     346            2 :       CALL timeset(routineN, handle)
     347            2 :       CALL cite_reference(Davidson1975)
     348            2 :       CALL cite_reference(Vecharynski2017)
     349            2 :       CALL cite_reference(Bai2012)
     350            2 :       CALL cite_reference(Stathopoulos1998)
     351            2 :       CALL cite_reference(Fukaya2014)
     352              : 
     353            2 :       abba_status = abba_ok
     354            2 :       ab_margin = 0.0_dp
     355            2 :       para_env => mv_env%para_env
     356              :       ! every local_data use below takes whole rows per rank: the npe x 1 grid of bse_matvec_create
     357            2 :       CPASSERT(mv_env%blacs_env%num_pe(2) == 1)
     358              :       ! the trial vectors live on an npe x 1 grid, which is a bad one to diagonalize on
     359            2 :       NULLIFY (diag_blacs_env)
     360            2 :       CALL cp_blacs_env_create(diag_blacs_env, para_env)
     361            2 :       n_ov = mv_env%n_ov
     362            2 :       CALL davidson_sizes(bse_env, n_ov, unit_nr, n_want, n_act, block_size)
     363              : 
     364            6 :       ALLOCATE (diag(n_ov))
     365            2 :       CALL bse_matvec_diagonal(mv_env, bse_env%preconditioner, diag)
     366              : 
     367            2 :       CALL subspace_ceiling(bse_env, mv_env, driver_mk, n_act, block_size, unit_nr, m_max)
     368              : 
     369            2 :       CALL bse_matvec_vector_struct(mv_env, m_max, fm_struct)
     370            2 :       CALL cp_fm_create(fm_V, fm_struct, name="fm_V_davidson", set_zero=.TRUE.)
     371            2 :       CALL cp_fm_create(fm_W, fm_struct, name="fm_W_davidson", set_zero=.TRUE.)
     372            2 :       CALL cp_fm_create(fm_MW, fm_struct, name="fm_MW_davidson", set_zero=.TRUE.)
     373            2 :       CALL cp_fm_struct_release(fm_struct)
     374            2 :       CALL bse_matvec_vector_struct(mv_env, MIN(2*n_act, m_max), fm_struct)
     375            2 :       CALL cp_fm_create(fm_work, fm_struct, name="fm_work_davidson", set_zero=.TRUE.)
     376            2 :       CALL cp_fm_struct_release(fm_struct)
     377            2 :       CALL bse_matvec_vector_struct(mv_env, block_size, fm_struct)
     378            2 :       CALL cp_fm_create(fm_scratch, fm_struct, name="fm_scratch_davidson", set_zero=.TRUE.)
     379            2 :       CALL cp_fm_struct_release(fm_struct)
     380            2 :       NULLIFY (fm_struct)
     381              :       CALL cp_fm_struct_create(fm_struct, para_env=para_env, context=diag_blacs_env, &
     382            2 :                                nrow_global=m_max, ncol_global=m_max)
     383            2 :       CALL cp_fm_create(fm_red_M, fm_struct, name="fm_red_M_davidson", set_zero=.TRUE.)
     384            2 :       CALL cp_fm_struct_release(fm_struct)
     385            2 :       CALL cp_fm_get_info(fm_V, nrow_local=nrow_local)
     386              : 
     387              :       ! c_mk, the restart candidates and basis, then (Ω^k)^1/2 c_mk, (Ω^k)^-1/2 c_mk and Ω^k (Ω^k)^1/2 c_mk
     388           20 :       ALLOCATE (coef_ritz(m_max, n_act), coef_restart_cand(m_max, 2*n_act), coef_restart(m_max, 2*n_act))
     389           14 :       ALLOCATE (coef_x(m_max, n_act), coef_y(m_max, n_act), coef_x_omega(m_max, n_act))
     390              :       ALLOCATE (theta(m_max), omega(n_act), omega_prev(n_act), res(n_act), dE(n_act), conv(n_act), &
     391           24 :                 need(n_act), selected(n_act))
     392            2 :       coef_restart_cand(:, :) = 0.0_dp
     393            2 :       omega_prev(:) = 0.0_dp
     394            2 :       has_prev = .FALSE.
     395            2 :       stalled = .FALSE.
     396            2 :       all_conv = .FALSE.
     397            2 :       n_restart = 0
     398            2 :       n_kernel = 0
     399            2 :       n_dependent = 0
     400              : 
     401              :       ! guess: the TDA vectors if given, then the eigenvectors of the exact guess block or unit
     402              :       ! vectors on the lowest entries of the diagonal
     403            2 :       n_seed = 0
     404            2 :       IF (PRESENT(fm_X_tda)) THEN
     405            0 :          CALL cp_fm_get_info(fm_X_tda, ncol_global=n_seed)
     406            0 :          n_seed = MIN(n_seed, n_act)
     407            0 :          CALL cp_fm_to_fm(fm_X_tda, fm_V, n_seed)
     408              :       END IF
     409            2 :       IF (bse_env%num_guess_transitions > 0) THEN
     410              :          CALL initial_guess_subblock(mv_env, bse_env, diag, n_act, m_max, deg_thresh, fm_V, n_guess_cols, theta_sub, &
     411            0 :                                      unit_nr, n_seed, abba_status, ab_margin)
     412              :       ELSE
     413            2 :          CALL initial_guess(diag, n_act, m_max, deg_thresh, fm_V, n_guess_cols, n_seed)
     414              :       END IF
     415              :       ! the corrections divide by d_ia^2 - (Ω^k)^2
     416            4 :       ALLOCATE (diag_sq(n_ov))
     417           98 :       diag_sq(:) = diag(:)**2
     418              : 
     419              :       ! the guess enters the basis in blocks of the scratch width
     420            2 :       m = 0
     421            2 :       guess_col = 1
     422            4 :       DO WHILE (guess_col <= n_guess_cols)
     423            2 :          nt_guess = MIN(block_size, n_guess_cols - guess_col + 1)
     424            2 :          nt = nt_guess
     425            2 :          IF (guess_col > m + 1) THEN
     426            0 :             CALL cp_fm_to_fm(fm_V, fm_V, nt, guess_col, m + 1)
     427              :          END IF
     428              :          CALL extend_basis_mk(mv_env, fm_V, fm_W, fm_MW, fm_scratch, m, nt, n_kernel, n_dependent, abba_status, &
     429            2 :                               ab_margin)
     430            2 :          IF (abba_status /= abba_ok) EXIT
     431            2 :          guess_col = guess_col + nt_guess
     432            2 :          m = m + nt
     433              :       END DO
     434              : 
     435            2 :       IF (abba_status == abba_ok) THEN
     436            2 :          fm_V%local_data(1:nrow_local, m + 1:n_guess_cols) = 0.0_dp
     437              :          ! Hr_mn = sum_ia W_ia,m (M W)_ia,n on the guess basis
     438            2 :          CALL reduced_gram_blocks(fm_W, m, fm_MW, 1, m, block_size, para_env, fm_red_M)
     439              : 
     440            2 :          CALL print_iteration_header('Block Davidson iterations for ABBA, (A+B)(A-B) x = E^2 x:', unit_nr)
     441              : 
     442           16 :          DO iter = 1, bse_env%max_iter
     443           16 :             t_iter = m_walltime()
     444              : 
     445              :             ! sum_n Hr_mn c_nk = θ_k c_mk with θ_k = (Ω^k)^2, ascending
     446           16 :             CALL solve_reduced(fm_red_M, m, n_act, para_env, diag_blacs_env, theta, coef_ritz)
     447              : 
     448              :             ! thick restart: V, W and MW are rotated alike by Q_pk in coef_restart, which keeps
     449              :             ! sum_ia V_ia,m W_ia,n = δ_mn, and Hr_kl <- sum_pq Q_pk Hr_pq Q_ql
     450           16 :             IF (m + block_size > m_max .AND. m > n_act .AND. m < n_ov) THEN
     451          324 :                coef_restart_cand(1:m, 1:n_act) = coef_ritz(1:m, 1:n_act)
     452            2 :                n_cand = n_act
     453            2 :                IF (has_prev) n_cand = 2*n_act
     454            2 :                CALL restart_basis(coef_restart_cand, m, n_cand, 2*n_act, coef_restart, k)
     455            2 :                CALL rotate_in_place(fm_V, m, coef_restart, k, fm_work, fm_W, fm_MW)
     456            2 :                CALL reduced_rotate(fm_red_M, m, coef_restart, k, para_env, diag_blacs_env)
     457            2 :                m = k
     458            2 :                n_restart = n_restart + 1
     459            2 :                CALL solve_reduced(fm_red_M, m, n_act, para_env, diag_blacs_env, theta, coef_ritz)
     460              :             END IF
     461              : 
     462              :             ! a Ritz value bounds an eigenvalue of (A+B)(A-B) from above
     463           16 :             IF (theta(1) <= 0.0_dp) THEN
     464              :                CALL cp_abort(__LOCATION__, &
     465              :                              "BSE Davidson (MK_DAVIDSON): negative squared excitation energy. Matrix "// &
     466            0 :                              "(A+B) is not positive definite, or the basis lost its orthonormality.")
     467              :             END IF
     468          128 :             omega(:) = SQRT(theta(1:n_act))
     469              : 
     470              :             ! r_ia,k = sum_m (M W)_ia,m c_mk (Ω^k)^-1/2 - Ω^k (Ω^k)^1/2 sum_m V_ia,m c_mk
     471          128 :             DO k = 1, n_act
     472         1792 :                coef_y(1:m, k) = coef_ritz(1:m, k)/SQRT(omega(k))
     473         1808 :                coef_x_omega(1:m, k) = coef_ritz(1:m, k)*omega(k)*SQRT(omega(k))
     474              :             END DO
     475         1808 :             CALL subspace_rotate(fm_MW, m, coef_y(1:m, 1:n_act), n_act, fm_work, 1.0_dp, 0.0_dp)
     476         1808 :             CALL subspace_rotate(fm_V, m, coef_x_omega(1:m, 1:n_act), n_act, fm_work, -1.0_dp, 1.0_dp)
     477           16 :             CALL column_norms(fm_work, 1, n_act, para_env, res)
     478              : 
     479              :             CALL convergence_step(bse_env, omega, omega_prev, res, m, n_ov, n_want, iter, t_iter, unit_nr, &
     480           16 :                                   dE, conv, n_req, need)
     481           38 :             all_conv = .NOT. ANY(need)
     482           16 :             IF (all_conv) EXIT
     483              : 
     484           14 :             IF (iter == bse_env%max_iter) EXIT
     485              : 
     486              :             ! corrections t_ia,k = r_ia,k/(d_ia^2 - (Ω^k)^2) for the lowest unconverged roots
     487           14 :             CALL select_roots(bse_env, need, res, MIN(block_size, m_max - m), selected, nt)
     488           14 :             CALL davidson_corrections(fm_work, selected(1:nt), theta, diag_sq, fm_V, m + 1)
     489              :             CALL extend_basis_mk(mv_env, fm_V, fm_W, fm_MW, fm_scratch, m, nt, n_kernel, n_dependent, abba_status, &
     490           14 :                                  ab_margin)
     491           14 :             IF (abba_status /= abba_ok) EXIT
     492              :             ! no new vector: with an unchanged basis the next iteration finds dE = 0, which lets the roots that
     493              :             ! only wait for the energy criterion converge; a second empty pass means the residuals cannot improve
     494           14 :             IF (nt == 0 .AND. stalled) THEN
     495            0 :                CPABORT("BSE Davidson: correction vectors vanished before convergence")
     496              :             END IF
     497           14 :             stalled = nt == 0
     498              : 
     499           14 :             IF (nt > 0) THEN
     500              :                ! new columns Hr_mn = sum_ia W_ia,m (M W)_ia,n of the symmetric reduced matrix
     501           56 :                ALLOCATE (red_block(m + nt, nt))
     502           14 :                CALL subspace_gram(fm_W, 1, m + nt, fm_MW, m + 1, nt, para_env, red_block)
     503           14 :                CALL reduced_extend(fm_red_M, red_block, m, nt)
     504           14 :                DEALLOCATE (red_block)
     505              :             END IF
     506              : 
     507              :             ! the Ritz vectors of this iteration are the second block of restart candidates
     508         2856 :             coef_restart_cand(:, n_act + 1:2*n_act) = 0.0_dp
     509         1540 :             coef_restart_cand(1:m, n_act + 1:2*n_act) = coef_ritz(1:m, 1:n_act)
     510          112 :             has_prev = .TRUE.
     511          112 :             omega_prev(:) = omega(:)
     512           32 :             m = m + nt
     513              :          END DO
     514              :       END IF
     515              : 
     516            2 :       IF (abba_status == abba_ok) THEN
     517            2 :          IF (.NOT. all_conv) CALL abort_unconverged(omega, res, conv, need, n_req, unit_nr)
     518            2 :          IF (bse_env%bse_debug_print) CALL print_tracked_roots(omega, res, n_want, n_act, unit_nr)
     519              : 
     520            2 :          IF (bse_env%bse_debug_print) THEN
     521            0 :             dev = orthonormality_deviation(fm_V, fm_W, m, block_size, para_env)
     522            0 :             IF (unit_nr > 0) THEN
     523            0 :                WRITE (unit_nr, '(T2,A10,T13,A,T63,ES18.6)') 'BSE|DEBUG|', &
     524            0 :                   'Max deviation of the basis from orthonormality', dev
     525              :             END IF
     526              :          END IF
     527              : 
     528              :          ! with sum_ia V_ia,m W_ia,n = δ_mn the smallest eigenvalue of A-B on the span of V is the
     529              :          ! inverse of the largest eigenvalue of the metric sum_ia V_ia,m V_ia,n
     530            2 :          NULLIFY (fm_struct)
     531              :          CALL cp_fm_struct_create(fm_struct, para_env=para_env, context=diag_blacs_env, &
     532            2 :                                   nrow_global=m, ncol_global=m)
     533            2 :          CALL cp_fm_create(fm_metric, fm_struct, name="bse_basis_metric")
     534            2 :          CALL cp_fm_create(fm_metric_vec, fm_struct, name="bse_basis_metric_vectors")
     535            2 :          CALL cp_fm_struct_release(fm_struct)
     536            2 :          CALL reduced_gram_blocks(fm_V, m, fm_V, 1, m, block_size, para_env, fm_metric)
     537            6 :          ALLOCATE (omega_sq(m))
     538            2 :          omega_sq(:) = 0.0_dp
     539            2 :          CALL choose_eigv_solver(fm_metric, fm_metric_vec, omega_sq)
     540            2 :          ab_margin = 1.0_dp/omega_sq(m)
     541            2 :          DEALLOCATE (omega_sq)
     542            2 :          CALL cp_fm_release(fm_metric)
     543            2 :          CALL cp_fm_release(fm_metric_vec)
     544              : 
     545            2 :          IF (ALLOCATED(theta_sub)) CALL check_guess_bound(theta_sub, omega, n_req, unit_nr)
     546            2 :          CALL print_summary(iter, n_kernel, n_restart, n_want, n_req, n_act, n_ov, unit_nr, ab_margin, n_dependent)
     547              : 
     548              :          ! x_ia^n = (Ω^n)^1/2 sum_m V_ia,m c_mn, y_ia^n = (Ω^n)^-1/2 sum_m W_ia,m c_mn,
     549              :          ! X_ia^n = (y_ia^n + x_ia^n)/2, Y_ia^n = (y_ia^n - x_ia^n)/2
     550            6 :          ALLOCATE (exc_ens(n_want))
     551            8 :          exc_ens(:) = omega(1:n_want)
     552            8 :          DO k = 1, n_want
     553          114 :             coef_x(1:m, k) = coef_ritz(1:m, k)*SQRT(omega(k))
     554          116 :             coef_y(1:m, k) = coef_ritz(1:m, k)/SQRT(omega(k))
     555              :          END DO
     556            2 :          CALL bse_matvec_vector_struct(mv_env, n_want, fm_struct)
     557            2 :          CALL cp_fm_create(fm_X, fm_struct, name="fm_X_davidson", set_zero=.TRUE.)
     558            2 :          CALL cp_fm_create(fm_Y, fm_struct, name="fm_Y_davidson", set_zero=.TRUE.)
     559            2 :          CALL cp_fm_struct_release(fm_struct)
     560          116 :          CALL subspace_rotate(fm_W, m, coef_y(1:m, 1:n_want), n_want, fm_X, 0.5_dp, 0.0_dp)
     561          116 :          CALL subspace_rotate(fm_V, m, coef_x(1:m, 1:n_want), n_want, fm_X, 0.5_dp, 1.0_dp)
     562          116 :          CALL subspace_rotate(fm_W, m, coef_y(1:m, 1:n_want), n_want, fm_Y, 0.5_dp, 0.0_dp)
     563          118 :          CALL subspace_rotate(fm_V, m, coef_x(1:m, 1:n_want), n_want, fm_Y, -0.5_dp, 1.0_dp)
     564              :       END IF
     565              : 
     566            2 :       CALL cp_fm_release(fm_V)
     567            2 :       CALL cp_fm_release(fm_W)
     568            2 :       CALL cp_fm_release(fm_MW)
     569            2 :       CALL cp_fm_release(fm_work)
     570            2 :       CALL cp_fm_release(fm_scratch)
     571            2 :       CALL cp_fm_release(fm_red_M)
     572            2 :       CALL cp_blacs_env_release(diag_blacs_env)
     573            0 :       DEALLOCATE (coef_ritz, coef_restart_cand, coef_x, coef_y, coef_x_omega, coef_restart, theta, omega, omega_prev, &
     574            2 :                   res, dE, conv, need, selected, diag, diag_sq)
     575            2 :       IF (ALLOCATED(theta_sub)) DEALLOCATE (theta_sub)
     576              : 
     577            2 :       CALL timestop(handle)
     578              : 
     579            8 :    END SUBROUTINE bse_davidson_abba_mk
     580              : 
     581              : ! **************************************************************************************************
     582              : !> \brief Block Davidson with thick restart for the NUM_EXC_EN lowest solutions of the full problem
     583              : !>        in the paired form of Olsen and Stratmann: with x^n = X^n - Y^n, y^n = X^n + Y^n, M = A+B and
     584              : !>        K = A-B, sum_jb M_ia,jb y_jb^n = Ω^n x_ia^n and sum_jb K_ia,jb x_jb^n = Ω^n y_ia^n. One
     585              : !>        orthonormal basis b_ia,m carries both vectors: Mr_mn = sum_ia b_ia,m (M b)_ia,n and
     586              : !>        Kr_mn = sum_ia b_ia,m (K b)_ia,n are solved by solve_reduced_paired for Ω^k, R_mk and L_mk with
     587              : !>        y_ia^k = sum_m b_ia,m R_mk, x_ia^k = sum_m b_ia,m L_mk and sum_ia x_ia^k y_ia^k = 1. The basis
     588              : !>        grows by the corrections t_ia,k = r_ia,k/(d_ia - Ω^k) of both residuals,
     589              : !>        rR_ia,k = sum_m (M b)_ia,m R_mk - Ω^k x_ia^k and rL_ia,k = sum_m (K b)_ia,m L_mk - Ω^k y_ia^k,
     590              : !>        d_ia chosen by PRECONDITIONER; the rR block enters before the rL block, which is nearly
     591              : !>        parallel to it for a small B. Arrays: Mr_mn and Kr_mn in fm_red_M and fm_red_K on the process
     592              : !>        grid, R_mk in coef_right, L_mk in coef_left
     593              : !> \param mv_env slabs, prefactors and transition energies of the matrix-free A and B
     594              : !> \param bse_env bse_env carries the BSE_ITERAT settings and BSE_DEBUG_PRINT
     595              : !> \param unit_nr output unit, positive on the writing rank only
     596              : !> \param exc_ens Ω^n in Hartree
     597              : !> \param fm_X X_ia^n on the process grid of mv_env, column n; created only for abba_ok
     598              : !> \param fm_Y Y_ia^n, as fm_X
     599              : !> \param abba_status abba_indefinite if A-B is not positive definite on the trial space
     600              : !> \param ab_margin smallest eigenvalue of A-B on the trial space in Hartree
     601              : !> \param fm_X_tda converged TDA vectors as initial guess, x = y = X
     602              : ! **************************************************************************************************
     603            2 :    SUBROUTINE bse_davidson_abba_os(mv_env, bse_env, unit_nr, exc_ens, fm_X, fm_Y, abba_status, &
     604              :                                    ab_margin, fm_X_tda)
     605              : 
     606              :       TYPE(bse_matvec_env_type), INTENT(IN)              :: mv_env
     607              :       TYPE(bse_env_type), INTENT(IN)                     :: bse_env
     608              :       INTEGER, INTENT(IN)                                :: unit_nr
     609              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:), &
     610              :          INTENT(OUT)                                     :: exc_ens
     611              :       TYPE(cp_fm_type), INTENT(OUT)                      :: fm_X, fm_Y
     612              :       INTEGER, INTENT(OUT)                               :: abba_status
     613              :       REAL(KIND=dp), INTENT(OUT)                         :: ab_margin
     614              :       TYPE(cp_fm_type), INTENT(IN), OPTIONAL             :: fm_X_tda
     615              : 
     616              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'bse_davidson_abba_os'
     617              : 
     618              :       INTEGER :: block_size, guess_col, handle, iter, k, m, m_max, n_act, n_cand, n_dependent, &
     619              :          n_guess_cols, n_kernel, n_ov, n_pair, n_req, n_restart, n_seed, n_want, nrow_local, nt, &
     620              :          nt_guess, nt_L, nt_R, nt_sel
     621            2 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: selected
     622              :       LOGICAL                                            :: all_conv, has_prev, stalled
     623            2 :       LOGICAL, ALLOCATABLE, DIMENSION(:)                 :: conv, need
     624              :       REAL(KIND=dp)                                      :: dev, kr_min, t_iter
     625            2 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: dE, diag, omega, omega_prev, res, res_L, &
     626            2 :                                                             theta_sub
     627              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: coef_left, coef_restart, &
     628              :                                                             coef_restart_cand, coef_right, &
     629            2 :                                                             coef_work, red_block_K, red_block_M
     630              :       TYPE(cp_blacs_env_type), POINTER                   :: diag_blacs_env
     631              :       TYPE(cp_fm_struct_type), POINTER                   :: fm_struct
     632              :       TYPE(cp_fm_type)                                   :: fm_b, fm_Kb, fm_Mb, fm_red_K, fm_red_M, &
     633              :                                                             fm_scratch, fm_work
     634              :       TYPE(mp_para_env_type), POINTER                    :: para_env
     635              : 
     636            2 :       CALL timeset(routineN, handle)
     637            2 :       CALL cite_reference(Davidson1975)
     638            2 :       CALL cite_reference(Olsen1988)
     639            2 :       CALL cite_reference(Stratmann1998)
     640            2 :       CALL cite_reference(Bai2012)
     641            2 :       CALL cite_reference(Stathopoulos1998)
     642            2 :       CALL cite_reference(Fukaya2014)
     643              : 
     644            2 :       abba_status = abba_ok
     645            2 :       ab_margin = 0.0_dp
     646            2 :       para_env => mv_env%para_env
     647              :       ! every local_data use below takes whole rows per rank: the npe x 1 grid of bse_matvec_create
     648            2 :       CPASSERT(mv_env%blacs_env%num_pe(2) == 1)
     649              :       ! the trial vectors live on an npe x 1 grid, which is a bad one to diagonalize on
     650            2 :       NULLIFY (diag_blacs_env)
     651            2 :       CALL cp_blacs_env_create(diag_blacs_env, para_env)
     652            2 :       n_ov = mv_env%n_ov
     653            2 :       CALL davidson_sizes(bse_env, n_ov, unit_nr, n_want, n_act, block_size)
     654              : 
     655            6 :       ALLOCATE (diag(n_ov))
     656            2 :       CALL bse_matvec_diagonal(mv_env, bse_env%preconditioner, diag)
     657              : 
     658            2 :       CALL subspace_ceiling(bse_env, mv_env, driver_os, n_act, block_size, unit_nr, m_max)
     659              : 
     660            2 :       CALL bse_matvec_vector_struct(mv_env, m_max, fm_struct)
     661            2 :       CALL cp_fm_create(fm_b, fm_struct, name="fm_b_davidson", set_zero=.TRUE.)
     662            2 :       CALL cp_fm_create(fm_Mb, fm_struct, name="fm_Mb_davidson", set_zero=.TRUE.)
     663            2 :       CALL cp_fm_create(fm_Kb, fm_struct, name="fm_Kb_davidson", set_zero=.TRUE.)
     664            2 :       CALL cp_fm_struct_release(fm_struct)
     665              :       ! both residual blocks, or the restart basis
     666            2 :       CALL bse_matvec_vector_struct(mv_env, MAX(2*n_act, MIN(4*n_act, m_max)), fm_struct)
     667            2 :       CALL cp_fm_create(fm_work, fm_struct, name="fm_work_davidson", set_zero=.TRUE.)
     668            2 :       CALL cp_fm_struct_release(fm_struct)
     669            2 :       CALL bse_matvec_vector_struct(mv_env, 2*block_size, fm_struct)
     670            2 :       CALL cp_fm_create(fm_scratch, fm_struct, name="fm_scratch_davidson", set_zero=.TRUE.)
     671            2 :       CALL cp_fm_struct_release(fm_struct)
     672            2 :       NULLIFY (fm_struct)
     673              :       CALL cp_fm_struct_create(fm_struct, para_env=para_env, context=diag_blacs_env, &
     674            2 :                                nrow_global=m_max, ncol_global=m_max)
     675            2 :       CALL cp_fm_create(fm_red_M, fm_struct, name="fm_red_M_davidson", set_zero=.TRUE.)
     676            2 :       CALL cp_fm_create(fm_red_K, fm_struct, name="fm_red_K_davidson", set_zero=.TRUE.)
     677            2 :       CALL cp_fm_struct_release(fm_struct)
     678            2 :       CALL cp_fm_get_info(fm_b, nrow_local=nrow_local)
     679              : 
     680              :       ! R_mk, L_mk and a work block, then the restart candidates (current and previous pairs) and the basis Q_pk
     681           16 :       ALLOCATE (coef_right(m_max, n_act), coef_left(m_max, n_act), coef_work(m_max, n_act))
     682           14 :       ALLOCATE (coef_restart_cand(m_max, 4*n_act), coef_restart(m_max, 4*n_act))
     683              :       ALLOCATE (omega(n_act), omega_prev(n_act), res(n_act), res_L(n_act), dE(n_act), conv(n_act), &
     684           22 :                 need(n_act), selected(n_act))
     685            2 :       coef_restart_cand(:, :) = 0.0_dp
     686            2 :       omega_prev(:) = 0.0_dp
     687            2 :       kr_min = 0.0_dp
     688            2 :       has_prev = .FALSE.
     689            2 :       stalled = .FALSE.
     690            2 :       all_conv = .FALSE.
     691            2 :       n_restart = 0
     692            2 :       n_kernel = 0
     693            2 :       n_dependent = 0
     694              : 
     695              :       ! guess: the TDA vectors if given (each a pair by itself), then the pairs x_k, y_k of the exact
     696              :       ! guess block or unit vectors on the lowest entries of the diagonal
     697            2 :       n_seed = 0
     698            2 :       IF (PRESENT(fm_X_tda)) THEN
     699            0 :          CALL cp_fm_get_info(fm_X_tda, ncol_global=n_seed)
     700            0 :          n_seed = MIN(n_seed, n_act)
     701            0 :          CALL cp_fm_to_fm(fm_X_tda, fm_b, n_seed)
     702              :       END IF
     703            2 :       n_pair = 0
     704            2 :       IF (bse_env%num_guess_transitions > 0) THEN
     705              :          CALL initial_guess_subblock(mv_env, bse_env, diag, n_act, m_max, deg_thresh, fm_b, n_guess_cols, theta_sub, &
     706            0 :                                      unit_nr, n_seed, abba_status, ab_margin, n_pair)
     707              :       ELSE
     708            2 :          CALL initial_guess(diag, n_act, m_max, deg_thresh, fm_b, n_guess_cols, n_seed)
     709              :       END IF
     710            2 :       n_guess_cols = n_guess_cols + n_pair
     711              : 
     712              :       ! the guess enters the basis in blocks of the scratch width
     713            2 :       m = 0
     714            2 :       guess_col = 1
     715            4 :       DO WHILE (guess_col <= n_guess_cols)
     716            2 :          nt_guess = MIN(2*block_size, n_guess_cols - guess_col + 1)
     717            2 :          nt = nt_guess
     718            2 :          IF (guess_col > m + 1) THEN
     719            0 :             CALL cp_fm_to_fm(fm_b, fm_b, nt, guess_col, m + 1)
     720              :          END IF
     721            2 :          CALL extend_basis_os(mv_env, fm_b, fm_Mb, fm_Kb, fm_scratch, m, nt, n_kernel, n_dependent)
     722            2 :          guess_col = guess_col + nt_guess
     723            2 :          m = m + nt
     724              :       END DO
     725              : 
     726            2 :       IF (abba_status == abba_ok) THEN
     727            2 :          fm_b%local_data(1:nrow_local, m + 1:n_guess_cols) = 0.0_dp
     728              :          ! Mr_mn = sum_ia b_ia,m (M b)_ia,n and Kr_mn = sum_ia b_ia,m (K b)_ia,n on the guess basis
     729            2 :          CALL reduced_gram_blocks(fm_b, m, fm_Mb, 1, m, block_size, para_env, fm_red_M)
     730            2 :          CALL reduced_gram_blocks(fm_b, m, fm_Kb, 1, m, block_size, para_env, fm_red_K)
     731              : 
     732              :          CALL print_iteration_header('Block Davidson iterations for ABBA, Olsen-Stratmann paired subspace:', &
     733            2 :                                      unit_nr)
     734              : 
     735           18 :          DO iter = 1, bse_env%max_iter
     736           18 :             t_iter = m_walltime()
     737              : 
     738              :             ! Ω^k ascending with the coefficients R_mk of y^k and L_mk of x^k, sum_m R_mk L_mk = 1
     739              :             CALL solve_reduced_paired(fm_red_M, fm_red_K, m, n_act, para_env, omega, coef_right, coef_left, kr_min, &
     740           18 :                                       abba_status, diag_blacs_env)
     741              :             ! the smallest eigenvalue of A-B on the span of b is that of the last Kr
     742           18 :             ab_margin = kr_min
     743           18 :             IF (abba_status /= abba_ok) EXIT
     744              : 
     745              :             ! thick restart: b, Mb and Kb are rotated alike; the current pairs come first, so that
     746              :             ! they survive the cap that leaves room for one block of corrections; with Q_pk in
     747              :             ! coef_restart, b_ia,k <- sum_p b_ia,p Q_pk and Mr_kl <- sum_pq Q_pk Mr_pq Q_ql, Kr alike
     748           18 :             IF (m + 2*block_size > m_max .AND. m > 2*n_act .AND. m < n_ov) THEN
     749         2214 :                coef_restart_cand(1:m, 1:n_act) = coef_right(1:m, :)
     750         2214 :                coef_restart_cand(1:m, n_act + 1:2*n_act) = coef_left(1:m, :)
     751           16 :                n_cand = 2*n_act
     752           16 :                IF (has_prev) n_cand = 4*n_act
     753           16 :                CALL restart_basis(coef_restart_cand, m, n_cand, m_max - 2*block_size, coef_restart, k)
     754           16 :                CALL rotate_in_place(fm_b, m, coef_restart, k, fm_work, fm_Mb, fm_Kb)
     755           16 :                CALL reduced_rotate(fm_red_M, m, coef_restart, k, para_env, diag_blacs_env)
     756           16 :                CALL reduced_rotate(fm_red_K, m, coef_restart, k, para_env, diag_blacs_env)
     757           16 :                m = k
     758           16 :                n_restart = n_restart + 1
     759              :                CALL solve_reduced_paired(fm_red_M, fm_red_K, m, n_act, para_env, omega, coef_right, coef_left, kr_min, &
     760           16 :                                          abba_status, diag_blacs_env)
     761           16 :                ab_margin = kr_min
     762           16 :                IF (abba_status /= abba_ok) EXIT
     763              :             END IF
     764              : 
     765              :             ! rR_ia,k = sum_m (M b)_ia,m R_mk - Ω^k sum_m b_ia,m L_mk into the columns 1..n_act and
     766              :             ! rL_ia,k = sum_m (K b)_ia,m L_mk - Ω^k sum_m b_ia,m R_mk into the columns n_act+1..2 n_act
     767          144 :             DO k = 1, n_act
     768         1810 :                coef_work(1:m, k) = coef_left(1:m, k)*omega(k)
     769              :             END DO
     770         1810 :             CALL subspace_rotate(fm_Mb, m, coef_right(1:m, 1:n_act), n_act, fm_work, 1.0_dp, 0.0_dp)
     771         1810 :             CALL subspace_rotate(fm_b, m, coef_work(1:m, 1:n_act), n_act, fm_work, -1.0_dp, 1.0_dp)
     772          144 :             DO k = 1, n_act
     773         1810 :                coef_work(1:m, k) = coef_right(1:m, k)*omega(k)
     774              :             END DO
     775         1810 :             CALL subspace_rotate(fm_Kb, m, coef_left(1:m, 1:n_act), n_act, fm_work, 1.0_dp, 0.0_dp, n_act + 1)
     776         1810 :             CALL subspace_rotate(fm_b, m, coef_work(1:m, 1:n_act), n_act, fm_work, -1.0_dp, 1.0_dp, n_act + 1)
     777           18 :             CALL column_norms(fm_work, 1, n_act, para_env, res)
     778           18 :             CALL column_norms(fm_work, n_act + 1, n_act, para_env, res_L)
     779          144 :             res(:) = MAX(res(:), res_L(:))
     780              : 
     781              :             CALL convergence_step(bse_env, omega, omega_prev, res, m, n_ov, n_want, iter, t_iter, unit_nr, &
     782           18 :                                   dE, conv, n_req, need)
     783           32 :             all_conv = .NOT. ANY(need)
     784           18 :             IF (all_conv) EXIT
     785              : 
     786           16 :             IF (iter == bse_env%max_iter) EXIT
     787              : 
     788              :             ! corrections t_ia,k = r_ia,k/(d_ia - Ω^k) of the lowest unconverged roots: the rR block
     789              :             ! against the basis, then the rL block of the same roots against the extended basis;
     790              :             ! both fit the room, which is odd only when the ceiling is the number of transitions
     791           16 :             CALL select_roots(bse_env, need, res, MIN(block_size, (m_max - m + 1)/2), selected, nt_sel)
     792           16 :             nt_R = nt_sel
     793           16 :             CALL davidson_corrections(fm_work, selected(1:nt_R), omega, diag, fm_b, m + 1)
     794           16 :             CALL extend_basis_os(mv_env, fm_b, fm_Mb, fm_Kb, fm_scratch, m, nt_R, n_kernel, n_dependent)
     795           16 :             nt_L = MIN(nt_sel, m_max - m - nt_R)
     796           16 :             CALL davidson_corrections(fm_work, selected(1:nt_L), omega, diag, fm_b, m + nt_R + 1, n_act)
     797           16 :             CALL extend_basis_os(mv_env, fm_b, fm_Mb, fm_Kb, fm_scratch, m + nt_R, nt_L, n_kernel, n_dependent)
     798           16 :             nt = nt_R + nt_L
     799              :             ! no new vector: with an unchanged basis the next iteration finds dE = 0, which lets the roots that
     800              :             ! only wait for the energy criterion converge; a second empty pass means the residuals cannot improve
     801           16 :             IF (nt == 0 .AND. stalled) THEN
     802            0 :                CPABORT("BSE Davidson: correction vectors vanished before convergence")
     803              :             END IF
     804           16 :             stalled = nt == 0
     805              : 
     806           16 :             IF (nt > 0) THEN
     807              :                ! new columns n of Mr_mn = sum_ia b_ia,m (M b)_ia,n and Kr_mn = sum_ia b_ia,m (K b)_ia,n
     808           96 :                ALLOCATE (red_block_M(m + nt, nt), red_block_K(m + nt, nt))
     809           16 :                CALL subspace_gram(fm_b, 1, m + nt, fm_Mb, m + 1, nt, para_env, red_block_M)
     810           16 :                CALL subspace_gram(fm_b, 1, m + nt, fm_Kb, m + 1, nt, para_env, red_block_K)
     811           16 :                CALL reduced_extend(fm_red_M, red_block_M, m, nt)
     812           16 :                CALL reduced_extend(fm_red_K, red_block_K, m, nt)
     813           16 :                DEALLOCATE (red_block_M, red_block_K)
     814              :             END IF
     815              : 
     816              :             ! the pairs of this iteration are the second block of restart candidates
     817         6512 :             coef_restart_cand(:, 2*n_act + 1:4*n_act) = 0.0_dp
     818         1598 :             coef_restart_cand(1:m, 2*n_act + 1:3*n_act) = coef_right(1:m, :)
     819         1598 :             coef_restart_cand(1:m, 3*n_act + 1:4*n_act) = coef_left(1:m, :)
     820          128 :             has_prev = .TRUE.
     821          128 :             omega_prev(:) = omega(:)
     822           54 :             m = m + nt
     823              :          END DO
     824              :       END IF
     825              : 
     826            2 :       IF (abba_status == abba_ok) THEN
     827            2 :          IF (.NOT. all_conv) CALL abort_unconverged(omega, res, conv, need, n_req, unit_nr)
     828            2 :          IF (bse_env%bse_debug_print) CALL print_tracked_roots(omega, res, n_want, n_act, unit_nr)
     829              : 
     830            2 :          IF (bse_env%bse_debug_print) THEN
     831            0 :             dev = orthonormality_deviation(fm_b, fm_b, m, block_size, para_env)
     832            0 :             IF (unit_nr > 0) THEN
     833            0 :                WRITE (unit_nr, '(T2,A10,T13,A,T63,ES18.6)') 'BSE|DEBUG|', &
     834            0 :                   'Max deviation of the basis from orthonormality', dev
     835              :             END IF
     836              :          END IF
     837              : 
     838            2 :          IF (ALLOCATED(theta_sub)) CALL check_guess_bound(theta_sub, omega, n_req, unit_nr)
     839            2 :          CALL print_summary(iter, n_kernel, n_restart, n_want, n_req, n_act, n_ov, unit_nr, ab_margin, n_dependent)
     840              : 
     841              :          ! X_ia^n = sum_m b_ia,m (R_mn + L_mn)/2, Y_ia^n = sum_m b_ia,m (R_mn - L_mn)/2
     842            6 :          ALLOCATE (exc_ens(n_want))
     843            8 :          exc_ens(:) = omega(1:n_want)
     844            2 :          CALL bse_matvec_vector_struct(mv_env, n_want, fm_struct)
     845            2 :          CALL cp_fm_create(fm_X, fm_struct, name="fm_X_davidson", set_zero=.TRUE.)
     846            2 :          CALL cp_fm_create(fm_Y, fm_struct, name="fm_Y_davidson", set_zero=.TRUE.)
     847            2 :          CALL cp_fm_struct_release(fm_struct)
     848           92 :          coef_work(1:m, 1:n_want) = 0.5_dp*(coef_right(1:m, 1:n_want) + coef_left(1:m, 1:n_want))
     849           92 :          CALL subspace_rotate(fm_b, m, coef_work(1:m, 1:n_want), n_want, fm_X, 1.0_dp, 0.0_dp)
     850           92 :          coef_work(1:m, 1:n_want) = 0.5_dp*(coef_right(1:m, 1:n_want) - coef_left(1:m, 1:n_want))
     851           92 :          CALL subspace_rotate(fm_b, m, coef_work(1:m, 1:n_want), n_want, fm_Y, 1.0_dp, 0.0_dp)
     852              :       END IF
     853              : 
     854            2 :       CALL cp_fm_release(fm_b)
     855            2 :       CALL cp_fm_release(fm_Mb)
     856            2 :       CALL cp_fm_release(fm_Kb)
     857            2 :       CALL cp_fm_release(fm_work)
     858            2 :       CALL cp_fm_release(fm_scratch)
     859            2 :       CALL cp_fm_release(fm_red_M)
     860            2 :       CALL cp_fm_release(fm_red_K)
     861            2 :       CALL cp_blacs_env_release(diag_blacs_env)
     862            0 :       DEALLOCATE (coef_right, coef_left, coef_work, coef_restart_cand, coef_restart, omega, omega_prev, res, res_L, &
     863            2 :                   dE, conv, need, selected, diag)
     864            2 :       IF (ALLOCATED(theta_sub)) DEALLOCATE (theta_sub)
     865              : 
     866            2 :       CALL timestop(handle)
     867              : 
     868            8 :    END SUBROUTINE bse_davidson_abba_os
     869              : 
     870              : ! **************************************************************************************************
     871              : !> \brief Makes the nt columns T that follow column m of V orthonormal in the (A-B) inner product and
     872              : !>        stores their products: T_ia,k <- T_ia,k - sum_m V_ia,m sum_jb W_jb,m T_jb,k, Euclidean
     873              : !>        orthonormalisation of T, W_ia,k = sum_jb (A-B)_ia,jb T_jb,k, T_ia,k <- sum_l T_ia,l (R^-1)_lk and
     874              : !>        W alike with sum_p R_pk R_pl = sum_ia T_ia,k W_ia,l, and (M W)_ia,k = sum_jb (A+B)_ia,jb W_jb,k
     875              : !> \param mv_env the matrix-free A and B
     876              : !> \param fm_V basis; columns 1..m are kept, m+1..m+nt hold T on entry and the kept columns on exit
     877              : !> \param fm_W (A-B) V, extended in the same columns
     878              : !> \param fm_MW (A+B) W, extended in the same columns
     879              : !> \param fm_scratch scratch of at least nt columns
     880              : !> \param m columns of the basis before the extension
     881              : !> \param nt on exit the number of columns kept
     882              : !> \param n_kernel counter of the kernel applications
     883              : !> \param n_dependent counter of the correction vectors dropped as dependent
     884              : !> \param abba_status abba_indefinite if the Gram matrix of T in the (A-B) inner product is not positive definite
     885              : !> \param ab_margin smallest eigenvalue of T^T (A-B) T if that matrix is not positive definite
     886              : ! **************************************************************************************************
     887           16 :    SUBROUTINE extend_basis_mk(mv_env, fm_V, fm_W, fm_MW, fm_scratch, m, nt, n_kernel, n_dependent, abba_status, &
     888              :                               ab_margin)
     889              : 
     890              :       TYPE(bse_matvec_env_type), INTENT(IN)              :: mv_env
     891              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_V, fm_W, fm_MW, fm_scratch
     892              :       INTEGER, INTENT(IN)                                :: m
     893              :       INTEGER, INTENT(INOUT)                             :: nt, n_kernel, n_dependent, abba_status
     894              :       REAL(KIND=dp), INTENT(INOUT)                       :: ab_margin
     895              : 
     896              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'extend_basis_mk'
     897              : 
     898              :       INTEGER                                            :: handle
     899              : 
     900           16 :       CALL timeset(routineN, handle)
     901              : 
     902              :       ! T_ia,k <- T_ia,k - sum_m V_ia,m sum_jb W_jb,m T_jb,k, then Euclidean orthonormalisation
     903           16 :       CALL extend_orthonormal(fm_V, m, nt, mv_env%para_env, n_dependent, fm_W)
     904              : 
     905           16 :       IF (nt > 0) THEN
     906              :          ! W_ia,k = sum_jb (A-B)_ia,jb T_jb,k: A T into W, B T into the scratch
     907           16 :          CALL bse_matvec_apply(mv_env, fm_V, m + 1, nt, fm_W, fm_scratch, 1)
     908           16 :          CALL columns_axpy(-1.0_dp, fm_scratch, 1, fm_W, m + 1, nt)
     909           16 :          n_kernel = n_kernel + nt
     910              : 
     911              :          ! T_ia,k <- sum_l T_ia,l (R^-1)_lk and W alike, with sum_p R_pk R_pl = sum_ia T_ia,k W_ia,l
     912           16 :          CALL cholqr2_metric(fm_V, fm_W, m + 1, nt, mv_env%para_env, abba_status, ab_margin)
     913              : 
     914           16 :          IF (abba_status == abba_ok) THEN
     915              :             ! post-condition of the extension, one Gram matrix; the residual norms below rely on it
     916           16 :             IF (extension_deviation(fm_V, fm_W, m, nt, mv_env%para_env) > ortho_tol) THEN
     917            0 :                CPABORT("BSE Davidson: the basis lost orthonormality in the (A-B) inner product")
     918              :             END IF
     919              :             ! (M W)_ia,k = sum_jb (A+B)_ia,jb W_jb,k: A W into MW, B W into the scratch
     920           16 :             CALL bse_matvec_apply(mv_env, fm_W, m + 1, nt, fm_MW, fm_scratch, 1)
     921           16 :             CALL columns_axpy(1.0_dp, fm_scratch, 1, fm_MW, m + 1, nt)
     922           16 :             n_kernel = n_kernel + nt
     923              :          END IF
     924              :       END IF
     925              : 
     926           16 :       CALL timestop(handle)
     927              : 
     928           16 :    END SUBROUTINE extend_basis_mk
     929              : 
     930              : ! **************************************************************************************************
     931              : !> \brief Two Cholesky QR passes in the inner product of a symmetric K with the products KT at hand:
     932              : !>        G_kl = sum_ia T_ia,k (KT)_ia,l = sum_p R_pk R_pl, T_ia,k <- sum_l T_ia,l (R^-1)_lk, KT alike. A failed
     933              : !>        factorization of a G with a negative eigenvalue reports K as indefinite, any other failure
     934              : !>        is repeated once with a diagonal shift and a third pass. The Gram is the only communication:
     935              : !>        R is factorised on one rank and broadcast, the triangular solve runs on the local rows
     936              : !> \param fm_T the block to orthonormalise, columns first_col .. first_col+nt-1
     937              : !> \param fm_KT K T in the same columns, transformed alike
     938              : !> \param first_col first column of the block
     939              : !> \param nt number of columns in the block
     940              : !> \param para_env communicator of the row-distributed matrices
     941              : !> \param abba_status abba_indefinite if gram has a negative eigenvalue, else untouched
     942              : !> \param eig_min smallest eigenvalue of gram, set if it is negative
     943              : ! **************************************************************************************************
     944           16 :    SUBROUTINE cholqr2_metric(fm_T, fm_KT, first_col, nt, para_env, abba_status, eig_min)
     945              : 
     946              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_T, fm_KT
     947              :       INTEGER, INTENT(IN)                                :: first_col, nt
     948              :       TYPE(mp_para_env_type), INTENT(IN)                 :: para_env
     949              :       INTEGER, INTENT(INOUT)                             :: abba_status
     950              :       REAL(KIND=dp), INTENT(INOUT)                       :: eig_min
     951              : 
     952              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'cholqr2_metric'
     953              : 
     954              :       INTEGER                                            :: handle, info, ipass, k, n_ov, n_pass, &
     955              :                                                             nrow_local
     956              :       REAL(KIND=dp)                                      :: shift
     957              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: eigval
     958           16 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: chol_factor, gram, gram_raw
     959              : 
     960           16 :       CALL timeset(routineN, handle)
     961           16 :       CALL cp_fm_get_info(fm_T, nrow_local=nrow_local, nrow_global=n_ov)
     962          160 :       ALLOCATE (gram_raw(nt, nt), gram(nt, nt), chol_factor(nt, nt), eigval(nt))
     963              : 
     964           16 :       n_pass = 2
     965           16 :       ipass = 0
     966           48 :       DO WHILE (ipass < n_pass)
     967           32 :          ipass = ipass + 1
     968              :          ! G_kl = (sum_ia T_ia,k (KT)_ia,l + sum_ia T_ia,l (KT)_ia,k)/2 = sum_p R_pk R_pl
     969           32 :          CALL subspace_gram(fm_T, first_col, nt, fm_KT, first_col, nt, para_env, gram_raw)
     970          608 :          gram(:, :) = 0.5_dp*(gram_raw(:, :) + TRANSPOSE(gram_raw(:, :)))
     971          608 :          chol_factor(:, :) = gram(:, :)
     972           32 :          info = 0
     973           32 :          IF (para_env%is_source()) CALL DPOTRF('U', nt, chol_factor, nt, info)
     974           32 :          CALL para_env%bcast(info)
     975           32 :          IF (info /= 0) THEN
     976            0 :             IF (n_pass == 3) THEN
     977              :                CALL cp_abort(__LOCATION__, &
     978            0 :                              "BSE Davidson: orthonormalisation in the (A-B) inner product broke down")
     979              :             END IF
     980            0 :             chol_factor(:, :) = gram(:, :)
     981            0 :             eigval(:) = 0.0_dp
     982            0 :             IF (para_env%is_source()) CALL diamat_all(chol_factor, eigval)
     983            0 :             CALL para_env%bcast(eigval)
     984            0 :             IF (eigval(1) < 0.0_dp) THEN
     985            0 :                abba_status = abba_indefinite
     986            0 :                eig_min = eigval(1)
     987            0 :                EXIT
     988              :             END IF
     989              :             ! a positive G that failed numerically: factorise G_kl + shift δ_kl and pass once more, the
     990              :             ! shift of shifted Cholesky QR (Fukaya et al. 2020), 11 (N_ov nt + nt (nt+1)) EPSILON |G|_F
     991            0 :             n_pass = 3
     992              :             shift = 11.0_dp*(REAL(n_ov, dp)*REAL(nt, dp) + REAL(nt, dp)*REAL(nt + 1, dp))* &
     993            0 :                     EPSILON(1.0_dp)*NORM2(gram)
     994            0 :             chol_factor(:, :) = gram(:, :)
     995            0 :             DO k = 1, nt
     996            0 :                chol_factor(k, k) = chol_factor(k, k) + shift
     997              :             END DO
     998            0 :             IF (para_env%is_source()) CALL DPOTRF('U', nt, chol_factor, nt, info)
     999            0 :             CALL para_env%bcast(info)
    1000            0 :             IF (info /= 0) THEN
    1001              :                CALL cp_abort(__LOCATION__, &
    1002            0 :                              "BSE Davidson: orthonormalisation in the (A-B) inner product broke down")
    1003              :             END IF
    1004              :          END IF
    1005              :          ! T_ia,k <- sum_l T_ia,l (R^-1)_lk and KT alike, on the local rows
    1006           32 :          CALL para_env%bcast(chol_factor)
    1007           48 :          IF (nrow_local > 0) THEN
    1008              :             CALL DTRSM('R', 'U', 'N', 'N', nrow_local, nt, 1.0_dp, chol_factor, nt, &
    1009           32 :                        fm_T%local_data(:, first_col:first_col + nt - 1), SIZE(fm_T%local_data, 1))
    1010              :             CALL DTRSM('R', 'U', 'N', 'N', nrow_local, nt, 1.0_dp, chol_factor, nt, &
    1011           32 :                        fm_KT%local_data(:, first_col:first_col + nt - 1), SIZE(fm_KT%local_data, 1))
    1012              :          END IF
    1013              :       END DO
    1014              : 
    1015           16 :       DEALLOCATE (gram_raw, gram, chol_factor, eigval)
    1016           16 :       CALL timestop(handle)
    1017              : 
    1018           16 :    END SUBROUTINE cholqr2_metric
    1019              : 
    1020              : ! **************************************************************************************************
    1021              : !> \brief Paired reduced problem on the orthonormal basis b_ia,m,
    1022              : !>        Mr_mn = sum_ia b_ia,m (M b)_ia,n and Kr_mn = sum_ia b_ia,m (K b)_ia,n,
    1023              : !>        sum_n Mr_mn R_nk = Ω_k L_mk and sum_n Kr_mn L_nk = Ω_k R_mk,
    1024              : !>        solved as in initial_guess_subblock through the symmetric form:
    1025              : !>        Kr_mn = sum_p U_mp κ_p U_np and S_mn = sum_p U_mp √κ_p U_np, the square root of Kr,
    1026              : !>        sum_nop S_mn Mr_no S_op T_pk = λ_k T_mk with Ω_k = √λ_k,
    1027              : !>        R_mk = sum_n S_mn T_nk/√Ω_k and L_mk = sum_n Mr_mn R_nk/Ω_k.
    1028              : !>        κ_1 <= 0 reports K as indefinite on the span of b.
    1029              : !>        Every m x m step runs on the grid of blacs_env: the two eigensolves through
    1030              : !>        choose_eigv_solver and the four products through parallel_gemm; only the pair
    1031              : !>        coefficients come back replicated, R_mk in coef_right and L_mk in coef_left
    1032              : !> \param fm_red_M Mr in its live block, on the grid of blacs_env
    1033              : !> \param fm_red_K Kr in its live block, on the grid of blacs_env
    1034              : !> \param m dimension of the live block
    1035              : !> \param n_act number of pairs formed
    1036              : !> \param para_env communicator of the grid
    1037              : !> \param omega Ω_k in Hartree
    1038              : !> \param coef_right coefficients of y_k in column k, zero below row m
    1039              : !> \param coef_left coefficients of x_k in column k, zero below row m
    1040              : !> \param kr_min κ_1, the smallest eigenvalue of Kr
    1041              : !> \param abba_status abba_indefinite when κ_1 <= 0; omega, coef_right and coef_left are then untouched
    1042              : !> \param blacs_env grid the reduced problem is solved on
    1043              : ! **************************************************************************************************
    1044           34 :    SUBROUTINE solve_reduced_paired(fm_red_M, fm_red_K, m, n_act, para_env, omega, coef_right, coef_left, kr_min, &
    1045              :                                    abba_status, blacs_env)
    1046              : 
    1047              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_red_M, fm_red_K
    1048              :       INTEGER, INTENT(IN)                                :: m, n_act
    1049              :       TYPE(mp_para_env_type), INTENT(IN), TARGET         :: para_env
    1050              :       REAL(KIND=dp), DIMENSION(:), INTENT(OUT)           :: omega
    1051              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(OUT)        :: coef_right, coef_left
    1052              :       REAL(KIND=dp), INTENT(OUT)                         :: kr_min
    1053              :       INTEGER, INTENT(INOUT)                             :: abba_status
    1054              :       TYPE(cp_blacs_env_type), INTENT(IN), TARGET        :: blacs_env
    1055              : 
    1056              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'solve_reduced_paired'
    1057              : 
    1058              :       INTEGER                                            :: handle
    1059              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: kappa, lambda
    1060              :       TYPE(cp_fm_struct_type), POINTER                   :: fm_struct
    1061              :       TYPE(cp_fm_type)                                   :: fm_H, fm_H_eigvec, fm_K_eigvec, &
    1062              :                                                             fm_K_sqrt, fm_L, fm_Mr, fm_R, fm_work
    1063              : 
    1064           34 :       CALL timeset(routineN, handle)
    1065              :       ! n_act pairs are extracted from an m-dimensional basis
    1066           34 :       CPASSERT(n_act <= m)
    1067              : 
    1068              :       ! Kr_mn = sum_p U_mp κ_p U_np, S_mn = sum_p U_mp √κ_p U_np, H_mp = sum_no S_mn Mr_no S_op, T its eigenvectors:
    1069              :       ! κ, U in fm_K_eigvec, S in fm_K_sqrt, the symmetrised Mr, H, T in fm_H_eigvec and a work matrix,
    1070              :       ! all m x m on the grid of blacs_env
    1071          136 :       ALLOCATE (kappa(m), lambda(m))
    1072           34 :       NULLIFY (fm_struct)
    1073              :       CALL cp_fm_struct_create(fm_struct, para_env=para_env, context=blacs_env, &
    1074           34 :                                nrow_global=m, ncol_global=m)
    1075           34 :       CALL cp_fm_create(fm_K_eigvec, fm_struct, name="bse_paired_U")
    1076           34 :       CALL cp_fm_create(fm_K_sqrt, fm_struct, name="bse_paired_S")
    1077           34 :       CALL cp_fm_create(fm_Mr, fm_struct, name="bse_paired_Mr")
    1078           34 :       CALL cp_fm_create(fm_H, fm_struct, name="bse_paired_H")
    1079           34 :       CALL cp_fm_create(fm_H_eigvec, fm_struct, name="bse_paired_T")
    1080           34 :       CALL cp_fm_create(fm_work, fm_struct, name="bse_paired_work")
    1081           34 :       CALL cp_fm_struct_release(fm_struct)
    1082              : 
    1083              :       ! Kr_mn = sum_p U_mp κ_p U_np on the symmetrised live block, (Kr_mn + Kr_nm)/2
    1084           34 :       CALL cp_fm_to_fm_submat(fm_red_K, fm_work, m, m, 1, 1, 1, 1)
    1085           34 :       CALL symmetrise_in_place(fm_work, fm_K_eigvec)
    1086           34 :       kappa(:) = 0.0_dp
    1087           34 :       CALL choose_eigv_solver(fm_work, fm_K_eigvec, kappa)
    1088           34 :       kr_min = kappa(1)
    1089              : 
    1090           34 :       IF (kappa(1) <= 0.0_dp) THEN
    1091            0 :          abba_status = abba_indefinite
    1092              :       ELSE
    1093              :          ! S_mn = sum_p U_mp √κ_p U_np
    1094           34 :          CALL cp_fm_to_fm(fm_K_eigvec, fm_work)
    1095          570 :          CALL cp_fm_column_scale(fm_work, SQRT(kappa))
    1096           34 :          CALL parallel_gemm('N', 'T', m, m, m, 1.0_dp, fm_work, fm_K_eigvec, 0.0_dp, fm_K_sqrt)
    1097              : 
    1098              :          ! H_mp = sum_no S_mn Mr_no S_op with Mr_no <- (Mr_no + Mr_on)/2, then T holds its eigenvectors
    1099           34 :          CALL cp_fm_to_fm_submat(fm_red_M, fm_Mr, m, m, 1, 1, 1, 1)
    1100           34 :          CALL symmetrise_in_place(fm_Mr, fm_H_eigvec)
    1101           34 :          CALL parallel_gemm('N', 'N', m, m, m, 1.0_dp, fm_Mr, fm_K_sqrt, 0.0_dp, fm_work)
    1102           34 :          CALL parallel_gemm('N', 'N', m, m, m, 1.0_dp, fm_K_sqrt, fm_work, 0.0_dp, fm_H)
    1103           34 :          lambda(:) = 0.0_dp
    1104           34 :          CALL choose_eigv_solver(fm_H, fm_H_eigvec, lambda)
    1105              :          ! H is congruent to Mr = sum_ia b (M b), so λ_1 <= 0 means (A+B) indefinite on the span of b, or
    1106              :          ! round-off in a nearly dependent basis, which extension_deviation excludes at every extension
    1107           34 :          IF (lambda(1) <= 0.0_dp) THEN
    1108              :             CALL cp_abort(__LOCATION__, &
    1109              :                           "BSE Davidson (OLSEN_STRATMANN): negative squared excitation energy. Matrix "// &
    1110              :                           "(A+B) is not positive definite on the trial space, or the basis lost its "// &
    1111            0 :                           "orthonormality.")
    1112              :          END IF
    1113          272 :          omega(1:n_act) = SQRT(lambda(1:n_act))
    1114              : 
    1115              :          ! R_mk = sum_n S_mn T_nk/√Ω_k, L_mk = sum_n Mr_mn R_nk/Ω_k
    1116           34 :          NULLIFY (fm_struct)
    1117              :          CALL cp_fm_struct_create(fm_struct, para_env=para_env, context=blacs_env, &
    1118           34 :                                   nrow_global=m, ncol_global=n_act)
    1119           34 :          CALL cp_fm_create(fm_R, fm_struct, name="bse_paired_R")
    1120           34 :          CALL cp_fm_create(fm_L, fm_struct, name="bse_paired_L")
    1121           34 :          CALL cp_fm_struct_release(fm_struct)
    1122           34 :          CALL parallel_gemm('N', 'N', m, n_act, m, 1.0_dp, fm_K_sqrt, fm_H_eigvec, 0.0_dp, fm_R)
    1123          272 :          CALL cp_fm_column_scale(fm_R, 1.0_dp/SQRT(omega(1:n_act)))
    1124           34 :          CALL parallel_gemm('N', 'N', m, n_act, m, 1.0_dp, fm_Mr, fm_R, 0.0_dp, fm_L)
    1125          272 :          CALL cp_fm_column_scale(fm_L, 1.0_dp/omega(1:n_act))
    1126              : 
    1127         6936 :          coef_right(:, :) = 0.0_dp
    1128         6936 :          coef_left(:, :) = 0.0_dp
    1129           34 :          CALL cp_fm_get_submatrix(fm_R, coef_right(1:m, 1:n_act))
    1130           34 :          CALL cp_fm_get_submatrix(fm_L, coef_left(1:m, 1:n_act))
    1131           34 :          CALL cp_fm_release(fm_R)
    1132           34 :          CALL cp_fm_release(fm_L)
    1133              :       END IF
    1134              : 
    1135           34 :       CALL cp_fm_release(fm_K_eigvec)
    1136           34 :       CALL cp_fm_release(fm_K_sqrt)
    1137           34 :       CALL cp_fm_release(fm_Mr)
    1138           34 :       CALL cp_fm_release(fm_H)
    1139           34 :       CALL cp_fm_release(fm_H_eigvec)
    1140           34 :       CALL cp_fm_release(fm_work)
    1141           34 :       DEALLOCATE (kappa, lambda)
    1142           34 :       CALL timestop(handle)
    1143              : 
    1144           68 :    END SUBROUTINE solve_reduced_paired
    1145              : 
    1146              : ! **************************************************************************************************
    1147              : !> \brief Makes the nt columns T that follow column m of b orthonormal to b and to each other,
    1148              : !>        T_ia,k <- T_ia,k - sum_m b_ia,m sum_jb b_jb,m T_jb,k, then T_ia,k <- sum_l T_ia,l (R^-1)_lk with
    1149              : !>        sum_p R_pk R_pl = sum_ia T_ia,k T_ia,l (cholqr2), and stores the products
    1150              : !>        (M b)_ia,k = sum_jb (A+B)_ia,jb b_jb,k, (K b)_ia,k = sum_jb (A-B)_ia,jb b_jb,k of the kept
    1151              : !>        columns from one application of A and B
    1152              : !> \param mv_env the matrix-free A and B
    1153              : !> \param fm_b basis; columns 1..m are kept, m+1..m+nt hold T on entry and the kept columns on exit
    1154              : !> \param fm_Mb (A+B) b, extended in the same columns
    1155              : !> \param fm_Kb (A-B) b, extended in the same columns
    1156              : !> \param fm_scratch scratch of at least nt columns
    1157              : !> \param m columns of the basis before the extension
    1158              : !> \param nt on exit the number of columns kept
    1159              : !> \param n_kernel counter of the kernel applications
    1160              : !> \param n_dependent counter of the correction vectors dropped as dependent
    1161              : ! **************************************************************************************************
    1162           34 :    SUBROUTINE extend_basis_os(mv_env, fm_b, fm_Mb, fm_Kb, fm_scratch, m, nt, n_kernel, n_dependent)
    1163              : 
    1164              :       TYPE(bse_matvec_env_type), INTENT(IN)              :: mv_env
    1165              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_b, fm_Mb, fm_Kb, fm_scratch
    1166              :       INTEGER, INTENT(IN)                                :: m
    1167              :       INTEGER, INTENT(INOUT)                             :: nt, n_kernel, n_dependent
    1168              : 
    1169              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'extend_basis_os'
    1170              : 
    1171              :       INTEGER                                            :: handle
    1172              : 
    1173           34 :       CALL timeset(routineN, handle)
    1174              : 
    1175           34 :       CALL extend_orthonormal(fm_b, m, nt, mv_env%para_env, n_dependent)
    1176              : 
    1177           34 :       IF (nt > 0) THEN
    1178              :          ! post-condition of the extension, one Gram matrix; the residual norms below rely on it
    1179           34 :          IF (extension_deviation(fm_b, fm_b, m, nt, mv_env%para_env) > ortho_tol) THEN
    1180            0 :             CPABORT("BSE Davidson: the basis lost orthonormality")
    1181              :          END IF
    1182              :          ! A b into Mb and B b into the scratch, then K b = A b - B b and M b = A b + B b
    1183           34 :          CALL bse_matvec_apply(mv_env, fm_b, m + 1, nt, fm_Mb, fm_scratch, 1)
    1184           34 :          CALL cp_fm_to_fm(fm_Mb, fm_Kb, nt, m + 1, m + 1)
    1185           34 :          CALL columns_axpy(-1.0_dp, fm_scratch, 1, fm_Kb, m + 1, nt)
    1186           34 :          CALL columns_axpy(1.0_dp, fm_scratch, 1, fm_Mb, m + 1, nt)
    1187           34 :          n_kernel = n_kernel + nt
    1188              :       END IF
    1189              : 
    1190           34 :       CALL timestop(handle)
    1191              : 
    1192           34 :    END SUBROUTINE extend_basis_os
    1193              : 
    1194              : ! **************************************************************************************************
    1195              : !> \brief One convergence step of a driver: dE_k = |E_k - E_k^prev|, the flags from CONVERGENCE_CRITERION,
    1196              : !>        the last required root, every root converged once the basis spans all transitions, the roots
    1197              : !>        in need, and the line of the iteration table
    1198              : !> \param bse_env CONVERGENCE_CRITERION, EPS_ENERGY and EPS_RES
    1199              : !> \param energies tracked energies in Hartree, ascending; the first SIZE(res) are read
    1200              : !> \param energies_prev the same of the previous iteration
    1201              : !> \param res residual norms of the tracked roots
    1202              : !> \param m subspace dimension
    1203              : !> \param n_ov number of transitions
    1204              : !> \param n_want requested states
    1205              : !> \param iter iteration count
    1206              : !> \param t_iter wall time at the start of the iteration
    1207              : !> \param unit_nr output unit, positive on the writing rank only
    1208              : !> \param dE energy changes in Hartree
    1209              : !> \param conv convergence flags
    1210              : !> \param n_req last required root
    1211              : !> \param need roots in need
    1212              : ! **************************************************************************************************
    1213          108 :    SUBROUTINE convergence_step(bse_env, energies, energies_prev, res, m, n_ov, n_want, iter, t_iter, unit_nr, &
    1214          108 :                                dE, conv, n_req, need)
    1215              : 
    1216              :       TYPE(bse_env_type), INTENT(IN)                     :: bse_env
    1217              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: energies, energies_prev, res
    1218              :       INTEGER, INTENT(IN)                                :: m, n_ov, n_want, iter
    1219              :       REAL(KIND=dp), INTENT(IN)                          :: t_iter
    1220              :       INTEGER, INTENT(IN)                                :: unit_nr
    1221              :       REAL(KIND=dp), DIMENSION(:), INTENT(OUT)           :: dE
    1222              :       LOGICAL, DIMENSION(:), INTENT(OUT)                 :: conv
    1223              :       INTEGER, INTENT(OUT)                               :: n_req
    1224              :       LOGICAL, DIMENSION(:), INTENT(OUT)                 :: need
    1225              : 
    1226              :       INTEGER                                            :: n_act
    1227              : 
    1228           54 :       n_act = SIZE(res)
    1229          436 :       dE(:) = ABS(energies(1:n_act) - energies_prev(1:n_act))
    1230           54 :       CALL convergence_flags(bse_env, dE, res, conv)
    1231           54 :       n_req = multiplet_end(energies, n_want, n_act, deg_thresh)
    1232              :       ! a subspace that spans all transitions is exact
    1233           54 :       IF (m == n_ov) conv(:) = .TRUE.
    1234           54 :       CALL roots_in_need(conv, energies, res, n_req, need)
    1235              :       ! the masked MAXVAL is -HUGE when no root is in need; the unmasked term covers that case
    1236              :       CALL print_iteration(iter, m, COUNT(conv(1:n_want)), &
    1237              :                            MAX(MAXVAL(res(1:n_req)), MAXVAL(res, MASK=need)), &
    1238          942 :                            MAXVAL(dE(1:n_req)), energies(1), m_walltime() - t_iter, unit_nr)
    1239              : 
    1240           54 :    END SUBROUTINE convergence_step
    1241              : 
    1242              : ! **************************************************************************************************
    1243              : !> \brief Convergence flag of every tracked root k from CONVERGENCE_CRITERION: EN dE_k < EPS_ENERGY,
    1244              : !>        RES |r_k| < EPS_RES, OR either of the two, AND both
    1245              : !> \param bse_env CONVERGENCE_CRITERION, EPS_ENERGY and EPS_RES
    1246              : !> \param dE energy changes in Hartree
    1247              : !> \param res residual norms
    1248              : !> \param conv one flag per tracked root
    1249              : ! **************************************************************************************************
    1250           54 :    SUBROUTINE convergence_flags(bse_env, dE, res, conv)
    1251              : 
    1252              :       TYPE(bse_env_type), INTENT(IN)                     :: bse_env
    1253              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: dE, res
    1254              :       LOGICAL, DIMENSION(:), INTENT(OUT)                 :: conv
    1255              : 
    1256              :       INTEGER                                            :: k
    1257              : 
    1258          436 :       DO k = 1, SIZE(conv)
    1259           54 :          SELECT CASE (bse_env%convergence_criterion)
    1260              :          CASE (bse_iter_en_cond)
    1261            0 :             conv(k) = dE(k) < bse_env%eps_energy
    1262              :          CASE (bse_iter_res_cond)
    1263            0 :             conv(k) = res(k) < bse_env%eps_res
    1264              :          CASE (bse_iter_or_cond)
    1265            0 :             conv(k) = dE(k) < bse_env%eps_energy .OR. res(k) < bse_env%eps_res
    1266              :          CASE (bse_iter_and_cond)
    1267          698 :             conv(k) = dE(k) < bse_env%eps_energy .AND. res(k) < bse_env%eps_res
    1268              :          END SELECT
    1269              :       END DO
    1270              : 
    1271           54 :    END SUBROUTINE convergence_flags
    1272              : 
    1273              : ! **************************************************************************************************
    1274              : !> \brief Last member of the multiplet that contains state n_first: the scan stops at the first gap of
    1275              : !>        at least thresh, or at n_limit. A multiplet cut by NUM_EXC_EN is thereby taken as a whole
    1276              : !> \param energies ascending energies in Hartree
    1277              : !> \param n_first first state of the scan
    1278              : !> \param n_limit last state the scan may reach
    1279              : !> \param thresh gap that ends a multiplet, in Hartree
    1280              : !> \return the last member, n_first <= n_last <= n_limit
    1281              : ! **************************************************************************************************
    1282           62 :    PURE FUNCTION multiplet_end(energies, n_first, n_limit, thresh) RESULT(n_last)
    1283              : 
    1284              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: energies
    1285              :       INTEGER, INTENT(IN)                                :: n_first, n_limit
    1286              :       REAL(KIND=dp), INTENT(IN)                          :: thresh
    1287              :       INTEGER                                            :: n_last
    1288              : 
    1289           62 :       n_last = n_first
    1290           62 :       DO WHILE (n_last < n_limit)
    1291           62 :          IF (energies(n_last + 1) - energies(n_last) >= thresh) EXIT
    1292           62 :          n_last = n_last + 1
    1293              :       END DO
    1294              : 
    1295           62 :    END FUNCTION multiplet_end
    1296              : ! **************************************************************************************************
    1297              : !> \brief Roots that still need correction vectors: the unconverged ones up to n_req, and a tracked
    1298              : !>        root k above them as long as E_k - |r_k| < E_n_req. An eigenvalue of a symmetric matrix
    1299              : !>        lies within |r_k| of E_k, so such a root can still belong to the requested states; the ABBA
    1300              : !>        solvers, whose residuals are not those of a symmetric problem, use the same rule as a heuristic
    1301              : !> \param conv convergence flags of the tracked roots
    1302              : !> \param energies tracked energies in Hartree, ascending
    1303              : !> \param res residual norms
    1304              : !> \param n_req last required root
    1305              : !> \param need one flag per tracked root, set here
    1306              : ! **************************************************************************************************
    1307           54 :    SUBROUTINE roots_in_need(conv, energies, res, n_req, need)
    1308              : 
    1309              :       LOGICAL, DIMENSION(:), INTENT(IN)                  :: conv
    1310              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: energies, res
    1311              :       INTEGER, INTENT(IN)                                :: n_req
    1312              :       LOGICAL, DIMENSION(:), INTENT(OUT)                 :: need
    1313              : 
    1314              :       INTEGER                                            :: k
    1315              : 
    1316          436 :       DO k = 1, SIZE(need)
    1317          382 :          need(k) = .NOT. conv(k)
    1318          642 :          IF (k > n_req) need(k) = need(k) .AND. energies(k) - res(k) < energies(n_req)
    1319              :       END DO
    1320              : 
    1321           54 :    END SUBROUTINE roots_in_need
    1322              : 
    1323              : ! **************************************************************************************************
    1324              : !> \brief The lowest roots in need, which receive a correction vector
    1325              : !> \param bse_env CONVERGENCE_CRITERION and EPS_RES
    1326              : !> \param need roots in need, from roots_in_need
    1327              : !> \param res residual norms
    1328              : !> \param n_max largest number of roots
    1329              : !> \param selected indices of the selected roots in ascending order, zero beyond nt
    1330              : !> \param nt number of selected roots
    1331              : ! **************************************************************************************************
    1332           46 :    SUBROUTINE select_roots(bse_env, need, res, n_max, selected, nt)
    1333              : 
    1334              :       TYPE(bse_env_type), INTENT(IN)                     :: bse_env
    1335              :       LOGICAL, DIMENSION(:), INTENT(IN)                  :: need
    1336              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: res
    1337              :       INTEGER, INTENT(IN)                                :: n_max
    1338              :       INTEGER, DIMENSION(:), INTENT(OUT)                 :: selected
    1339              :       INTEGER, INTENT(OUT)                               :: nt
    1340              : 
    1341              :       INTEGER                                            :: k
    1342              : 
    1343          370 :       selected(:) = 0
    1344           46 :       nt = 0
    1345          370 :       DO k = 1, SIZE(need)
    1346          324 :          IF (.NOT. need(k)) CYCLE
    1347              :          ! a root below EPS_RES only waits for its energy criterion
    1348          118 :          IF (bse_env%convergence_criterion == bse_iter_and_cond .AND. &
    1349              :              res(k) < bse_env%eps_res) CYCLE
    1350          110 :          IF (nt == n_max) EXIT
    1351          110 :          nt = nt + 1
    1352          370 :          selected(nt) = k
    1353              :       END DO
    1354              : 
    1355           46 :    END SUBROUTINE select_roots
    1356              : 
    1357              : ! **************************************************************************************************
    1358              : !> \brief Title and column heads of the iteration table
    1359              : !> \param title line printed above the column heads
    1360              : !> \param unit_nr output unit, positive on the writing rank only
    1361              : ! **************************************************************************************************
    1362            8 :    SUBROUTINE print_iteration_header(title, unit_nr)
    1363              : 
    1364              :       CHARACTER(LEN=*), INTENT(IN)                       :: title
    1365              :       INTEGER, INTENT(IN)                                :: unit_nr
    1366              : 
    1367            8 :       IF (unit_nr > 0) THEN
    1368            4 :          WRITE (unit_nr, '(T2,A4)') 'BSE|'
    1369            4 :          WRITE (unit_nr, '(T2,A4,T7,A)') 'BSE|', title
    1370            4 :          WRITE (unit_nr, '(T2,A4)') 'BSE|'
    1371            4 :          WRITE (unit_nr, '(T2,A4,T7,A)') 'BSE|', &
    1372            8 :             '|r| in a.u., energies in eV, t the wall time of the iteration'
    1373            4 :          WRITE (unit_nr, '(T2,A4)') 'BSE|'
    1374            4 :          WRITE (unit_nr, '(T2,A4,T7,A5,T14,A8,T24,A6,T33,A12,T47,A12,T61,A12,T74,A7)') 'BSE|', &
    1375            8 :             'Iter.', 'Z-space', 'Conv.', 'Max |r|', 'Max dE', 'Lowest E', 't (s)'
    1376              :       END IF
    1377              : 
    1378            8 :    END SUBROUTINE print_iteration_header
    1379              : 
    1380              : ! **************************************************************************************************
    1381              : !> \brief One line of the iteration table
    1382              : !> \param iter iteration count
    1383              : !> \param m subspace dimension
    1384              : !> \param n_conv converged roots among the requested ones
    1385              : !> \param max_res largest residual norm over the required roots and the roots in need
    1386              : !> \param max_dE in Hartree
    1387              : !> \param lowest_E in Hartree
    1388              : !> \param t_iter wall time of the iteration in seconds
    1389              : !> \param unit_nr output unit, positive on the writing rank only
    1390              : ! **************************************************************************************************
    1391           54 :    SUBROUTINE print_iteration(iter, m, n_conv, max_res, max_dE, lowest_E, t_iter, unit_nr)
    1392              : 
    1393              :       INTEGER, INTENT(IN)                                :: iter, m, n_conv
    1394              :       REAL(KIND=dp), INTENT(IN)                          :: max_res, max_dE, lowest_E, t_iter
    1395              :       INTEGER, INTENT(IN)                                :: unit_nr
    1396              : 
    1397           54 :       IF (unit_nr > 0) THEN
    1398              :          WRITE (unit_nr, '(T2,A4,T7,I5,T14,I8,T24,I6,T33,ES12.4,T47,ES12.4,T61,F12.6,T74,F7.2)') &
    1399           27 :             'BSE|', iter, m, n_conv, max_res, max_dE*evolt, lowest_E*evolt, t_iter
    1400              :       END IF
    1401              : 
    1402           54 :    END SUBROUTINE print_iteration
    1403              : 
    1404              : ! **************************************************************************************************
    1405              : !> \brief Table of the requested roots and of the tracked roots in need, then the abort for MAX_ITER
    1406              : !> \param energies in Hartree
    1407              : !> \param res residual norms
    1408              : !> \param conv convergence flags
    1409              : !> \param need roots in need
    1410              : !> \param n_req last required root
    1411              : !> \param unit_nr output unit, positive on the writing rank only
    1412              : ! **************************************************************************************************
    1413            0 :    SUBROUTINE abort_unconverged(energies, res, conv, need, n_req, unit_nr)
    1414              : 
    1415              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: energies, res
    1416              :       LOGICAL, DIMENSION(:), INTENT(IN)                  :: conv, need
    1417              :       INTEGER, INTENT(IN)                                :: n_req, unit_nr
    1418              : 
    1419              :       INTEGER                                            :: k
    1420              : 
    1421            0 :       IF (unit_nr > 0) THEN
    1422            0 :          WRITE (unit_nr, '(T2,A4)') 'BSE|'
    1423            0 :          WRITE (unit_nr, '(T2,A4,T7,A12,T30,A11,T50,A10,T67,A14)') 'BSE|', &
    1424            0 :             'Excitation n', 'Energy (eV)', '|r| (a.u.)', 'Converged'
    1425            0 :          DO k = 1, SIZE(need)
    1426            0 :             IF (k > n_req .AND. .NOT. need(k)) CYCLE
    1427            0 :             WRITE (unit_nr, '(T2,A4,T7,I12,T27,F14.6,T46,ES14.4,T67,L14)') 'BSE|', &
    1428            0 :                k, energies(k)*evolt, res(k), conv(k)
    1429              :          END DO
    1430              :       END IF
    1431              :       ! unconverged roots would enter postprocess_bse as converged states; the table above is the diagnostic
    1432            0 :       CPABORT("BSE Davidson: MAX_ITER reached before convergence")
    1433              : 
    1434            0 :    END SUBROUTINE abort_unconverged
    1435              : 
    1436              : ! **************************************************************************************************
    1437              : !> \brief Debug table of all tracked roots of a converged solve
    1438              : !> \param energies in Hartree
    1439              : !> \param res residual norms
    1440              : !> \param n_want requested states
    1441              : !> \param n_act tracked states
    1442              : !> \param unit_nr output unit, positive on the writing rank only
    1443              : ! **************************************************************************************************
    1444            0 :    SUBROUTINE print_tracked_roots(energies, res, n_want, n_act, unit_nr)
    1445              : 
    1446              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: energies, res
    1447              :       INTEGER, INTENT(IN)                                :: n_want, n_act, unit_nr
    1448              : 
    1449              :       INTEGER                                            :: k
    1450              : 
    1451            0 :       IF (unit_nr > 0) THEN
    1452            0 :          WRITE (unit_nr, '(T2,A10,T13,A13,T27,A9,T45,A18,T67,A14)') 'BSE|DEBUG|', &
    1453            0 :             'Tracked state', 'Requested', 'Energy (eV)', '|r| (a.u.)'
    1454            0 :          DO k = 1, n_act
    1455            0 :             WRITE (unit_nr, '(T2,A10,T13,I13,T27,L9,T45,F18.10,T67,ES14.4)') 'BSE|DEBUG|', &
    1456            0 :                k, k <= n_want, energies(k)*evolt, res(k)
    1457              :          END DO
    1458              :       END IF
    1459              : 
    1460            0 :    END SUBROUTINE print_tracked_roots
    1461              : 
    1462              : ! **************************************************************************************************
    1463              : !> \brief Counters of a converged solve, the multiplet note and the warning for a degenerate group
    1464              : !>        that reaches the last buffer state
    1465              : !> \param iter iterations run
    1466              : !> \param n_kernel kernel applications
    1467              : !> \param n_restart thick restarts
    1468              : !> \param n_want requested states
    1469              : !> \param n_req last required root
    1470              : !> \param n_act tracked states
    1471              : !> \param n_ov number of transitions
    1472              : !> \param unit_nr output unit, positive on the writing rank only
    1473              : !> \param ab_margin smallest eigenvalue of A-B on the trial space in Hartree
    1474              : !> \param n_dependent correction vectors dropped as dependent, printed when not zero
    1475              : ! **************************************************************************************************
    1476            8 :    SUBROUTINE print_summary(iter, n_kernel, n_restart, n_want, n_req, n_act, n_ov, unit_nr, ab_margin, &
    1477              :                             n_dependent)
    1478              : 
    1479              :       INTEGER, INTENT(IN)                                :: iter, n_kernel, n_restart, n_want, &
    1480              :                                                             n_req, n_act, n_ov, unit_nr
    1481              :       REAL(KIND=dp), INTENT(IN), OPTIONAL                :: ab_margin
    1482              :       INTEGER, INTENT(IN), OPTIONAL                      :: n_dependent
    1483              : 
    1484            8 :       IF (unit_nr > 0) THEN
    1485            4 :          WRITE (unit_nr, '(T2,A4)') 'BSE|'
    1486            4 :          WRITE (unit_nr, '(T2,A4,T7,A,T71,I10)') 'BSE|', 'Davidson iterations', iter
    1487            4 :          WRITE (unit_nr, '(T2,A4,T7,A,T71,I10)') 'BSE|', 'Kernel applications', n_kernel
    1488            4 :          WRITE (unit_nr, '(T2,A4,T7,A,T71,I10)') 'BSE|', 'Thick restarts', n_restart
    1489            4 :          IF (PRESENT(n_dependent)) THEN
    1490            4 :             IF (n_dependent > 0) THEN
    1491            0 :                WRITE (unit_nr, '(T2,A4,T7,A,T71,I10)') 'BSE|', 'Correction vectors dropped as dependent', n_dependent
    1492              :             END IF
    1493              :          END IF
    1494            4 :          IF (PRESENT(ab_margin)) THEN
    1495            2 :             WRITE (unit_nr, '(T2,A4,T7,A,T65,F16.6)') 'BSE|', &
    1496            4 :                'A-B margin on the trial space (eV)', ab_margin*evolt
    1497              :          END IF
    1498            4 :          IF (n_req > n_want) THEN
    1499            0 :             WRITE (unit_nr, '(T2,A4,T7,A)') 'BSE|', &
    1500            0 :                'Note: the requested states end inside a degenerate multiplet.'
    1501              :          END IF
    1502            4 :          WRITE (unit_nr, '(T2,A4)') 'BSE|'
    1503              :       END IF
    1504              : 
    1505            8 :       IF (n_req > n_want .AND. n_req == n_act .AND. n_act < n_ov .AND. unit_nr > 0) THEN
    1506              :          CALL cp_warn(__LOCATION__, &
    1507              :                       "BSE Davidson: the degenerate group at NUM_EXC_EN reaches the last buffer state, "// &
    1508            0 :                       "further members may lie above it. Increase BSE_ITERAT%NUM_BUFFER_STATES.")
    1509              :       END IF
    1510              : 
    1511            8 :    END SUBROUTINE print_summary
    1512              : 
    1513              : ! **************************************************************************************************
    1514              : !> \brief Number of wanted and tracked states and the block size, capped at the problem size
    1515              : !> \param bse_env NUM_EXC_EN, NUM_BUFFER_STATES, BLOCK_SIZE and MAX_ITER
    1516              : !> \param n_ov number of transitions
    1517              : !> \param unit_nr output unit, positive on the writing rank only
    1518              : !> \param n_want NUM_EXC_EN, at most n_ov
    1519              : !> \param n_act wanted plus buffer states
    1520              : !> \param block_size BLOCK_SIZE, or MIN(32, n_act) for -1; at most n_act
    1521              : ! **************************************************************************************************
    1522            8 :    SUBROUTINE davidson_sizes(bse_env, n_ov, unit_nr, n_want, n_act, block_size)
    1523              : 
    1524              :       TYPE(bse_env_type), INTENT(IN)                     :: bse_env
    1525              :       INTEGER, INTENT(IN)                                :: n_ov, unit_nr
    1526              :       INTEGER, INTENT(OUT)                               :: n_want, n_act, block_size
    1527              : 
    1528            8 :       IF (bse_env%num_exc_en < 1) CPABORT("BSE_ITERAT%NUM_EXC_EN must be at least 1")
    1529            8 :       IF (bse_env%num_buffer_states < 0) CPABORT("BSE_ITERAT%NUM_BUFFER_STATES must not be negative")
    1530            8 :       IF (bse_env%block_size < 1 .AND. bse_env%block_size /= -1) THEN
    1531            0 :          CPABORT("BSE_ITERAT%BLOCK_SIZE must be at least 1, or -1 for the default")
    1532              :       END IF
    1533            8 :       IF (bse_env%max_iter < 1) CPABORT("BSE_ITERAT%MAX_ITER must be at least 1")
    1534              : 
    1535            8 :       n_want = bse_env%num_exc_en
    1536            8 :       IF (n_want > n_ov) THEN
    1537            0 :          IF (unit_nr > 0) CALL cp_warn(__LOCATION__, &
    1538            0 :                                        "BSE_ITERAT%NUM_EXC_EN exceeds the number of transitions and is reduced to it.")
    1539            0 :          n_want = n_ov
    1540              :       END IF
    1541            8 :       n_act = MIN(n_want + bse_env%num_buffer_states, n_ov)
    1542            8 :       block_size = bse_env%block_size
    1543              :       ! 32 was within 5 % of the best width on the L8 deck at 200 states; wider passes cost restarts
    1544            8 :       IF (block_size == -1) block_size = MIN(32, n_act)
    1545            8 :       IF (block_size > n_act) THEN
    1546            0 :          IF (unit_nr > 0) CALL cp_warn(__LOCATION__, &
    1547            0 :                                        "BSE_ITERAT%BLOCK_SIZE exceeds the number of tracked states and is reduced to it.")
    1548            0 :          block_size = n_act
    1549              :       END IF
    1550              : 
    1551            8 :    END SUBROUTINE davidson_sizes
    1552              : 
    1553              : ! **************************************************************************************************
    1554              : !> \brief Largest subspace dimension m_max: MAX_SUBSPACE_FACTOR x n_act, or 20 x n_act for -1, with
    1555              : !>        lowered to the largest whose arrays fit what MEMORY_PER_PROC leaves after the memory the rank
    1556              : !>        holds, never below 3 x n_act (4 x n_act for OLSEN_STRATMANN); the minimal subspace over that
    1557              : !>        remainder runs with a warning
    1558              : !> \param bse_env MAX_SUBSPACE_FACTOR and the resolved MEMORY_PER_PROC
    1559              : !> \param mv_env n_ov, the RI share per rank and block_cols
    1560              : !> \param driver driver_tda, driver_mk or driver_os
    1561              : !> \param n_act tracked states
    1562              : !> \param block_size correction vectors per iteration
    1563              : !> \param unit_nr output unit, positive on the writing rank only
    1564              : !> \param m_max the ceiling, 3 or 4 x n_act <= m_max <= n_ov
    1565              : ! **************************************************************************************************
    1566           16 :    SUBROUTINE subspace_ceiling(bse_env, mv_env, driver, n_act, block_size, unit_nr, m_max)
    1567              : 
    1568              :       TYPE(bse_env_type), INTENT(IN)                     :: bse_env
    1569              :       TYPE(bse_matvec_env_type), INTENT(IN)              :: mv_env
    1570              :       INTEGER, INTENT(IN)                                :: driver, n_act, block_size, unit_nr
    1571              :       INTEGER, INTENT(OUT)                               :: m_max
    1572              : 
    1573              :       CHARACTER(LEN=16)                                  :: bud_str, fac_str, mem_str
    1574              :       INTEGER                                            :: m_floor, m_hi, m_lo, m_mid, m_start, &
    1575              :                                                             min_fac, n_ri_max, nb
    1576              :       LOGICAL                                            :: floor_over, lowered
    1577              :       REAL(KIND=dp)                                      :: dist_GB, mem_left_GB, repl_GB
    1578              : 
    1579              :       ! the smallest ceiling that leaves room for the restart and one block: TDA and MK restart onto at
    1580              :       ! most 2 n_act vectors and add a block of at most n_act; OS restarts onto right and left pairs
    1581              :       ! under the cap m_max - 2 block_size, which has to hold the current 2 n_act
    1582            8 :       IF (driver == driver_os) THEN
    1583            2 :          min_fac = 4
    1584              :       ELSE
    1585            6 :          min_fac = 3
    1586              :       END IF
    1587            8 :       WRITE (fac_str, '(I16)') min_fac
    1588              : 
    1589              :       ! the start value: the keyword, or for -1 the generous 20 x n_act that the memory check below trims to what fits
    1590            8 :       IF (bse_env%max_subspace_factor == -1) THEN
    1591            0 :          m_start = 20*n_act
    1592            8 :       ELSE IF (bse_env%max_subspace_factor >= min_fac) THEN
    1593            8 :          m_start = bse_env%max_subspace_factor*n_act
    1594              :       ELSE
    1595              :          CALL cp_abort(__LOCATION__, &
    1596              :                        "BSE_ITERAT%MAX_SUBSPACE_FACTOR must be at least "//TRIM(ADJUSTL(fac_str))// &
    1597            0 :                        " (or -1) for the chosen solver")
    1598              :       END IF
    1599            8 :       m_floor = MIN(min_fac*n_act, mv_env%n_ov)
    1600            8 :       m_start = MIN(MAX(m_start, m_floor), mv_env%n_ov)
    1601            8 :       m_max = m_start
    1602              : 
    1603              :       ! the widest kernel call of the driver caps the pass width; the largest RI share of any rank
    1604              :       ! keeps the estimate, and so m_max, identical on all ranks
    1605            4 :       SELECT CASE (driver)
    1606              :       CASE (driver_tda)
    1607            4 :          nb = n_act
    1608              :       CASE (driver_mk)
    1609            2 :          nb = block_size
    1610              :       CASE DEFAULT
    1611            8 :          nb = 2*block_size
    1612              :       END SELECT
    1613            8 :       IF (mv_env%block_cols > 0) nb = MIN(nb, mv_env%block_cols)
    1614            8 :       n_ri_max = mv_env%n_ri_loc
    1615            8 :       CALL mv_env%para_env%max(n_ri_max)
    1616              : 
    1617              :       ! what MEMORY_PER_PROC leaves after the slabs and everything else the rank holds
    1618            8 :       mem_left_GB = bse_memory_left_GB(bse_env%memory_per_proc_gb, mv_env%para_env)
    1619            8 :       CALL davidson_footprint(driver, mv_env, m_max, n_act, block_size, nb, n_ri_max, dist_GB, repl_GB)
    1620            8 :       lowered = dist_GB + repl_GB > mem_left_GB
    1621            8 :       floor_over = .FALSE.
    1622            8 :       IF (lowered) THEN
    1623            0 :          CALL davidson_footprint(driver, mv_env, m_floor, n_act, block_size, nb, n_ri_max, dist_GB, repl_GB)
    1624            0 :          IF (dist_GB + repl_GB > mem_left_GB) THEN
    1625              :             ! nothing smaller than the minimal subspace exists: run with it and say so
    1626            0 :             floor_over = .TRUE.
    1627            0 :             m_max = m_floor
    1628              :          ELSE
    1629              :             ! the footprint grows with m: bisect for the largest m within what is left
    1630              :             m_lo = m_floor
    1631              :             m_hi = m_start
    1632            0 :             DO WHILE (m_hi - m_lo > 1)
    1633            0 :                m_mid = (m_lo + m_hi)/2
    1634            0 :                CALL davidson_footprint(driver, mv_env, m_mid, n_act, block_size, nb, n_ri_max, dist_GB, repl_GB)
    1635            0 :                IF (dist_GB + repl_GB <= mem_left_GB) THEN
    1636              :                   m_lo = m_mid
    1637              :                ELSE
    1638            0 :                   m_hi = m_mid
    1639              :                END IF
    1640              :             END DO
    1641            0 :             m_max = m_lo
    1642            0 :             CALL davidson_footprint(driver, mv_env, m_max, n_act, block_size, nb, n_ri_max, dist_GB, repl_GB)
    1643              :          END IF
    1644              :       END IF
    1645            8 :       IF (unit_nr > 0) THEN
    1646            4 :          WRITE (unit_nr, '(T2,A4)') 'BSE|'
    1647            4 :          IF (bse_env%max_subspace_factor == -1) THEN
    1648            0 :             WRITE (unit_nr, '(T2,A4,T7,A,T71,I10)') 'BSE|', 'Maximum subspace dimension (20 x buffered states)', &
    1649            0 :                m_start
    1650              :          ELSE
    1651            4 :             WRITE (unit_nr, '(T2,A4,T7,A,T71,I10)') 'BSE|', 'Maximum subspace dimension (MAX_SUBSPACE_FACTOR)', &
    1652            8 :                m_start
    1653              :          END IF
    1654            4 :          IF (m_max < m_start) THEN
    1655            0 :             WRITE (unit_nr, '(T2,A4,T7,A,T71,I10)') 'BSE|', 'Maximum subspace dimension lowered to the memory left', &
    1656            0 :                m_max
    1657              :          END IF
    1658            4 :          WRITE (unit_nr, '(T2,A4,T7,A)') 'BSE|', 'Memory per MPI rank at the subspace ceiling (GB)'
    1659            4 :          WRITE (unit_nr, '(T2,A4,T9,A,T67,F14.3)') 'BSE|', 'Distributed arrays', dist_GB
    1660            4 :          WRITE (unit_nr, '(T2,A4,T9,A,T67,F14.3)') 'BSE|', 'Replicated arrays', repl_GB
    1661            4 :          WRITE (unit_nr, '(T2,A4,T9,A,T67,F14.3)') 'BSE|', 'Total', dist_GB + repl_GB
    1662            4 :          WRITE (unit_nr, '(T2,A4,T7,A,T67,F14.3)') 'BSE|', 'Memory per MPI rank for the BSE, MEMORY_PER_PROC (GB)', &
    1663            8 :             bse_env%memory_per_proc_gb
    1664            4 :          WRITE (unit_nr, '(T2,A4,T7,A,T67,F14.3)') 'BSE|', 'Left for the solver after the memory held (GB)', &
    1665            8 :             mem_left_GB
    1666            4 :          WRITE (unit_nr, '(T2,A4)') 'BSE|'
    1667              :       END IF
    1668            8 :       IF (floor_over) THEN
    1669            0 :          WRITE (mem_str, '(F16.3)') dist_GB + repl_GB
    1670            0 :          WRITE (bud_str, '(F16.3)') mem_left_GB
    1671              :          CALL cp_warn(__LOCATION__, &
    1672              :                       "BSE Davidson: the minimal subspace of "//TRIM(ADJUSTL(fac_str))//" vectors per state needs "// &
    1673              :                       TRIM(ADJUSTL(mem_str))//" GB per MPI rank, "//TRIM(ADJUSTL(bud_str))// &
    1674            0 :                       " GB are left of MEMORY_PER_PROC; running with it. Raise MEMORY_PER_PROC or use more MPI ranks.")
    1675              :       END IF
    1676              : 
    1677            8 :    END SUBROUTINE subspace_ceiling
    1678              : 
    1679              : ! **************************************************************************************************
    1680              : !> \brief Memory per MPI rank of the solver's arrays at subspace dimension m, split into the family
    1681              : !>        spread over the ranks and the family every rank holds whole. The counts are those of the
    1682              : !>        ALLOCATE and cp_fm_create statements of the drivers at a thick restart, their peak:
    1683              : !>        distributed 8 B (N_ov (n_basis m + n_work + n_scratch) + (n_red + n_solve) m^2)/P,
    1684              : !>        replicated 8 B (c_coef m n_act + m block_size + n_mv N_ov nb + n_ri nb + n_virt virt^2)
    1685              : !> \param driver driver_tda, driver_mk or driver_os
    1686              : !> \param mv_env n_ov, virt and the communicator
    1687              : !> \param m subspace dimension
    1688              : !> \param n_act tracked states
    1689              : !> \param block_size correction vectors per iteration
    1690              : !> \param nb kernel pass width
    1691              : !> \param n_ri RI functions of a rank, the largest over the ranks
    1692              : !> \param dist_GB distributed family, falls as 1/P
    1693              : !> \param repl_GB replicated family, flat in P
    1694              : ! **************************************************************************************************
    1695            8 :    SUBROUTINE davidson_footprint(driver, mv_env, m, n_act, block_size, nb, n_ri, dist_GB, repl_GB)
    1696              : 
    1697              :       INTEGER, INTENT(IN)                                :: driver
    1698              :       TYPE(bse_matvec_env_type), INTENT(IN)              :: mv_env
    1699              :       INTEGER, INTENT(IN)                                :: m, n_act, block_size, nb, n_ri
    1700              :       REAL(KIND=dp), INTENT(OUT)                         :: dist_GB, repl_GB
    1701              : 
    1702              :       INTEGER                                            :: c_coef, n_basis, n_mv, n_red, n_scratch, &
    1703              :                                                             n_solve, n_virt, n_work
    1704              :       REAL(KIND=dp)                                      :: m_real, n_ov_real
    1705              : 
    1706              :       ! distributed 8 B (N_ov (n_basis m + n_work + n_scratch) + (n_red + n_solve) m^2)/P: the trial vector
    1707              :       ! arrays, the work and scratch columns, the reduced and the per-solve m x m matrices;
    1708              :       ! replicated 8 B (c_coef m n_act + m block_size + n_mv N_ov nb + n_ri nb + n_virt virt^2): the m x n_act
    1709              :       ! coefficient blocks with the restart work array, the pass buffers of the kernel, virt x virt for B
    1710           12 :       SELECT CASE (driver)
    1711              :       CASE (driver_tda)
    1712            4 :          n_basis = 2
    1713            4 :          n_work = MIN(2*n_act, m)
    1714            4 :          n_scratch = 0
    1715            4 :          n_red = 1
    1716            4 :          n_solve = 2
    1717            4 :          c_coef = 8
    1718            4 :          n_mv = 3
    1719            4 :          n_virt = 0
    1720              :       CASE (driver_mk)
    1721            2 :          n_basis = 3
    1722            2 :          n_work = MIN(2*n_act, m)
    1723            2 :          n_scratch = block_size
    1724            2 :          n_red = 1
    1725            2 :          n_solve = 2
    1726            2 :          c_coef = 10
    1727            2 :          n_mv = 4
    1728            2 :          n_virt = 1
    1729              :       CASE (driver_os)
    1730            2 :          n_basis = 3
    1731            2 :          n_work = MAX(2*n_act, MIN(4*n_act, m))
    1732            2 :          n_scratch = 2*block_size
    1733            2 :          n_red = 2
    1734            2 :          n_solve = 6
    1735            2 :          c_coef = 15
    1736            2 :          n_mv = 4
    1737            2 :          n_virt = 1
    1738              :       CASE DEFAULT
    1739              :          ! unreachable for the three driver constants; the DEFAULT keeps every counter defined
    1740            8 :          CPABORT("BSE Davidson: unknown driver in the memory estimate")
    1741              :       END SELECT
    1742              : 
    1743              :       ! in REAL: m^2 overflows INTEGER(4) above 46340
    1744            8 :       m_real = REAL(m, dp)
    1745            8 :       n_ov_real = REAL(mv_env%n_ov, dp)
    1746              :       dist_GB = 8.0E-9_dp*(n_ov_real*REAL(n_basis*m + n_work + n_scratch, dp) + REAL(n_red + n_solve, dp)*m_real*m_real)/ &
    1747            8 :                 REAL(mv_env%para_env%num_pe, dp)
    1748              :       repl_GB = 8.0E-9_dp*(REAL(c_coef, dp)*m_real*REAL(n_act, dp) + m_real*REAL(block_size, dp) + &
    1749              :                            REAL(n_mv, dp)*n_ov_real*REAL(nb, dp) + REAL(n_ri, dp)*REAL(nb, dp) + &
    1750            8 :                            REAL(n_virt, dp)*REAL(mv_env%virt, dp)**2)
    1751              : 
    1752            8 :    END SUBROUTINE davidson_footprint
    1753              : 
    1754              : ! **************************************************************************************************
    1755              : !> \brief Unit vectors Z_ia,k = δ_ia,diag_order(k) on the entries diag_order(1..n_used) with the lowest diagonal, never
    1756              : !>        cutting a degenerate group. The sorted diagonal is replicated; each rank writes the entries
    1757              : !>        that fall on its own rows
    1758              : !> \param diag the diagonal chosen by PRECONDITIONER, replicated
    1759              : !> \param n_guess guess vectors wanted
    1760              : !> \param n_max largest number of guess vectors
    1761              : !> \param deg_thresh_diag entries of the sorted diagonal closer than this form one degenerate group
    1762              : !> \param fm_Z receives the unit vectors in its columns 1..n_used
    1763              : !> \param n_used guess vectors written, n_guess <= n_used <= n_max
    1764              : !> \param n_given leading columns that the caller has filled; they replace the lowest unit vectors
    1765              : ! **************************************************************************************************
    1766            6 :    SUBROUTINE initial_guess(diag, n_guess, n_max, deg_thresh_diag, fm_Z, n_used, n_given)
    1767              : 
    1768              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: diag
    1769              :       INTEGER, INTENT(IN)                                :: n_guess, n_max
    1770              :       REAL(KIND=dp), INTENT(IN)                          :: deg_thresh_diag
    1771              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_Z
    1772              :       INTEGER, INTENT(OUT)                               :: n_used
    1773              :       INTEGER, INTENT(IN), OPTIONAL                      :: n_given
    1774              : 
    1775              :       INTEGER                                            :: iloc, k, k_first, n_ov, nrow_local
    1776            6 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: diag_order
    1777            6 :       INTEGER, DIMENSION(:), POINTER                     :: row_indices
    1778              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: diag_sorted
    1779              : 
    1780            6 :       n_ov = SIZE(diag)
    1781           30 :       ALLOCATE (diag_sorted(n_ov), diag_order(n_ov))
    1782          294 :       diag_sorted(:) = diag(:)
    1783            6 :       CALL sort(diag_sorted, n_ov, diag_order)
    1784              : 
    1785            6 :       n_used = multiplet_end(diag_sorted, n_guess, n_max, deg_thresh_diag)
    1786              : 
    1787            6 :       k_first = 1
    1788            6 :       IF (PRESENT(n_given)) k_first = n_given + 1
    1789              : 
    1790            6 :       CALL cp_fm_get_info(fm_Z, nrow_local=nrow_local, row_indices=row_indices)
    1791           48 :       DO k = k_first, n_used
    1792         1056 :          DO iloc = 1, nrow_local
    1793         1050 :             IF (row_indices(iloc) == diag_order(k)) fm_Z%local_data(iloc, k) = 1.0_dp
    1794              :          END DO
    1795              :       END DO
    1796              : 
    1797            6 :       DEALLOCATE (diag_sorted, diag_order)
    1798              : 
    1799           12 :    END SUBROUTINE initial_guess
    1800              : 
    1801              : ! **************************************************************************************************
    1802              : !> \brief Initial guess from the exact A (and B) on the n_sub transitions with the lowest diagonal:
    1803              : !>        TDA: sum_q A_sub,pq V_qk = θ_k V_pk; ABBA: x_pk = sum_q (K_sub^-1/2)_pq t_qk with
    1804              : !>        sum_qrs (K_sub^1/2)_pq M_sub,qr (K_sub^1/2)_rs t_sk = θ_k^2 t_pk, K = A-B, M = A+B. The Ritz
    1805              : !>        values θ_k are upper bounds of the true energies (Rayleigh-Ritz for TDA, Cauchy-type
    1806              : !>        interlacing of Bai and Li for the linear response problem), kept for check_guess_bound. A
    1807              : !>        degenerate group at the upper boundary is taken as a whole. The block and its eigenvectors
    1808              : !>        are replicated; each rank writes the entries that fall on its own rows
    1809              : !> \param mv_env the matrix-free A and B, for the exact block
    1810              : !> \param bse_env NUM_GUESS_TRANSITIONS and BSE_DEBUG_PRINT
    1811              : !> \param diag the diagonal chosen by PRECONDITIONER, replicated; its lowest entries select the block
    1812              : !> \param n_guess guess vectors wanted
    1813              : !> \param n_max largest number of guess vectors
    1814              : !> \param deg_thresh_diag Ritz values closer than this form one degenerate group
    1815              : !> \param fm_Z receives the guess vectors in its columns 1..n_used
    1816              : !> \param n_used guess vectors written, n_guess <= n_used <= n_max; 0 for abba_indefinite
    1817              : !> \param theta_sub allocated here, the n_sub Ritz values in Hartree
    1818              : !> \param unit_nr output unit, positive on the writing rank only
    1819              : !> \param n_given leading columns that the caller has filled; they replace the lowest guess vectors
    1820              : !> \param abba_status ABBA problem when present; abba_indefinite if K_sub is not positive definite
    1821              : !> \param ab_margin smallest eigenvalue of K_sub in Hartree
    1822              : !> \param n_pair ABBA: partners y_k = K_sub x_k/θ_k of the guess vectors x_k not given by the caller,
    1823              : !>        written to the n_pair columns after n_used; n_used + n_pair stays within n_max
    1824              : ! **************************************************************************************************
    1825            2 :    SUBROUTINE initial_guess_subblock(mv_env, bse_env, diag, n_guess, n_max, deg_thresh_diag, fm_Z, n_used, &
    1826              :                                      theta_sub, unit_nr, n_given, abba_status, ab_margin, n_pair)
    1827              : 
    1828              :       TYPE(bse_matvec_env_type), INTENT(IN)              :: mv_env
    1829              :       TYPE(bse_env_type), INTENT(IN)                     :: bse_env
    1830              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: diag
    1831              :       INTEGER, INTENT(IN)                                :: n_guess, n_max
    1832              :       REAL(KIND=dp), INTENT(IN)                          :: deg_thresh_diag
    1833              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_Z
    1834              :       INTEGER, INTENT(OUT)                               :: n_used
    1835              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:), &
    1836              :          INTENT(OUT)                                     :: theta_sub
    1837              :       INTEGER, INTENT(IN)                                :: unit_nr
    1838              :       INTEGER, INTENT(IN), OPTIONAL                      :: n_given
    1839              :       INTEGER, INTENT(OUT), OPTIONAL                     :: abba_status
    1840              :       REAL(KIND=dp), INTENT(OUT), OPTIONAL               :: ab_margin
    1841              :       INTEGER, INTENT(OUT), OPTIONAL                     :: n_pair
    1842              : 
    1843              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'initial_guess_subblock'
    1844              : 
    1845              :       INTEGER                                            :: handle, iloc, k, k_first, n_ov, n_sub, &
    1846              :                                                             n_top, nrow_local
    1847            2 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: diag_order, rank_in_block
    1848            2 :       INTEGER, DIMENSION(:), POINTER                     :: row_indices
    1849              :       LOGICAL                                            :: ok
    1850            2 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: diag_sorted, eig_K
    1851            2 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: A_sub, B_sub, eigvec_H, eigvec_K, &
    1852            2 :                                                             guess_x, guess_y, H_sub, K_mhalf, &
    1853            2 :                                                             K_phalf, K_sub, M_sub
    1854              :       TYPE(mp_para_env_type), POINTER                    :: para_env
    1855              : 
    1856            2 :       CALL timeset(routineN, handle)
    1857              : 
    1858            2 :       IF (PRESENT(n_pair)) n_pair = 0
    1859            2 :       para_env => mv_env%para_env
    1860            2 :       n_ov = SIZE(diag)
    1861            2 :       n_sub = MIN(n_ov, MAX(bse_env%num_guess_transitions, n_guess))
    1862           12 :       ALLOCATE (diag_sorted(n_ov), diag_order(n_ov), rank_in_block(n_ov))
    1863           98 :       diag_sorted(:) = diag(:)
    1864            2 :       CALL sort(diag_sorted, n_ov, diag_order)
    1865              : 
    1866           16 :       ALLOCATE (A_sub(n_sub, n_sub), guess_x(n_sub, n_sub), theta_sub(n_sub))
    1867            2 :       ok = .TRUE.
    1868            2 :       IF (PRESENT(abba_status)) THEN
    1869            0 :          abba_status = abba_ok
    1870              :          ALLOCATE (B_sub(n_sub, n_sub), K_sub(n_sub, n_sub), M_sub(n_sub, n_sub), H_sub(n_sub, n_sub), &
    1871              :                    eigvec_K(n_sub, n_sub), eigvec_H(n_sub, n_sub), K_phalf(n_sub, n_sub), &
    1872            0 :                    K_mhalf(n_sub, n_sub), eig_K(n_sub))
    1873            0 :          CALL bse_matvec_subblock(mv_env, diag_order(1:n_sub), A_sub, B_sub)
    1874              :          ! (K_sub^±1/2)_pq = sum_r U_pr κ_r^±1/2 U_qr from K_sub,pq = sum_r U_pr κ_r U_qr
    1875            0 :          K_sub(:, :) = A_sub(:, :) - B_sub(:, :)
    1876            0 :          CALL solve_replicated(K_sub, n_sub, para_env, eig_K, eigvec_K)
    1877            0 :          ab_margin = eig_K(1)
    1878            0 :          IF (eig_K(1) <= 0.0_dp) THEN
    1879            0 :             abba_status = abba_indefinite
    1880            0 :             ok = .FALSE.
    1881            0 :             n_used = 0
    1882              :          ELSE
    1883            0 :             DO k = 1, n_sub
    1884            0 :                K_phalf(:, k) = eigvec_K(:, k)*SQRT(eig_K(k))
    1885            0 :                K_mhalf(:, k) = eigvec_K(:, k)/SQRT(eig_K(k))
    1886              :             END DO
    1887            0 :             K_phalf(:, :) = MATMUL(K_phalf, TRANSPOSE(eigvec_K))
    1888            0 :             K_mhalf(:, :) = MATMUL(K_mhalf, TRANSPOSE(eigvec_K))
    1889              :             ! M_sub = 2 A_sub - K_sub, H_ps = sum_qr (K^1/2)_pq M_qr (K^1/2)_rs, x_pk = sum_q (K^-1/2)_pq t_qk
    1890            0 :             M_sub(:, :) = 2.0_dp*A_sub(:, :) - K_sub(:, :)
    1891            0 :             H_sub(:, :) = MATMUL(K_phalf, MATMUL(M_sub, K_phalf))
    1892            0 :             CALL solve_replicated(H_sub, n_sub, para_env, theta_sub, eigvec_H)
    1893              :             ! the same failure as in solve_reduced_paired: with K_sub positive definite, M_sub is indefinite
    1894            0 :             IF (theta_sub(1) <= 0.0_dp) THEN
    1895            0 :                CPABORT("BSE Davidson: the guess block gives a non-positive squared excitation energy")
    1896              :             END IF
    1897            0 :             theta_sub(:) = SQRT(theta_sub(:))
    1898            0 :             guess_x(:, :) = MATMUL(K_mhalf, eigvec_H)
    1899            0 :             IF (PRESENT(n_pair)) THEN
    1900              :                ! partners guess_y_k = K_sub guess_x_k/θ_k
    1901            0 :                ALLOCATE (guess_y(n_sub, n_sub))
    1902            0 :                guess_y(:, :) = MATMUL(K_sub, guess_x)
    1903            0 :                DO k = 1, n_sub
    1904            0 :                   guess_y(:, k) = guess_y(:, k)/theta_sub(k)
    1905              :                END DO
    1906              :             END IF
    1907              :          END IF
    1908            0 :          DEALLOCATE (B_sub, K_sub, M_sub, H_sub, eigvec_K, eigvec_H, K_phalf, K_mhalf, eig_K)
    1909              :       ELSE
    1910            2 :          CALL bse_matvec_subblock(mv_env, diag_order(1:n_sub), A_sub)
    1911            2 :          CALL solve_replicated(A_sub, n_sub, para_env, theta_sub, guess_x)
    1912              :       END IF
    1913              : 
    1914            0 :       IF (ok) THEN
    1915            2 :          k_first = 1
    1916            2 :          IF (PRESENT(n_given)) k_first = n_given + 1
    1917              : 
    1918            2 :          n_top = MIN(n_max, n_sub)
    1919              :          ! the partners of the columns k_first..n_used share the n_max columns with the guess
    1920            2 :          IF (PRESENT(n_pair)) n_top = MIN(n_top, (n_max + k_first - 1)/2)
    1921            2 :          n_used = multiplet_end(theta_sub, n_guess, n_top, deg_thresh_diag)
    1922              : 
    1923            2 :          rank_in_block(:) = 0
    1924           98 :          DO k = 1, n_sub
    1925           98 :             rank_in_block(diag_order(k)) = k
    1926              :          END DO
    1927            2 :          CALL cp_fm_get_info(fm_Z, nrow_local=nrow_local, row_indices=row_indices)
    1928           18 :          DO k = k_first, n_used
    1929          402 :             DO iloc = 1, nrow_local
    1930          400 :                IF (rank_in_block(row_indices(iloc)) > 0) fm_Z%local_data(iloc, k) = guess_x(rank_in_block(row_indices(iloc)), k)
    1931              :             END DO
    1932              :          END DO
    1933            2 :          IF (PRESENT(n_pair)) THEN
    1934            0 :             n_pair = MIN(n_used - k_first + 1, n_max - n_used)
    1935            0 :             DO k = 1, n_pair
    1936            0 :                DO iloc = 1, nrow_local
    1937            0 :                   IF (rank_in_block(row_indices(iloc)) > 0) THEN
    1938            0 :                      fm_Z%local_data(iloc, n_used + k) = guess_y(rank_in_block(row_indices(iloc)), k_first + k - 1)
    1939              :                   END IF
    1940              :                END DO
    1941              :             END DO
    1942              :          END IF
    1943              : 
    1944            2 :          IF (unit_nr > 0) THEN
    1945            1 :             WRITE (unit_nr, '(T2,A4,T7,A,T71,I10)') 'BSE|', 'Transitions in the exact guess block', n_sub
    1946            1 :             IF (bse_env%bse_debug_print) THEN
    1947            0 :                WRITE (unit_nr, '(T2,A10,T13,A,T67,F14.6)') 'BSE|DEBUG|', &
    1948            0 :                   'Lowest Ritz value of the guess block (eV)', theta_sub(1)*evolt
    1949              :             END IF
    1950              :          END IF
    1951              :       END IF
    1952              : 
    1953            2 :       DEALLOCATE (diag_sorted, diag_order, rank_in_block, A_sub, guess_x)
    1954            2 :       IF (ALLOCATED(guess_y)) DEALLOCATE (guess_y)
    1955              : 
    1956            2 :       CALL timestop(handle)
    1957              : 
    1958            4 :    END SUBROUTINE initial_guess_subblock
    1959              : 
    1960              : ! **************************************************************************************************
    1961              : !> \brief A converged energy above the Ritz value of the guess block with the same index is a
    1962              : !>        provable miss: both are upper bounds of the true eigenvalue with that index
    1963              : !> \param theta_sub Ritz values of the guess block in Hartree, ascending
    1964              : !> \param energies converged energies in Hartree
    1965              : !> \param n_req roots compared, the required ones
    1966              : !> \param unit_nr output unit, positive on the writing rank only
    1967              : ! **************************************************************************************************
    1968            2 :    SUBROUTINE check_guess_bound(theta_sub, energies, n_req, unit_nr)
    1969              : 
    1970              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: theta_sub, energies
    1971              :       INTEGER, INTENT(IN)                                :: n_req, unit_nr
    1972              : 
    1973              :       REAL(KIND=dp)                                      :: excess
    1974              : 
    1975           10 :       excess = MAXVAL(energies(1:n_req) - theta_sub(1:n_req))
    1976            2 :       IF (unit_nr > 0) THEN
    1977            1 :          WRITE (unit_nr, '(T2,A4,T7,A,T65,F16.6)') 'BSE|', &
    1978            2 :             'Largest excess over the guess-block bound (eV)', excess*evolt
    1979              :       END IF
    1980            2 :       IF (excess > deg_thresh .AND. unit_nr > 0) THEN
    1981              :          CALL cp_warn(__LOCATION__, &
    1982              :                       "A converged BSE state lies above the upper bound given by the guess block, "// &
    1983              :                       "so a lower state was missed. Raise BSE_ITERAT%NUM_GUESS_TRANSITIONS or "// &
    1984            0 :                       "BSE_ITERAT%NUM_BUFFER_STATES.")
    1985              :       END IF
    1986              : 
    1987            2 :    END SUBROUTINE check_guess_bound
    1988              : 
    1989              : ! **************************************************************************************************
    1990              : !> \brief sum_q (red_pq + red_qp)/2 c_qk = θ_k c_pk for p, q <= m, red_pq the live block of fm_red, solved
    1991              : !>        on the grid of blacs_env: θ_k in theta ascending, c_pk of the first n_vec k gathered into
    1992              : !>        coef_ritz
    1993              : !> \param fm_red the reduced matrix; its live block is the first m rows and columns
    1994              : !> \param m dimension of the live block
    1995              : !> \param n_vec eigenvectors gathered; the drivers never read more than n_act
    1996              : !> \param para_env communicator of the grid
    1997              : !> \param blacs_env grid the reduced problem is solved on
    1998              : !> \param theta θ_k in the first m entries, zero beyond
    1999              : !> \param coef_ritz c_pk in the first m rows and n_vec columns, zero beyond
    2000              : ! **************************************************************************************************
    2001           44 :    SUBROUTINE solve_reduced(fm_red, m, n_vec, para_env, blacs_env, theta, coef_ritz)
    2002              : 
    2003              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_red
    2004              :       INTEGER, INTENT(IN)                                :: m, n_vec
    2005              :       TYPE(mp_para_env_type), INTENT(IN), TARGET         :: para_env
    2006              :       TYPE(cp_blacs_env_type), INTENT(IN), TARGET        :: blacs_env
    2007              :       REAL(KIND=dp), DIMENSION(:), INTENT(OUT)           :: theta
    2008              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(OUT)        :: coef_ritz
    2009              : 
    2010              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'solve_reduced'
    2011              : 
    2012              :       INTEGER                                            :: handle, n_get
    2013              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: eigval
    2014              :       TYPE(cp_fm_type)                                   :: fm_reduced_eigvec, fm_reduced_sym
    2015              : 
    2016           44 :       CALL timeset(routineN, handle)
    2017              : 
    2018           44 :       CALL reduced_live_block(fm_red, m, para_env, blacs_env, "bse_reduced", fm_reduced_sym)
    2019           44 :       CALL cp_fm_create(fm_reduced_eigvec, fm_reduced_sym%matrix_struct, name="bse_reduced_vectors")
    2020              :       ! a_pq <- (red_pq + red_qp)/2
    2021           44 :       CALL symmetrise_in_place(fm_reduced_sym, fm_reduced_eigvec)
    2022          132 :       ALLOCATE (eigval(m))
    2023           44 :       eigval(:) = 0.0_dp
    2024           44 :       CALL choose_eigv_solver(fm_reduced_sym, fm_reduced_eigvec, eigval)
    2025              : 
    2026         1202 :       theta(:) = 0.0_dp
    2027          642 :       theta(1:m) = eigval(:)
    2028         8654 :       coef_ritz(:, :) = 0.0_dp
    2029           44 :       n_get = MIN(m, n_vec)
    2030           44 :       CALL cp_fm_get_submatrix(fm_reduced_eigvec, coef_ritz(1:m, 1:n_get), 1, 1, m, n_get)
    2031              : 
    2032           44 :       DEALLOCATE (eigval)
    2033           44 :       CALL cp_fm_release(fm_reduced_sym)
    2034           44 :       CALL cp_fm_release(fm_reduced_eigvec)
    2035              : 
    2036           44 :       CALL timestop(handle)
    2037              : 
    2038           44 :    END SUBROUTINE solve_reduced
    2039              : 
    2040              : ! **************************************************************************************************
    2041              : !> \brief sum_q (mat_pq + mat_qp)/2 coef_qk = θ_k coef_pk for p, q <= m, a small replicated block solved
    2042              : !>        on one rank and broadcast: the fixed-size blocks of the guess and of cholqr2, never the
    2043              : !>        reduced matrix
    2044              : !> \param mat the block; its first m rows and columns are read
    2045              : !> \param m dimension of the block
    2046              : !> \param para_env communicator; the solve runs on its source rank
    2047              : !> \param theta ascending
    2048              : !> \param coef all m eigenvectors in the columns
    2049              : ! **************************************************************************************************
    2050            2 :    SUBROUTINE solve_replicated(mat, m, para_env, theta, coef)
    2051              : 
    2052              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: mat
    2053              :       INTEGER, INTENT(IN)                                :: m
    2054              :       TYPE(mp_para_env_type), INTENT(IN)                 :: para_env
    2055              :       REAL(KIND=dp), DIMENSION(:), INTENT(OUT)           :: theta
    2056              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(OUT)        :: coef
    2057              : 
    2058              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'solve_replicated'
    2059              : 
    2060              :       INTEGER                                            :: handle
    2061              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: eigval
    2062              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: mat_sym
    2063              : 
    2064            2 :       CALL timeset(routineN, handle)
    2065              : 
    2066           12 :       ALLOCATE (mat_sym(m, m), eigval(m))
    2067         4706 :       mat_sym(:, :) = 0.5_dp*(mat(1:m, 1:m) + TRANSPOSE(mat(1:m, 1:m)))
    2068            2 :       eigval(:) = 0.0_dp
    2069            2 :       IF (para_env%is_source()) CALL diamat_all(mat_sym, eigval)
    2070            2 :       CALL para_env%bcast(mat_sym)
    2071            2 :       CALL para_env%bcast(eigval)
    2072              : 
    2073           98 :       theta(:) = 0.0_dp
    2074           98 :       theta(1:m) = eigval(:)
    2075         4706 :       coef(:, :) = 0.0_dp
    2076         4706 :       coef(1:m, 1:m) = mat_sym(:, :)
    2077            2 :       DEALLOCATE (mat_sym, eigval)
    2078              : 
    2079            2 :       CALL timestop(handle)
    2080              : 
    2081            2 :    END SUBROUTINE solve_replicated
    2082              : 
    2083              : ! **************************************************************************************************
    2084              : !> \brief a_pq <- (a_pq + a_qp)/2 through a scratch matrix of the same shape: the eigensolvers read one triangle
    2085              : !> \param fm_a symmetrised in place
    2086              : !> \param fm_scratch overwritten with the transpose
    2087              : ! **************************************************************************************************
    2088          112 :    SUBROUTINE symmetrise_in_place(fm_a, fm_scratch)
    2089              : 
    2090              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_a, fm_scratch
    2091              : 
    2092          112 :       CALL cp_fm_transpose(fm_a, fm_scratch)
    2093          112 :       CALL cp_fm_scale_and_add(0.5_dp, fm_a, 0.5_dp, fm_scratch)
    2094              : 
    2095          112 :    END SUBROUTINE symmetrise_in_place
    2096              : 
    2097              : ! **************************************************************************************************
    2098              : !> \brief Orthonormal coefficient vectors of the restart basis from the candidate columns in their
    2099              : !>        order of priority, at most n_cap of them: per candidate k, u_p = cand_pk -
    2100              : !>        sum_j (sum_q coef_restart_qj cand_qk) coef_restart_pj over the vectors j kept so far, twice,
    2101              : !>        then coef_restart_p,new = u_p/|u| unless |u| is below coef_norm_drop
    2102              : !> \param cand candidate coefficient columns, zero-padded to m rows: the current Ritz vectors, then
    2103              : !>        the previous ones
    2104              : !> \param m rows of the coefficient vectors, the current basis size
    2105              : !> \param n_cand candidate columns
    2106              : !> \param n_cap largest number of vectors kept
    2107              : !> \param coef_restart m x n_new restart basis
    2108              : !> \param n_new vectors kept, at most MIN(n_cap, n_cand)
    2109              : ! **************************************************************************************************
    2110           24 :    SUBROUTINE restart_basis(cand, m, n_cand, n_cap, coef_restart, n_new)
    2111              : 
    2112              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: cand
    2113              :       INTEGER, INTENT(IN)                                :: m, n_cand, n_cap
    2114              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(OUT)        :: coef_restart
    2115              :       INTEGER, INTENT(OUT)                               :: n_new
    2116              : 
    2117              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'restart_basis'
    2118              :       REAL(KIND=dp), PARAMETER                           :: coef_norm_drop = 1.0E-8_dp
    2119              : 
    2120              :       INTEGER                                            :: handle, ipass, j, k
    2121              :       REAL(KIND=dp)                                      :: norm
    2122           24 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: basis
    2123              : 
    2124           24 :       CALL timeset(routineN, handle)
    2125              : 
    2126              :       ! never more columns than candidates: the paired solver's cap is close to m_max
    2127           96 :       ALLOCATE (basis(m, MIN(n_cap, n_cand)))
    2128           24 :       n_new = 0
    2129          360 :       DO k = 1, n_cand
    2130          352 :          IF (n_new == n_cap) EXIT
    2131         6440 :          basis(:, n_new + 1) = cand(1:m, k)
    2132              :          ! a previous vector is nearly parallel to its successor, one pass leaves the basis
    2133              :          ! non-orthonormal at the 1e-6 level and stalls the residuals there
    2134          992 :          DO ipass = 1, 2
    2135         4898 :             DO j = 1, n_new
    2136              :                basis(:, n_new + 1) = basis(:, n_new + 1) - &
    2137       159510 :                                      DOT_PRODUCT(basis(:, j), basis(:, n_new + 1))*basis(:, j)
    2138              :             END DO
    2139        12750 :             norm = NORM2(basis(:, n_new + 1))
    2140          664 :             IF (norm < coef_norm_drop) EXIT
    2141        12956 :             basis(:, n_new + 1) = basis(:, n_new + 1)/norm
    2142              :          END DO
    2143              :          ! relative: a candidate that keeps less than this of its unit norm repeats the vectors kept
    2144          336 :          IF (norm < coef_norm_drop) CYCLE
    2145          360 :          n_new = n_new + 1
    2146              :       END DO
    2147        15676 :       coef_restart(:, :) = 0.0_dp
    2148         6334 :       coef_restart(1:m, 1:n_new) = basis(:, 1:n_new)
    2149           24 :       DEALLOCATE (basis)
    2150              : 
    2151           24 :       CALL timestop(handle)
    2152              : 
    2153           24 :    END SUBROUTINE restart_basis
    2154              : 
    2155              : ! **************************************************************************************************
    2156              : !> \brief out_ia,k = alpha sum_m V_ia,m coef_mk + beta out_ia,k for m <= nv, k <= nc on the local rows ia
    2157              : !>        On the npe x 1 grid whole columns are local, so the rotation is one local DGEMM
    2158              : !> \param fm_V basis, columns 1..nv read
    2159              : !> \param nv columns of fm_V read
    2160              : !> \param coef nv x nc coefficients, replicated
    2161              : !> \param nc columns written
    2162              : !> \param fm_out receives the rotated columns
    2163              : !> \param alpha scale of the product
    2164              : !> \param beta scale of the previous content of fm_out
    2165              : !> \param out_col first column of fm_out written, 1 by default
    2166              : ! **************************************************************************************************
    2167          452 :    SUBROUTINE subspace_rotate(fm_V, nv, coef, nc, fm_out, alpha, beta, out_col)
    2168              : 
    2169              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_V
    2170              :       INTEGER, INTENT(IN)                                :: nv
    2171              :       REAL(KIND=dp), CONTIGUOUS, DIMENSION(:, :), &
    2172              :          INTENT(IN)                                      :: coef
    2173              :       INTEGER, INTENT(IN)                                :: nc
    2174              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_out
    2175              :       REAL(KIND=dp), INTENT(IN)                          :: alpha, beta
    2176              :       INTEGER, INTENT(IN), OPTIONAL                      :: out_col
    2177              : 
    2178              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'subspace_rotate'
    2179              : 
    2180              :       INTEGER                                            :: handle, nrow_local, o_col
    2181              : 
    2182          226 :       CALL timeset(routineN, handle)
    2183              : 
    2184          226 :       o_col = 1
    2185          226 :       IF (PRESENT(out_col)) o_col = out_col
    2186          226 :       CALL cp_fm_get_info(fm_V, nrow_local=nrow_local)
    2187          226 :       IF (nrow_local > 0) THEN
    2188              :          CALL DGEMM('N', 'N', nrow_local, nc, nv, alpha, fm_V%local_data, SIZE(fm_V%local_data, 1), &
    2189          226 :                     coef, SIZE(coef, 1), beta, fm_out%local_data(:, o_col:o_col + nc - 1), SIZE(fm_out%local_data, 1))
    2190              :       END IF
    2191              : 
    2192          226 :       CALL timestop(handle)
    2193              : 
    2194          226 :    END SUBROUTINE subspace_rotate
    2195              : 
    2196              : ! **************************************************************************************************
    2197              : !> \brief V_ia,k <- sum_p V_ia,p coef_pk for p <= m, k <= nq through the work matrix, and the same rotation
    2198              : !>        of up to two further matrices that share the basis, so that a thick restart is one call
    2199              : !> \param fm_V basis; on exit columns 1..nq hold the rotation and nq+1..m are zero
    2200              : !> \param m columns of fm_V read
    2201              : !> \param coef restart basis, m x nq
    2202              : !> \param nq columns of the rotated basis
    2203              : !> \param fm_work at least nq columns
    2204              : !> \param fm_V2 rotated alike
    2205              : !> \param fm_V3 rotated alike
    2206              : ! **************************************************************************************************
    2207           24 :    SUBROUTINE rotate_in_place(fm_V, m, coef, nq, fm_work, fm_V2, fm_V3)
    2208              : 
    2209              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_V
    2210              :       INTEGER, INTENT(IN)                                :: m
    2211              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: coef
    2212              :       INTEGER, INTENT(IN)                                :: nq
    2213              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_work
    2214              :       TYPE(cp_fm_type), INTENT(IN), OPTIONAL             :: fm_V2, fm_V3
    2215              : 
    2216           24 :       CALL rotate_one(fm_V)
    2217           24 :       IF (PRESENT(fm_V2)) CALL rotate_one(fm_V2)
    2218           24 :       IF (PRESENT(fm_V3)) CALL rotate_one(fm_V3)
    2219              : 
    2220              :    CONTAINS
    2221              : 
    2222              : ! **************************************************************************************************
    2223              : !> \brief The rotation of one matrix
    2224              : !> \param fm rotated in place
    2225              : ! **************************************************************************************************
    2226          132 :       SUBROUTINE rotate_one(fm)
    2227              : 
    2228              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm
    2229              : 
    2230              :       INTEGER                                            :: nrow_local
    2231              : 
    2232        17726 :          CALL subspace_rotate(fm, m, coef(1:m, 1:nq), nq, fm_work, 1.0_dp, 0.0_dp)
    2233           66 :          CALL cp_fm_to_fm(fm_work, fm, nq)
    2234           66 :          CALL cp_fm_get_info(fm, nrow_local=nrow_local)
    2235         7716 :          fm%local_data(1:nrow_local, nq + 1:m) = 0.0_dp
    2236              : 
    2237           66 :       END SUBROUTINE rotate_one
    2238              : 
    2239              :    END SUBROUTINE rotate_in_place
    2240              : ! **************************************************************************************************
    2241              : !> \brief T_ia,t0+k-1 <- T_ia,t0+k-1 + alpha U_ia,u0+k-1 for k <= n
    2242              : !>        On the npe x 1 grid whole columns are local, so the update needs no communication
    2243              : !> \param alpha scale of the added columns
    2244              : !> \param fm_U columns added
    2245              : !> \param u0 first column read
    2246              : !> \param fm_T columns updated
    2247              : !> \param t0 first column written
    2248              : !> \param n number of columns
    2249              : ! **************************************************************************************************
    2250          100 :    SUBROUTINE columns_axpy(alpha, fm_U, u0, fm_T, t0, n)
    2251              : 
    2252              :       REAL(KIND=dp), INTENT(IN)                          :: alpha
    2253              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_U
    2254              :       INTEGER, INTENT(IN)                                :: u0
    2255              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_T
    2256              :       INTEGER, INTENT(IN)                                :: t0, n
    2257              : 
    2258              :       INTEGER                                            :: nrow_local
    2259              : 
    2260          100 :       CALL cp_fm_get_info(fm_T, nrow_local=nrow_local)
    2261              :       fm_T%local_data(1:nrow_local, t0:t0 + n - 1) = fm_T%local_data(1:nrow_local, t0:t0 + n - 1) + &
    2262         7800 :                                                      alpha*fm_U%local_data(1:nrow_local, u0:u0 + n - 1)
    2263              : 
    2264          100 :    END SUBROUTINE columns_axpy
    2265              : 
    2266              : ! **************************************************************************************************
    2267              : !> \brief G_kl = sum_ia U_ia,u0+k-1 V_ia,v0+l-1 for k <= nu, l <= nv, local product and sum over the ranks
    2268              : !>        The npe x 1 grid splits the rows over the ranks, which is what the one allreduce completes
    2269              : !> \param fm_U left factor
    2270              : !> \param u0 first column of fm_U
    2271              : !> \param nu columns of fm_U
    2272              : !> \param fm_V right factor
    2273              : !> \param v0 first column of fm_V
    2274              : !> \param nv columns of fm_V
    2275              : !> \param para_env communicator of the row-distributed matrices
    2276              : !> \param gram nu x nv, replicated
    2277              : ! **************************************************************************************************
    2278          848 :    SUBROUTINE subspace_gram(fm_U, u0, nu, fm_V, v0, nv, para_env, gram)
    2279              : 
    2280              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_U
    2281              :       INTEGER, INTENT(IN)                                :: u0, nu
    2282              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_V
    2283              :       INTEGER, INTENT(IN)                                :: v0, nv
    2284              :       TYPE(mp_para_env_type), INTENT(IN)                 :: para_env
    2285              :       REAL(KIND=dp), CONTIGUOUS, DIMENSION(:, :), &
    2286              :          INTENT(OUT)                                     :: gram
    2287              : 
    2288              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'subspace_gram'
    2289              : 
    2290              :       INTEGER                                            :: handle, nrow_local
    2291              : 
    2292          424 :       CALL timeset(routineN, handle)
    2293              : 
    2294          424 :       CALL cp_fm_get_info(fm_U, nrow_local=nrow_local)
    2295        16036 :       gram(:, :) = 0.0_dp
    2296          424 :       IF (nrow_local > 0) THEN
    2297              :          CALL DGEMM('T', 'N', nu, nv, nrow_local, 1.0_dp, &
    2298              :                     fm_U%local_data(:, u0:u0 + nu - 1), SIZE(fm_U%local_data, 1), &
    2299              :                     fm_V%local_data(:, v0:v0 + nv - 1), SIZE(fm_V%local_data, 1), &
    2300          424 :                     0.0_dp, gram, SIZE(gram, 1))
    2301              :       END IF
    2302          424 :       CALL para_env%sum(gram)
    2303              : 
    2304          424 :       CALL timestop(handle)
    2305              : 
    2306          424 :    END SUBROUTINE subspace_gram
    2307              : 
    2308              : ! **************************************************************************************************
    2309              : !> \brief Overlaps of the whole basis U with a block of nv columns of V, written into the distributed
    2310              : !>        reduced matrix: fm_red_k,v0+l-1 <- sum_ia U_ia,k V_ia,v0+l-1 for k <= nu, l <= nv, in column
    2311              : !>        blocks of nb so that the replicated temporary is nu x nb and never nu x nv
    2312              : !> \param fm_U the basis, columns 1..nu
    2313              : !> \param nu columns of fm_U, rows of fm_red written
    2314              : !> \param fm_V the block of columns
    2315              : !> \param v0 first column of fm_V read and first column of fm_red written
    2316              : !> \param nv columns of fm_V read
    2317              : !> \param nb width of one column block
    2318              : !> \param para_env communicator of the row-distributed matrices
    2319              : !> \param fm_red the distributed reduced matrix, on the diagonalisation grid
    2320              : ! **************************************************************************************************
    2321           12 :    SUBROUTINE reduced_gram_blocks(fm_U, nu, fm_V, v0, nv, nb, para_env, fm_red)
    2322              : 
    2323              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_U
    2324              :       INTEGER, INTENT(IN)                                :: nu
    2325              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_V
    2326              :       INTEGER, INTENT(IN)                                :: v0, nv, nb
    2327              :       TYPE(mp_para_env_type), INTENT(IN)                 :: para_env
    2328              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_red
    2329              : 
    2330              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'reduced_gram_blocks'
    2331              : 
    2332              :       INTEGER                                            :: c0, handle, nc
    2333           12 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: gram_block
    2334              : 
    2335           12 :       CALL timeset(routineN, handle)
    2336              : 
    2337           48 :       ALLOCATE (gram_block(nu, nb))
    2338           28 :       DO c0 = v0, v0 + nv - 1, nb
    2339           16 :          nc = MIN(nb, v0 + nv - c0)
    2340           16 :          CALL subspace_gram(fm_U, 1, nu, fm_V, c0, nc, para_env, gram_block(:, 1:nc))
    2341           28 :          CALL cp_fm_set_submatrix(fm_red, gram_block(:, 1:nc), 1, c0, nu, nc)
    2342              :       END DO
    2343           12 :       DEALLOCATE (gram_block)
    2344              : 
    2345           12 :       CALL timestop(handle)
    2346              : 
    2347           12 :    END SUBROUTINE reduced_gram_blocks
    2348              : 
    2349              : ! **************************************************************************************************
    2350              : !> \brief fm_red_p,m+l <- red_block_pl for p <= m+nt and fm_red_m+l,p <- red_block_pl for p <= m,
    2351              : !>        l <= nt: the two blocks a basis extension by nt columns adds to the symmetric reduced matrix
    2352              : !> \param fm_red the reduced matrix, at least m+nt rows and columns
    2353              : !> \param red_block (m+nt) x nt, the new columns' overlaps with the whole extended basis
    2354              : !> \param m live dimension before the extension
    2355              : !> \param nt columns added
    2356              : ! **************************************************************************************************
    2357           60 :    SUBROUTINE reduced_extend(fm_red, red_block, m, nt)
    2358              : 
    2359              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_red
    2360              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: red_block
    2361              :       INTEGER, INTENT(IN)                                :: m, nt
    2362              : 
    2363           60 :       CALL cp_fm_set_submatrix(fm_red, red_block, 1, m + 1, m + nt, nt)
    2364           60 :       IF (m > 0) CALL cp_fm_set_submatrix(fm_red, red_block(1:m, :), m + 1, 1, nt, m, transpose=.TRUE.)
    2365              : 
    2366           60 :    END SUBROUTINE reduced_extend
    2367              : 
    2368              : ! **************************************************************************************************
    2369              : !> \brief A new m x m matrix on blacs_env holding the live block fm_red(1:m, 1:m)
    2370              : !> \param fm_red the reduced matrix
    2371              : !> \param m dimension of the live block
    2372              : !> \param para_env communicator of the grid
    2373              : !> \param blacs_env grid of the new matrix
    2374              : !> \param name name of the new matrix
    2375              : !> \param fm_live created here, released by the caller
    2376              : ! **************************************************************************************************
    2377          168 :    SUBROUTINE reduced_live_block(fm_red, m, para_env, blacs_env, name, fm_live)
    2378              : 
    2379              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_red
    2380              :       INTEGER, INTENT(IN)                                :: m
    2381              :       TYPE(mp_para_env_type), INTENT(IN), TARGET         :: para_env
    2382              :       TYPE(cp_blacs_env_type), INTENT(IN), TARGET        :: blacs_env
    2383              :       CHARACTER(LEN=*), INTENT(IN)                       :: name
    2384              :       TYPE(cp_fm_type), INTENT(OUT)                      :: fm_live
    2385              : 
    2386              :       TYPE(cp_fm_struct_type), POINTER                   :: fm_struct
    2387              : 
    2388           84 :       NULLIFY (fm_struct)
    2389              :       CALL cp_fm_struct_create(fm_struct, para_env=para_env, context=blacs_env, &
    2390           84 :                                nrow_global=m, ncol_global=m)
    2391           84 :       CALL cp_fm_create(fm_live, fm_struct, name=name)
    2392           84 :       CALL cp_fm_struct_release(fm_struct)
    2393           84 :       CALL cp_fm_to_fm_submat(fm_red, fm_live, m, m, 1, 1, 1, 1)
    2394              : 
    2395           84 :    END SUBROUTINE reduced_live_block
    2396              : 
    2397              : ! **************************************************************************************************
    2398              : !> \brief fm_red_ij <- sum_pq coef_restart_pi fm_red_pq coef_restart_qj for p, q <= m and i, j <= k,
    2399              : !>        the rest of the matrix zeroed: two distributed products, no replicated m x m
    2400              : !> \param fm_red the reduced matrix, rotated in place
    2401              : !> \param m live dimension before the restart
    2402              : !> \param coef_restart replicated, zero-padded to m rows
    2403              : !> \param k vectors of the restart basis, the live dimension after
    2404              : !> \param para_env communicator of the grid
    2405              : !> \param blacs_env grid of fm_red and of the products
    2406              : ! **************************************************************************************************
    2407           40 :    SUBROUTINE reduced_rotate(fm_red, m, coef_restart, k, para_env, blacs_env)
    2408              : 
    2409              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_red
    2410              :       INTEGER, INTENT(IN)                                :: m
    2411              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: coef_restart
    2412              :       INTEGER, INTENT(IN)                                :: k
    2413              :       TYPE(mp_para_env_type), INTENT(IN), TARGET         :: para_env
    2414              :       TYPE(cp_blacs_env_type), INTENT(IN), TARGET        :: blacs_env
    2415              : 
    2416              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'reduced_rotate'
    2417              : 
    2418              :       INTEGER                                            :: handle
    2419              :       TYPE(cp_fm_struct_type), POINTER                   :: fm_struct
    2420              :       TYPE(cp_fm_type)                                   :: fm_GQ, fm_live, fm_Q, fm_QGQ
    2421              : 
    2422           40 :       CALL timeset(routineN, handle)
    2423              : 
    2424           40 :       CALL reduced_live_block(fm_red, m, para_env, blacs_env, "bse_reduced_live", fm_live)
    2425           40 :       NULLIFY (fm_struct)
    2426              :       CALL cp_fm_struct_create(fm_struct, para_env=para_env, context=blacs_env, &
    2427           40 :                                nrow_global=m, ncol_global=k)
    2428           40 :       CALL cp_fm_create(fm_Q, fm_struct, name="bse_restart_basis")
    2429           40 :       CALL cp_fm_create(fm_GQ, fm_struct, name="bse_reduced_GQ")
    2430           40 :       CALL cp_fm_struct_release(fm_struct)
    2431              :       CALL cp_fm_struct_create(fm_struct, para_env=para_env, context=blacs_env, &
    2432           40 :                                nrow_global=k, ncol_global=k)
    2433           40 :       CALL cp_fm_create(fm_QGQ, fm_struct, name="bse_reduced_QGQ")
    2434           40 :       CALL cp_fm_struct_release(fm_struct)
    2435              : 
    2436           40 :       CALL cp_fm_set_submatrix(fm_Q, coef_restart(1:m, 1:k))
    2437              :       ! GQ_pj = sum_q fm_red_pq coef_restart_qj, then QGQ_ij = sum_p coef_restart_pi GQ_pj
    2438           40 :       CALL parallel_gemm('N', 'N', m, k, m, 1.0_dp, fm_live, fm_Q, 0.0_dp, fm_GQ)
    2439           40 :       CALL parallel_gemm('T', 'N', k, k, m, 1.0_dp, fm_Q, fm_GQ, 0.0_dp, fm_QGQ)
    2440           40 :       CALL cp_fm_set_all(fm_red, 0.0_dp)
    2441           40 :       CALL cp_fm_to_fm_submat(fm_QGQ, fm_red, k, k, 1, 1, 1, 1)
    2442              : 
    2443           40 :       CALL cp_fm_release(fm_live)
    2444           40 :       CALL cp_fm_release(fm_Q)
    2445           40 :       CALL cp_fm_release(fm_GQ)
    2446           40 :       CALL cp_fm_release(fm_QGQ)
    2447              : 
    2448           40 :       CALL timestop(handle)
    2449              : 
    2450           40 :    END SUBROUTINE reduced_rotate
    2451              : 
    2452              : ! **************************************************************************************************
    2453              : !> \brief max_kl |sum_ia U_ia,k V_ia,l - δ_kl| over k, l <= m, in column blocks of nb
    2454              : !> \param fm_U left basis
    2455              : !> \param fm_V right basis, fm_U for the Euclidean case
    2456              : !> \param m columns compared
    2457              : !> \param nb width of one column block
    2458              : !> \param para_env communicator of the row-distributed matrices
    2459              : !> \return the largest deviation
    2460              : ! **************************************************************************************************
    2461            0 :    FUNCTION orthonormality_deviation(fm_U, fm_V, m, nb, para_env) RESULT(dev)
    2462              : 
    2463              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_U, fm_V
    2464              :       INTEGER, INTENT(IN)                                :: m, nb
    2465              :       TYPE(mp_para_env_type), INTENT(IN)                 :: para_env
    2466              :       REAL(KIND=dp)                                      :: dev
    2467              : 
    2468              :       INTEGER                                            :: c0, l, nc
    2469            0 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: gram_block
    2470              : 
    2471            0 :       ALLOCATE (gram_block(m, nb))
    2472            0 :       dev = 0.0_dp
    2473            0 :       DO c0 = 1, m, nb
    2474            0 :          nc = MIN(nb, m - c0 + 1)
    2475            0 :          CALL subspace_gram(fm_U, 1, m, fm_V, c0, nc, para_env, gram_block(:, 1:nc))
    2476            0 :          DO l = 1, nc
    2477            0 :             gram_block(c0 + l - 1, l) = gram_block(c0 + l - 1, l) - 1.0_dp
    2478              :          END DO
    2479            0 :          dev = MAX(dev, MAXVAL(ABS(gram_block(:, 1:nc))))
    2480              :       END DO
    2481            0 :       DEALLOCATE (gram_block)
    2482              : 
    2483            0 :    END FUNCTION orthonormality_deviation
    2484              : 
    2485              : ! **************************************************************************************************
    2486              : !> \brief Euclidean norms of nt columns of a row-distributed matrix
    2487              : !>        Each rank sums the squares of its own rows of the npe x 1 grid, then one allreduce
    2488              : !> \param fm_T row-distributed matrix
    2489              : !> \param first_col first column measured
    2490              : !> \param nt number of columns
    2491              : !> \param para_env communicator of the rows
    2492              : !> \param norms nt Euclidean norms
    2493              : ! **************************************************************************************************
    2494          136 :    SUBROUTINE column_norms(fm_T, first_col, nt, para_env, norms)
    2495              : 
    2496              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_T
    2497              :       INTEGER, INTENT(IN)                                :: first_col, nt
    2498              :       TYPE(mp_para_env_type), INTENT(IN)                 :: para_env
    2499              :       REAL(KIND=dp), DIMENSION(:), INTENT(OUT)           :: norms
    2500              : 
    2501              :       INTEGER                                            :: k, nrow_local
    2502              : 
    2503          136 :       CALL cp_fm_get_info(fm_T, nrow_local=nrow_local)
    2504          826 :       norms(:) = 0.0_dp
    2505          826 :       DO k = 1, nt
    2506        17386 :          norms(k) = SUM(fm_T%local_data(1:nrow_local, first_col + k - 1)**2)
    2507              :       END DO
    2508         1516 :       CALL para_env%sum(norms)
    2509          826 :       norms(:) = SQRT(norms(:))
    2510              : 
    2511          136 :    END SUBROUTINE column_norms
    2512              : 
    2513              : ! **************************************************************************************************
    2514              : !> \brief Davidson corrections t_ia,k = r_ia,k/(d_ia - θ_k) of the selected roots k, written to fm_Z
    2515              : !>        Each rank divides the entries of its own rows of the npe x 1 grid, no communication
    2516              : !> \param fm_R residuals, column r_offset + k belongs to root k
    2517              : !> \param selected indices of the roots corrected
    2518              : !> \param theta Ritz values, indexed by root
    2519              : !> \param diag d_ia, replicated
    2520              : !> \param fm_Z receives the corrections
    2521              : !> \param first_col first column of fm_Z written
    2522              : !> \param r_offset columns of fm_R before the residuals, 0 by default
    2523              : ! **************************************************************************************************
    2524           62 :    SUBROUTINE davidson_corrections(fm_R, selected, theta, diag, fm_Z, first_col, r_offset)
    2525              : 
    2526              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_R
    2527              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: selected
    2528              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: theta, diag
    2529              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_Z
    2530              :       INTEGER, INTENT(IN)                                :: first_col
    2531              :       INTEGER, INTENT(IN), OPTIONAL                      :: r_offset
    2532              : 
    2533              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'davidson_corrections'
    2534              :       REAL(KIND=dp), PARAMETER :: eref_scale = 0.99_dp, threshold = 16.0_dp*EPSILON(1.0_dp)
    2535              : 
    2536              :       INTEGER                                            :: handle, iloc, it, k, nrow_local, r_off
    2537           62 :       INTEGER, DIMENSION(:), POINTER                     :: row_indices
    2538              :       REAL(KIND=dp)                                      :: denom
    2539              : 
    2540           62 :       CALL timeset(routineN, handle)
    2541              : 
    2542           62 :       r_off = 0
    2543           62 :       IF (PRESENT(r_offset)) r_off = r_offset
    2544           62 :       CALL cp_fm_get_info(fm_Z, nrow_local=nrow_local, row_indices=row_indices)
    2545          216 :       DO it = 1, SIZE(selected)
    2546          154 :          k = selected(it)
    2547         3912 :          DO iloc = 1, nrow_local
    2548         3696 :             denom = diag(row_indices(iloc)) - theta(k)
    2549              :             ! near-degeneracy guard as in tddfpt_compute_residual_vects: threshold detects a numerically
    2550              :             ! zero d_ia - θ_k, which the shift (1 - eref_scale) θ_k replaces by the denominator at 0.99 θ_k
    2551         3696 :             IF (ABS(denom) < threshold) denom = denom + (1.0_dp - eref_scale)*theta(k)
    2552         3850 :             fm_Z%local_data(iloc, first_col + it - 1) = fm_R%local_data(iloc, r_off + k)/denom
    2553              :          END DO
    2554              :       END DO
    2555              : 
    2556           62 :       CALL timestop(handle)
    2557              : 
    2558           62 :    END SUBROUTINE davidson_corrections
    2559              : 
    2560              : ! **************************************************************************************************
    2561              : !> \brief T_ia,k <- T_ia,k - sum_m V_ia,m sum_jb U_jb,m T_jb,k for m <= nv and nt columns k, twice;
    2562              : !>        U = V for orthonormal columns V, else the dual basis with sum_ia U_ia,m V_ia,n = δ_mn
    2563              : !>        One allreduce per pass inside the Gram, the subtraction stays on the rows of the npe x 1 grid
    2564              : !> \param fm_T columns projected
    2565              : !> \param first_col first column of fm_T projected
    2566              : !> \param nt number of columns projected
    2567              : !> \param fm_V the basis projected out
    2568              : !> \param nv columns of fm_V
    2569              : !> \param para_env communicator of the row-distributed matrices
    2570              : !> \param fm_dual U
    2571              : ! **************************************************************************************************
    2572           66 :    SUBROUTINE project_out(fm_T, first_col, nt, fm_V, nv, para_env, fm_dual)
    2573              : 
    2574              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_T
    2575              :       INTEGER, INTENT(IN)                                :: first_col, nt
    2576              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_V
    2577              :       INTEGER, INTENT(IN)                                :: nv
    2578              :       TYPE(mp_para_env_type), INTENT(IN)                 :: para_env
    2579              :       TYPE(cp_fm_type), INTENT(IN), OPTIONAL             :: fm_dual
    2580              : 
    2581              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'project_out'
    2582              : 
    2583              :       INTEGER                                            :: handle, ipass, nrow_local
    2584              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: coeff
    2585              : 
    2586           66 :       IF (nt == 0 .OR. nv == 0) RETURN
    2587           60 :       CALL timeset(routineN, handle)
    2588           60 :       CALL cp_fm_get_info(fm_T, nrow_local=nrow_local)
    2589          240 :       ALLOCATE (coeff(nv, nt))
    2590          180 :       DO ipass = 1, 2
    2591          120 :          IF (PRESENT(fm_dual)) THEN
    2592           28 :             CALL subspace_gram(fm_dual, 1, nv, fm_T, first_col, nt, para_env, coeff)
    2593              :          ELSE
    2594           92 :             CALL subspace_gram(fm_V, 1, nv, fm_T, first_col, nt, para_env, coeff)
    2595              :          END IF
    2596          180 :          IF (nrow_local > 0) THEN
    2597              :             CALL DGEMM('N', 'N', nrow_local, nt, nv, -1.0_dp, &
    2598              :                        fm_V%local_data, SIZE(fm_V%local_data, 1), coeff, nv, 1.0_dp, &
    2599          120 :                        fm_T%local_data(:, first_col:first_col + nt - 1), SIZE(fm_T%local_data, 1))
    2600              :          END IF
    2601              :       END DO
    2602           60 :       DEALLOCATE (coeff)
    2603           60 :       CALL timestop(handle)
    2604              : 
    2605           60 :    END SUBROUTINE project_out
    2606              : 
    2607              : ! **************************************************************************************************
    2608              : !> \brief Normalises nt columns and removes those that vanished in the projection
    2609              : !>        Only the norms communicate; the scaling and the zero-fill stay on the local rows
    2610              : !> \param fm_T row-distributed matrix; the kept columns move to the front of the block
    2611              : !> \param first_col first column of the block
    2612              : !> \param nt on exit the number of columns kept
    2613              : !> \param para_env communicator of the rows
    2614              : ! **************************************************************************************************
    2615           66 :    SUBROUTINE drop_small_columns(fm_T, first_col, nt, para_env)
    2616              : 
    2617              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_T
    2618              :       INTEGER, INTENT(IN)                                :: first_col
    2619              :       INTEGER, INTENT(INOUT)                             :: nt
    2620              :       TYPE(mp_para_env_type), INTENT(IN)                 :: para_env
    2621              : 
    2622              :       REAL(KIND=dp), PARAMETER                           :: col_norm_drop = 1.0E-10_dp
    2623              : 
    2624              :       INTEGER                                            :: k, n_kept, nrow_local
    2625              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: norms
    2626              : 
    2627           66 :       IF (nt == 0) RETURN
    2628           64 :       CALL cp_fm_get_info(fm_T, nrow_local=nrow_local)
    2629          192 :       ALLOCATE (norms(nt))
    2630           64 :       CALL column_norms(fm_T, first_col, nt, para_env, norms)
    2631           64 :       n_kept = 0
    2632          246 :       DO k = 1, nt
    2633              :          ! absolute: the corrections r/(d - θ) arrive unnormalised, a column below this is numerically zero
    2634          182 :          IF (norms(k) < col_norm_drop) CYCLE
    2635          182 :          n_kept = n_kept + 1
    2636              :          fm_T%local_data(1:nrow_local, first_col + n_kept - 1) = &
    2637         4614 :             fm_T%local_data(1:nrow_local, first_col + k - 1)/norms(k)
    2638              :       END DO
    2639           64 :       fm_T%local_data(1:nrow_local, first_col + n_kept:first_col + nt - 1) = 0.0_dp
    2640           64 :       nt = n_kept
    2641           64 :       DEALLOCATE (norms)
    2642              : 
    2643           64 :    END SUBROUTINE drop_small_columns
    2644              : 
    2645              : ! **************************************************************************************************
    2646              : !> \brief Orthonormalises nt columns by two Cholesky QR passes on the replicated Gram matrix,
    2647              : !>        T_ia,k <- sum_l T_ia,l (R^-1)_lk with sum_p R_pk R_pl = G_kl = sum_ia T_ia,k T_ia,l. A
    2648              : !>        failed factorization means a numerically dependent block: the directions of the Gram matrix
    2649              : !>        below the drop tolerance are removed (canonical orthogonalisation), nt shrinks, and the
    2650              : !>        passes start again. The Gram is the only communication: R is factorised on one rank and
    2651              : !>        broadcast, the triangular solve runs on the rows of the npe x 1 grid
    2652              : !> \param fm_T the block to orthonormalise, columns first_col .. first_col+nt-1
    2653              : !> \param first_col first column of the block
    2654              : !> \param nt columns of the block; on exit the number kept
    2655              : !> \param para_env communicator of the row-distributed matrix
    2656              : !> \param ok .FALSE. when two drop rounds still leave no factorisable Gram matrix, or no direction survives
    2657              : !> \param n_dropped columns removed as dependent
    2658              : ! **************************************************************************************************
    2659           66 :    SUBROUTINE cholqr2(fm_T, first_col, nt, para_env, ok, n_dropped)
    2660              : 
    2661              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_T
    2662              :       INTEGER, INTENT(IN)                                :: first_col
    2663              :       INTEGER, INTENT(INOUT)                             :: nt
    2664              :       TYPE(mp_para_env_type), INTENT(IN)                 :: para_env
    2665              :       LOGICAL, INTENT(OUT)                               :: ok
    2666              :       INTEGER, INTENT(OUT), OPTIONAL                     :: n_dropped
    2667              : 
    2668              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'cholqr2'
    2669              :       REAL(KIND=dp), PARAMETER                           :: eig_drop_rel = 1.0E-10_dp
    2670              : 
    2671              :       INTEGER                                            :: handle, info, ipass, k, n_drop_calls, &
    2672              :                                                             n_keep, nrow_local
    2673           66 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: eig
    2674           66 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: chol_factor, gram, gram_eigvec, T_reduced
    2675              : 
    2676           66 :       ok = .TRUE.
    2677           66 :       IF (PRESENT(n_dropped)) n_dropped = 0
    2678           66 :       IF (nt == 0) RETURN
    2679           64 :       CALL timeset(routineN, handle)
    2680           64 :       CALL cp_fm_get_info(fm_T, nrow_local=nrow_local)
    2681          384 :       ALLOCATE (gram(nt, nt), chol_factor(nt, nt))
    2682              : 
    2683           64 :       n_drop_calls = 0
    2684           64 :       ipass = 0
    2685          192 :       DO WHILE (ipass < 2)
    2686          128 :          ipass = ipass + 1
    2687              :          ! G_kl = sum_ia T_ia,k T_ia,l = sum_p R_pk R_pl
    2688          128 :          CALL subspace_gram(fm_T, first_col, nt, fm_T, first_col, nt, para_env, gram)
    2689         1840 :          chol_factor(:, :) = gram(:, :)
    2690          128 :          info = 0
    2691          128 :          IF (para_env%is_source()) CALL DPOTRF('U', nt, chol_factor, nt, info)
    2692          128 :          CALL para_env%bcast(info)
    2693          128 :          IF (info /= 0) THEN
    2694            0 :             n_drop_calls = n_drop_calls + 1
    2695            0 :             IF (n_drop_calls > 2) THEN
    2696            0 :                ok = .FALSE.
    2697            0 :                EXIT
    2698              :             END IF
    2699              :             ! T_ia,k <- sum_l T_ia,l U_lk Λ_k^-1/2 over the eigenpairs of G above the drop tolerance,
    2700              :             ! eig_drop_rel of the largest eigenvalue: directions below it are numerically dependent
    2701            0 :             ALLOCATE (eig(nt), gram_eigvec(nt, nt))
    2702            0 :             CALL solve_replicated(gram, nt, para_env, eig, gram_eigvec)
    2703            0 :             n_keep = COUNT(eig > eig_drop_rel*eig(nt))
    2704            0 :             IF (n_keep == 0) THEN
    2705            0 :                ok = .FALSE.
    2706            0 :                DEALLOCATE (eig, gram_eigvec)
    2707            0 :                EXIT
    2708              :             END IF
    2709            0 :             DO k = 1, n_keep
    2710            0 :                gram_eigvec(:, nt - n_keep + k) = gram_eigvec(:, nt - n_keep + k)/SQRT(eig(nt - n_keep + k))
    2711              :             END DO
    2712            0 :             IF (nrow_local > 0) THEN
    2713            0 :                ALLOCATE (T_reduced(nrow_local, n_keep))
    2714              :                CALL DGEMM('N', 'N', nrow_local, n_keep, nt, 1.0_dp, &
    2715              :                           fm_T%local_data(:, first_col:first_col + nt - 1), SIZE(fm_T%local_data, 1), &
    2716            0 :                           gram_eigvec(:, nt - n_keep + 1:nt), nt, 0.0_dp, T_reduced, nrow_local)
    2717            0 :                fm_T%local_data(1:nrow_local, first_col:first_col + n_keep - 1) = T_reduced(:, :)
    2718            0 :                fm_T%local_data(1:nrow_local, first_col + n_keep:first_col + nt - 1) = 0.0_dp
    2719            0 :                DEALLOCATE (T_reduced)
    2720              :             END IF
    2721            0 :             IF (PRESENT(n_dropped)) n_dropped = n_dropped + nt - n_keep
    2722            0 :             nt = n_keep
    2723            0 :             DEALLOCATE (eig, gram_eigvec, gram, chol_factor)
    2724            0 :             ALLOCATE (gram(nt, nt), chol_factor(nt, nt))
    2725            0 :             ipass = 0
    2726            0 :             CYCLE
    2727              :          END IF
    2728              :          ! T_ia,k <- sum_l T_ia,l (R^-1)_lk on the local rows
    2729          128 :          CALL para_env%bcast(chol_factor)
    2730          192 :          IF (nrow_local > 0) THEN
    2731              :             CALL DTRSM('R', 'U', 'N', 'N', nrow_local, nt, 1.0_dp, chol_factor, nt, &
    2732          128 :                        fm_T%local_data(:, first_col:first_col + nt - 1), SIZE(fm_T%local_data, 1))
    2733              :          END IF
    2734              :       END DO
    2735              : 
    2736           64 :       DEALLOCATE (gram, chol_factor)
    2737           64 :       CALL timestop(handle)
    2738              : 
    2739          130 :    END SUBROUTINE cholqr2
    2740              : 
    2741              : ! **************************************************************************************************
    2742              : !> \brief Makes the nt columns T after column m of fm_V orthonormal to the basis and to each other:
    2743              : !>        T_ia,k <- T_ia,k - sum_p V_ia,p sum_jb U_jb,p T_jb,k twice, the columns that vanished dropped, then
    2744              : !>        the Euclidean cholqr2; nt shrinks by the vanished and the dependent columns
    2745              : !> \param fm_V basis in columns 1..m, the block T in columns m+1..m+nt
    2746              : !> \param m columns of the basis
    2747              : !> \param nt columns of T; on exit the number kept
    2748              : !> \param para_env communicator of the row-distributed matrix
    2749              : !> \param n_dependent counter of the columns dropped as dependent, incremented
    2750              : !> \param fm_dual U, the dual basis with sum_ia U_ia,p V_ia,q = δ_pq; fm_V itself when absent
    2751              : ! **************************************************************************************************
    2752           66 :    SUBROUTINE extend_orthonormal(fm_V, m, nt, para_env, n_dependent, fm_dual)
    2753              : 
    2754              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_V
    2755              :       INTEGER, INTENT(IN)                                :: m
    2756              :       INTEGER, INTENT(INOUT)                             :: nt
    2757              :       TYPE(mp_para_env_type), INTENT(IN)                 :: para_env
    2758              :       INTEGER, INTENT(INOUT)                             :: n_dependent
    2759              :       TYPE(cp_fm_type), INTENT(IN), OPTIONAL             :: fm_dual
    2760              : 
    2761              :       INTEGER                                            :: n_dropped
    2762              :       LOGICAL                                            :: ok
    2763              : 
    2764           66 :       CALL project_out(fm_V, m + 1, nt, fm_V, m, para_env, fm_dual)
    2765           66 :       CALL drop_small_columns(fm_V, m + 1, nt, para_env)
    2766           66 :       CALL cholqr2(fm_V, m + 1, nt, para_env, ok, n_dropped)
    2767              :       ! cholqr2 gives up after two rounds of dropping dependent directions, or when none is left
    2768           66 :       IF (.NOT. ok) CPABORT("BSE Davidson: orthonormalisation broke down")
    2769           66 :       n_dependent = n_dependent + n_dropped
    2770              : 
    2771           66 :    END SUBROUTINE extend_orthonormal
    2772              : 
    2773              : ! **************************************************************************************************
    2774              : !> \brief Largest deviation of the nt new columns from orthonormality against the whole basis,
    2775              : !>        sum_ia V_ia,m D_ia,n - δ_mn for m = 1..nv+nt, n = nv+1..nv+nt; D = V in the Euclidean case
    2776              : !> \param fm_V the basis with its nt new columns
    2777              : !> \param fm_dual D, fm_V itself in the Euclidean case
    2778              : !> \param nv columns of the basis before the extension
    2779              : !> \param nt new columns
    2780              : !> \param para_env communicator of the row-distributed matrices
    2781              : !> \return the largest deviation
    2782              : ! **************************************************************************************************
    2783           70 :    FUNCTION extension_deviation(fm_V, fm_dual, nv, nt, para_env) RESULT(dev)
    2784              : 
    2785              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_V, fm_dual
    2786              :       INTEGER, INTENT(IN)                                :: nv, nt
    2787              :       TYPE(mp_para_env_type), INTENT(IN)                 :: para_env
    2788              :       REAL(KIND=dp)                                      :: dev
    2789              : 
    2790              :       INTEGER                                            :: k
    2791           70 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: gram
    2792              : 
    2793           70 :       dev = 0.0_dp
    2794           70 :       IF (nt == 0) RETURN
    2795          272 :       ALLOCATE (gram(nv + nt, nt))
    2796           68 :       CALL subspace_gram(fm_V, 1, nv + nt, fm_dual, nv + 1, nt, para_env, gram)
    2797          280 :       DO k = 1, nt
    2798          280 :          gram(nv + k, k) = gram(nv + k, k) - 1.0_dp
    2799              :       END DO
    2800         3214 :       dev = MAXVAL(ABS(gram))
    2801           68 :       DEALLOCATE (gram)
    2802              : 
    2803           70 :    END FUNCTION extension_deviation
    2804              : 
    2805              : ! **************************************************************************************************
    2806              : !> \brief Debug check of a Davidson result against the full diagonalization of the explicit
    2807              : !>        matrices: energies, and per multiplet the smallest singular value of X_ref^T X for the
    2808              : !>        TDA, or of X_ref^T X - Y_ref^T Y for ABBA. ABBA diagonalizes the Hermitian reduction
    2809              : !>        C = (A-B)^1/2 (A+B) (A-B)^1/2 with C T^n = (Ω^n)^2 T^n,
    2810              : !>        (X+Y)^n = (Ω^n)^-1/2 (A-B)^1/2 T^n, (X-Y)^n = (Ω^n)^1/2 (A-B)^-1/2 T^n.
    2811              : !>        A failed reference solve aborts: this is debug output with nothing to fall back on
    2812              : !> \param fm_A_explicit A from create_A_and_B, N_ov x N_ov
    2813              : !> \param exc_ens Davidson energies in Hartree
    2814              : !> \param fm_X Davidson X_ia^n, column n
    2815              : !> \param bse_env handed to create_hermitian_form_of_ABBA
    2816              : !> \param unit_nr output unit, positive on the writing rank only
    2817              : !> \param fm_B_explicit present for an ABBA result, together with fm_Y
    2818              : !> \param fm_Y Davidson Y_ia^n, with fm_B_explicit
    2819              : ! **************************************************************************************************
    2820            0 :    SUBROUTINE bse_davidson_refcheck(fm_A_explicit, exc_ens, fm_X, bse_env, unit_nr, fm_B_explicit, fm_Y)
    2821              : 
    2822              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_A_explicit
    2823              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: exc_ens
    2824              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_X
    2825              :       TYPE(bse_env_type), INTENT(IN)                     :: bse_env
    2826              :       INTEGER, INTENT(IN)                                :: unit_nr
    2827              :       TYPE(cp_fm_type), INTENT(IN), OPTIONAL             :: fm_B_explicit, fm_Y
    2828              : 
    2829              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'bse_davidson_refcheck'
    2830              : 
    2831              :       INTEGER                                            :: diag_info, handle, k, n_ov, n_ref, n_want
    2832              :       REAL(KIND=dp)                                      :: min_overlap
    2833            0 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: ref_ens
    2834            0 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: diff_ref, sum_ref, X_dav, X_ref, Y_dav, &
    2835            0 :                                                             Y_ref
    2836              :       TYPE(cp_fm_type)                                   :: fm_C, fm_eigvec, fm_inv_sqrt_A_minus_B, &
    2837              :                                                             fm_sqrt_A_minus_B, fm_work
    2838              : 
    2839            0 :       CALL timeset(routineN, handle)
    2840              : 
    2841              :       ! B and Y arrive together or not at all
    2842            0 :       CPASSERT(PRESENT(fm_B_explicit) .EQV. PRESENT(fm_Y))
    2843            0 :       IF (unit_nr > 0) THEN
    2844            0 :          WRITE (unit_nr, '(T2,A4)') 'BSE|'
    2845            0 :          IF (PRESENT(fm_B_explicit)) THEN
    2846            0 :             WRITE (unit_nr, '(T2,A10,T13,A)') 'BSE|DEBUG|', 'Reference check against the explicit A and B'
    2847              :          ELSE
    2848            0 :             WRITE (unit_nr, '(T2,A10,T13,A)') 'BSE|DEBUG|', 'Reference check against the explicit A'
    2849              :          END IF
    2850              :       END IF
    2851            0 :       CALL cp_fm_get_info(fm_A_explicit, nrow_global=n_ov)
    2852            0 :       n_want = SIZE(exc_ens)
    2853            0 :       ALLOCATE (ref_ens(n_ov))
    2854              : 
    2855            0 :       IF (PRESENT(fm_B_explicit)) THEN
    2856              :          CALL create_hermitian_form_of_ABBA(fm_A_explicit, fm_B_explicit, fm_C, fm_sqrt_A_minus_B, &
    2857            0 :                                             fm_inv_sqrt_A_minus_B, unit_nr, bse_env, 0.0_dp)
    2858            0 :          CALL cp_fm_create(fm_eigvec, fm_C%matrix_struct)
    2859            0 :          CALL choose_eigv_solver(fm_C, fm_eigvec, ref_ens, diag_info)
    2860            0 :          IF (diag_info /= 0) CPABORT("Reference diagonalization of C failed in the BSE Davidson check")
    2861            0 :          IF (ref_ens(1) <= 0.0_dp) THEN
    2862              :             CALL cp_abort(__LOCATION__, &
    2863            0 :                           "Reference matrix C has a non-positive eigenvalue in the BSE Davidson check")
    2864              :          END IF
    2865            0 :          ref_ens(:) = SQRT(ref_ens(:))
    2866              : 
    2867            0 :          n_ref = multiplet_end(ref_ens, n_want, n_ov, deg_thresh)
    2868            0 :          ALLOCATE (sum_ref(n_ov, n_ref), diff_ref(n_ov, n_ref))
    2869            0 :          CALL cp_fm_create(fm_work, fm_C%matrix_struct)
    2870              :          CALL parallel_gemm("N", "N", n_ov, n_ref, n_ov, 1.0_dp, fm_sqrt_A_minus_B, fm_eigvec, 0.0_dp, &
    2871            0 :                             fm_work)
    2872            0 :          CALL cp_fm_get_submatrix(fm_work, sum_ref, 1, 1, n_ov, n_ref)
    2873              :          CALL parallel_gemm("N", "N", n_ov, n_ref, n_ov, 1.0_dp, fm_inv_sqrt_A_minus_B, fm_eigvec, &
    2874            0 :                             0.0_dp, fm_work)
    2875            0 :          CALL cp_fm_get_submatrix(fm_work, diff_ref, 1, 1, n_ov, n_ref)
    2876            0 :          CALL cp_fm_release(fm_work)
    2877            0 :          CALL cp_fm_release(fm_C)
    2878            0 :          CALL cp_fm_release(fm_sqrt_A_minus_B)
    2879            0 :          CALL cp_fm_release(fm_inv_sqrt_A_minus_B)
    2880              : 
    2881            0 :          ALLOCATE (X_ref(n_ov, n_ref), Y_ref(n_ov, n_ref), Y_dav(n_ov, n_want))
    2882            0 :          DO k = 1, n_ref
    2883            0 :             sum_ref(:, k) = sum_ref(:, k)/SQRT(ref_ens(k))
    2884            0 :             diff_ref(:, k) = diff_ref(:, k)*SQRT(ref_ens(k))
    2885              :          END DO
    2886            0 :          X_ref(:, :) = 0.5_dp*(sum_ref(:, :) + diff_ref(:, :))
    2887            0 :          Y_ref(:, :) = 0.5_dp*(sum_ref(:, :) - diff_ref(:, :))
    2888            0 :          DEALLOCATE (sum_ref, diff_ref)
    2889            0 :          CALL cp_fm_get_submatrix(fm_Y, Y_dav)
    2890              :       ELSE
    2891              :          ! the eigensolver destroys its input
    2892            0 :          CALL cp_fm_create(fm_work, fm_A_explicit%matrix_struct)
    2893            0 :          CALL cp_fm_to_fm(fm_A_explicit, fm_work)
    2894            0 :          CALL cp_fm_create(fm_eigvec, fm_A_explicit%matrix_struct)
    2895            0 :          CALL choose_eigv_solver(fm_work, fm_eigvec, ref_ens, diag_info)
    2896            0 :          IF (diag_info /= 0) CPABORT("Reference diagonalization of A failed in the BSE Davidson check")
    2897            0 :          CALL cp_fm_release(fm_work)
    2898              : 
    2899            0 :          n_ref = multiplet_end(ref_ens, n_want, n_ov, deg_thresh)
    2900            0 :          ALLOCATE (X_ref(n_ov, n_ref))
    2901            0 :          CALL cp_fm_get_submatrix(fm_eigvec, X_ref, 1, 1, n_ov, n_ref)
    2902              :       END IF
    2903            0 :       CALL cp_fm_release(fm_eigvec)
    2904              : 
    2905            0 :       ALLOCATE (X_dav(n_ov, n_want))
    2906            0 :       CALL cp_fm_get_submatrix(fm_X, X_dav)
    2907              : 
    2908              :       ! Y_ref and Y_dav stay unallocated for the TDA and then count as absent (Fortran 2008)
    2909            0 :       CALL min_multiplet_overlap(ref_ens, n_want, n_ref, X_ref, X_dav, min_overlap, Y_ref, Y_dav)
    2910            0 :       CALL print_refcheck(MAXVAL(ABS(exc_ens(:) - ref_ens(1:n_want))), min_overlap, unit_nr)
    2911              : 
    2912            0 :       DEALLOCATE (ref_ens, X_ref, X_dav)
    2913            0 :       IF (ALLOCATED(Y_ref)) DEALLOCATE (Y_ref, Y_dav)
    2914              : 
    2915            0 :       CALL timestop(handle)
    2916              : 
    2917            0 :    END SUBROUTINE bse_davidson_refcheck
    2918              : 
    2919              : ! **************************************************************************************************
    2920              : !> \brief Smallest singular value over the multiplets of O_kl = sum_ia (X_ref,ia^k X_ia^l - Y_ref,ia^k Y_ia^l),
    2921              : !>        k over a multiplet of the reference and l over the Davidson states inside it. Degenerate
    2922              : !>        eigenvectors are arbitrary within their multiplet, so the vectors are not compared one by one:
    2923              : !>        every singular value of O is 1 when the Davidson states lie in the span of the multiplet
    2924              : !> \param ref_ens reference energies in Hartree, ascending
    2925              : !> \param n_want Davidson states compared
    2926              : !> \param n_ref reference states up to the end of the multiplet that contains state n_want
    2927              : !> \param X_ref reference X, N_ov x n_ref
    2928              : !> \param X_dav Davidson X, N_ov x n_want
    2929              : !> \param min_overlap the smallest singular value found, 1 if every multiplet is reproduced
    2930              : !> \param Y_ref reference Y for ABBA, with Y_dav
    2931              : !> \param Y_dav Davidson Y for ABBA
    2932              : ! **************************************************************************************************
    2933            0 :    SUBROUTINE min_multiplet_overlap(ref_ens, n_want, n_ref, X_ref, X_dav, min_overlap, Y_ref, Y_dav)
    2934              : 
    2935              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: ref_ens
    2936              :       INTEGER, INTENT(IN)                                :: n_want, n_ref
    2937              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: X_ref, X_dav
    2938              :       REAL(KIND=dp), INTENT(OUT)                         :: min_overlap
    2939              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN), &
    2940              :          OPTIONAL                                        :: Y_ref, Y_dav
    2941              : 
    2942              :       INTEGER                                            :: info, lwork, mult_first, mult_last, &
    2943              :                                                             mult_last_dav
    2944            0 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: sing_vals, work
    2945            0 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: overlap
    2946              :       REAL(KIND=dp), DIMENSION(1, 1)                     :: dummy
    2947              : 
    2948            0 :       min_overlap = 1.0_dp
    2949            0 :       mult_first = 1
    2950            0 :       DO WHILE (mult_first <= n_want)
    2951            0 :          mult_last = multiplet_end(ref_ens, mult_first, n_ref, deg_thresh)
    2952            0 :          mult_last_dav = MIN(mult_last, n_want)
    2953            0 :          ALLOCATE (overlap(mult_last - mult_first + 1, mult_last_dav - mult_first + 1), sing_vals(mult_last_dav - mult_first + 1))
    2954            0 :          overlap(:, :) = MATMUL(TRANSPOSE(X_ref(:, mult_first:mult_last)), X_dav(:, mult_first:mult_last_dav))
    2955            0 :          IF (PRESENT(Y_ref)) THEN
    2956            0 :             overlap(:, :) = overlap(:, :) - MATMUL(TRANSPOSE(Y_ref(:, mult_first:mult_last)), Y_dav(:, mult_first:mult_last_dav))
    2957              :          END IF
    2958              :          ! DGESVD wants lwork >= MAX(3 n + m, 5 n) for the m x n overlap with n <= m; 5 m + 10 covers both
    2959            0 :          lwork = 5*(mult_last - mult_first + 1) + 10
    2960            0 :          ALLOCATE (work(lwork))
    2961              :  CALL DGESVD('N', 'N', mult_last - mult_first + 1, mult_last_dav - mult_first + 1, overlap, mult_last - mult_first + 1, sing_vals, &
    2962            0 :                      dummy, 1, dummy, 1, work, lwork, info)
    2963            0 :          IF (info /= 0) CPABORT("SVD failed in the BSE Davidson check")
    2964            0 :          min_overlap = MIN(min_overlap, MINVAL(sing_vals))
    2965            0 :          DEALLOCATE (overlap, sing_vals, work)
    2966            0 :          mult_first = mult_last + 1
    2967              :       END DO
    2968              : 
    2969            0 :    END SUBROUTINE min_multiplet_overlap
    2970              : 
    2971              : ! **************************************************************************************************
    2972              : !> \brief The two lines of a reference check
    2973              : !> \param dev_E largest energy deviation in Hartree
    2974              : !> \param min_overlap smallest singular value from min_multiplet_overlap
    2975              : !> \param unit_nr output unit, positive on the writing rank only
    2976              : ! **************************************************************************************************
    2977            0 :    SUBROUTINE print_refcheck(dev_E, min_overlap, unit_nr)
    2978              : 
    2979              :       REAL(KIND=dp), INTENT(IN)                          :: dev_E, min_overlap
    2980              :       INTEGER, INTENT(IN)                                :: unit_nr
    2981              : 
    2982            0 :       IF (unit_nr > 0) THEN
    2983            0 :          WRITE (unit_nr, '(T2,A10,T13,A,T63,ES18.6)') 'BSE|DEBUG|', &
    2984            0 :             'Max energy deviation vs full diagonalization (eV)', dev_E*evolt
    2985            0 :          WRITE (unit_nr, '(T2,A10,T13,A,T63,F18.12)') 'BSE|DEBUG|', &
    2986            0 :             'Min subspace overlap vs full diagonalization', min_overlap
    2987              :       END IF
    2988              : 
    2989            0 :    END SUBROUTINE print_refcheck
    2990              : 
    2991            0 : END MODULE bse_davidson
        

Generated by: LCOV version 2.0-1