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