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