(git:cd2a8c4)
Loading...
Searching...
No Matches
cp_fm_diag_utils.F
Go to the documentation of this file.
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 Auxiliary tools to redistribute cp_fm_type and cp_cfm_type matrices before and after
10!> diagonalization. Heuristics are used to determine the optimal number of CPUs for
11!> diagonalization and the input matrices are redistributed if necessary
12!> \par History
13!> - [01.2018] moved redistribution related code from cp_fm_syevd here
14!> - [08.2026] added complex matrix (cp_cfm_type) counterparts
15!> \author Nico Holmberg [01.2018]
16! **************************************************************************************************
21 USE cp_cfm_types, ONLY: cp_cfm_create,&
29 USE cp_fm_types, ONLY: cp_fm_create,&
36 USE kinds, ONLY: dp
37 USE mathlib, ONLY: gcd
39#include "../base/base_uses.f90"
40
41 IMPLICIT NONE
42
43 PRIVATE
44
45 CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'cp_fm_diag_utils'
46
47 ! Information on redistribution
49 INTEGER :: matrix_order = -1
50 INTEGER :: num_pe_old = -1 ! number of processes before a potential redistribute
51 INTEGER :: num_pe_new = -1 ! number of processes after a potential redistribute
52 INTEGER :: num_pe_opt = -1 ! optimal number of processes for the given matrix
53 INTEGER :: num_pe_max_nz_col = -1 ! the maximal number of processes s.t. no column has zero width, may be < 0 if ignored
54 LOGICAL :: redistribute = .false. ! whether or not the matrix was actually redistributed
55 CONTAINS
56 PROCEDURE, pass(self) :: write => cp_fm_redistribute_info_write
58
59 ! Container for redistribution settings and temporary work structs
60 TYPE cp_fm_redistribute_type
61 ! Settings
62 INTEGER :: a = -1, x = -1
63 LOGICAL :: should_print = .false.
64 LOGICAL :: elpa_force_redistribute = .false.
65 ! Temporaries
66 INTEGER, DIMENSION(:), POINTER :: group_distribution => null(), &
67 group_partition => null()
68 TYPE(cp_blacs_env_type), POINTER :: blacs_env_new => null()
69 TYPE(mp_para_env_type), POINTER :: para_env_new => null()
70 END TYPE cp_fm_redistribute_type
71
72 ! Permanent instance of the redistribute type
73 TYPE(cp_fm_redistribute_type), PRIVATE, &
74 SAVE :: work_redistribute
75
76 ! Public subroutines
77
78 PUBLIC :: cp_fm_redistribute_start, &
83
84CONTAINS
85
86! **************************************************************************************************
87!> \brief Write the redistribute info nicely formatted to the given I/O unit
88!> \param self reference to the cp_fm_redistribute_info instance
89!> \param io_unit I/O unit to use for writing
90! **************************************************************************************************
91 SUBROUTINE cp_fm_redistribute_info_write(self, io_unit)
92 CLASS(cp_fm_redistribute_info), INTENT(IN) :: self
93 INTEGER, INTENT(IN) :: io_unit
94
95 WRITE (unit=io_unit, fmt="(A)") ""
96 WRITE (unit=io_unit, fmt="(T2,A,T71,I10)") &
97 "CP_FM_DIAG| Number of processes over which the matrix is distributed ", self%num_pe_old, &
98 "CP_FM_DIAG| Matrix order ", self%matrix_order
99 WRITE (unit=io_unit, fmt="(T2,A,T71,I10)") &
100 "CP_FM_DIAG| Optimal number of CPUs ", self%num_pe_opt
101 IF (self%num_pe_max_nz_col < 0) THEN
102 WRITE (unit=io_unit, fmt="(T2,A,T71,A10)") &
103 "CP_FM_DIAG| Maximum number of CPUs (with non-zero columns) ", "<N/A>"
104 ELSE
105 WRITE (unit=io_unit, fmt="(T2,A,T71,I10)") &
106 "CP_FM_DIAG| Maximum number of CPUs (with non-zero columns): ", self%num_pe_max_nz_col
107 END IF
108 IF (self%redistribute) THEN
109 WRITE (unit=io_unit, fmt="(T2,A,T71,I10)") &
110 "CP_FM_DIAG| Number of processes for the redistribution ", self%num_pe_new
111 ELSE
112 WRITE (unit=io_unit, fmt="(T2,A)") &
113 "CP_FM_DIAG| The matrix will NOT be redistributed"
114 END IF
115 WRITE (unit=io_unit, fmt="(A)") ""
116
117 END SUBROUTINE cp_fm_redistribute_info_write
118
119! **************************************************************************************************
120!> \brief Releases the temporary storage needed when redistributing arrays
121!> \param has_redistributed flag that determines if the processors holds a part of the
122!> redistributed array
123!> \author Nico Holmberg [01.2018]
124! **************************************************************************************************
125 SUBROUTINE cp_fm_redistribute_work_finalize(has_redistributed)
126 LOGICAL, INTENT(IN) :: has_redistributed
127
128 IF (ASSOCIATED(work_redistribute%group_distribution)) THEN
129 IF (has_redistributed) THEN
130 CALL cp_blacs_env_release(work_redistribute%blacs_env_new)
131 END IF
132 CALL work_redistribute%para_env_new%free()
133 DEALLOCATE (work_redistribute%para_env_new)
134 DEALLOCATE (work_redistribute%group_distribution)
135 DEALLOCATE (work_redistribute%group_partition)
136 END IF
137 ! Return work to its initial state
138 work_redistribute = cp_fm_redistribute_type()
139
140 END SUBROUTINE cp_fm_redistribute_work_finalize
141
142! **************************************************************************************************
143!> \brief Initializes the parameters that determine how to calculate the optimal number of CPUs
144!> for diagonalizing a matrix. The parameters are read from the GLOBAL input section.
145!> \param a integer parameter used to define the rule for determining the optimal
146!> number of CPUs for diagonalization
147!> \param x integer parameter used to define the rule for determining the optimal
148!> number of CPUs for diagonalization
149!> \param should_print flag that determines if information about the redistribution process
150!> should be printed
151!> \param elpa_force_redistribute flag that if redistribution should always be performed when
152!> the ELPA diagonalization library is in use
153!> \author Nico Holmberg [01.2018]
154! **************************************************************************************************
155 SUBROUTINE cp_fm_redistribute_init(a, x, should_print, elpa_force_redistribute)
156 INTEGER, INTENT(IN) :: a, x
157 LOGICAL, INTENT(IN) :: should_print, elpa_force_redistribute
158
159 work_redistribute%a = a
160 work_redistribute%x = x
161 work_redistribute%should_print = should_print
162 work_redistribute%elpa_force_redistribute = elpa_force_redistribute
163 ! Init work
164 work_redistribute = cp_fm_redistribute_type()
165
166 END SUBROUTINE cp_fm_redistribute_init
167
168! **************************************************************************************************
169!> \brief Calculates the optimal number of CPUs for diagonalizing a matrix.
170!> \param size the size of the diagonalized matrix
171!> \return the optimal number of CPUs
172!> \author Nico Holmberg [01.2018]
173! **************************************************************************************************
174 PURE FUNCTION cp_fm_diag_get_optimal_ncpu(size) RESULT(ncpu)
175 INTEGER, INTENT(IN) :: size
176 INTEGER :: ncpu
177
178 ncpu = ((size + work_redistribute%a*work_redistribute%x - 1)/ &
179 (work_redistribute%a*work_redistribute%x))*work_redistribute%a
180
181 END FUNCTION cp_fm_diag_get_optimal_ncpu
182
183#if defined(__parallel)
184! **************************************************************************************************
185!> \brief Determines the largest number of CPUs a matrix can be distributed on without any of the
186!> processors getting a zero-width column (currently only needed for ELPA).
187!> \param matrix the matrix that will be diagonalized
188!> \return the maximum number of CPUs for ELPA
189!> \author Nico Holmberg [01.2018]
190! **************************************************************************************************
191 FUNCTION cp_fm_max_ncpu_non_zero_column(matrix) RESULT(ncpu)
192 TYPE(cp_fm_type), INTENT(IN) :: matrix
193 INTEGER :: ncpu
194
195 INTEGER :: gcd_max, ipe, jpe, ncol_block, &
196 ncol_global, npcol, nrow_block, &
197 nrow_global, num_pe_old, nzero
198 INTEGER, DIMENSION(:), POINTER :: ncol_locals
199 INTEGER, EXTERNAL :: numroc
200
201 NULLIFY (ncol_locals)
202 ! First check if there are any zero width columns in current layout
203 CALL cp_fm_get_info(matrix, ncol_locals=ncol_locals, &
204 nrow_global=nrow_global, ncol_global=ncol_global, &
205 nrow_block=nrow_block, ncol_block=ncol_block)
206 nzero = count(ncol_locals == 0)
207 num_pe_old = matrix%matrix_struct%para_env%num_pe
208 ncpu = num_pe_old - nzero
209
210 ! Avoid layouts with odd number of CPUs (blacs grid layout will be square)
211 IF (ncpu > 2) THEN
212 ncpu = ncpu - modulo(ncpu, 2)
213 END IF
214
215 ! if there are no zero-width columns and the number of processors was even, leave it at that
216 IF (ncpu == num_pe_old) THEN
217 RETURN
218 END IF
219
220 ! Iteratively search for the maximum number of CPUs for ELPA
221 ! On each step, we test whether the blacs grid created with ncpu processes
222 ! contains any columns with zero width
223 DO WHILE (ncpu > 1)
224 ! Determine layout of new blacs grid with ncpu CPUs
225 ! (snippet copied from cp_blacs_env.F:cp_blacs_env_create)
226 gcd_max = -1
227 DO ipe = 1, ceiling(sqrt(real(ncpu, dp)))
228 jpe = ncpu/ipe
229 IF (ipe*jpe /= ncpu) THEN
230 cycle
231 END IF
232 IF (gcd(ipe, jpe) >= gcd_max) THEN
233 npcol = jpe
234 gcd_max = gcd(ipe, jpe)
235 END IF
236 END DO
237
238 ! Count the number of processors without any columns
239 ! (snippet copied from cp_fm_struct.F:cp_fm_struct_create)
240 nzero = 0
241 DO ipe = 0, npcol - 1
242 IF (numroc(ncol_global, ncol_block, ipe, 0, npcol) == 0) THEN
243 nzero = nzero + 1
244 END IF
245 END DO
246
247 IF (nzero == 0) THEN
248 EXIT
249 END IF
250
251 ncpu = ncpu - nzero
252
253 IF (ncpu > 2) THEN
254 ncpu = ncpu - modulo(ncpu, 2)
255 END IF
256 END DO
257
258 END FUNCTION cp_fm_max_ncpu_non_zero_column
259#endif
260
261! **************************************************************************************************
262!> \brief Determines the optimal number of CPUs for matrix diagonalization and redistributes
263!> the input matrices if necessary
264!> \param matrix the input cp_fm_type matrix to be diagonalized
265!> \param eigenvectors the cp_fm_type matrix that will hold the eigenvectors of the input matrix
266!> \param matrix_new the redistributed input matrix which will subsequently be diagonalized,
267!> or a pointer to the original matrix if no redistribution is required
268!> \param eigenvectors_new the redistributed eigenvectors matrix, or a pointer to the original
269!> matrix if no redistribution is required
270!> \param caller_is_elpa flag that determines if ELPA is used for diagonalization
271!> \param redist_info get info about the redistribution
272!> \par History
273!> - [01.2018] created by moving redistribution related code from cp_fm_syevd here
274!> \author Nico Holmberg [01.2018]
275! **************************************************************************************************
276 SUBROUTINE cp_fm_redistribute_start(matrix, eigenvectors, matrix_new, eigenvectors_new, &
277 caller_is_elpa, redist_info)
278
279 TYPE(cp_fm_type), INTENT(IN) :: matrix, eigenvectors
280 TYPE(cp_fm_type), INTENT(OUT) :: matrix_new, eigenvectors_new
281 LOGICAL, OPTIONAL, INTENT(IN) :: caller_is_elpa
282
283 CHARACTER(len=*), PARAMETER :: routinen = 'cp_fm_redistribute_start'
284
285 INTEGER :: handle
286 LOGICAL :: is_elpa
287 TYPE(cp_fm_redistribute_info), OPTIONAL, INTENT(OUT) :: redist_info
288
289#if defined(__parallel)
290 REAL(kind=dp) :: fake_local_data(1, 1)
291 INTEGER :: fake_descriptor(9), mepos_old, &
292 io_unit, ngroups, ncol_block, blksize, nrow_block
293 TYPE(cp_fm_struct_type), POINTER :: fm_struct_new
294 TYPE(mp_para_env_type), POINTER :: para_env
295 TYPE(cp_logger_type), POINTER :: logger
296 TYPE(cp_fm_redistribute_info) :: rdinfo
297#endif
298
299 CALL timeset(routinen, handle)
300 is_elpa = .false.
301 IF (PRESENT(caller_is_elpa)) THEN
302#if defined(__ELPA)
303 is_elpa = caller_is_elpa
304#else
305 cpabort("CP2K compiled without the ELPA library.")
306#endif
307 END IF
308
309#if defined(__parallel)
310
311 logger => cp_get_default_logger()
312 io_unit = cp_logger_get_default_io_unit(logger)
313
314 ! first figure out the optimal number of cpus
315 ! this is pure heuristics, the defaults are based on rosa timings
316 ! that demonstrate that timings go up sharply if too many tasks are used
317 ! we take a multiple of 4, and approximately n/60
318 para_env => matrix%matrix_struct%para_env
319 mepos_old = para_env%mepos
320 ncol_block = -1 ! normally we also want to adjust the block size according to the optimal # of CPUs
321 nrow_block = -1
322 blksize = -1
323
324 rdinfo%matrix_order = matrix%matrix_struct%nrow_global
325 rdinfo%num_pe_old = para_env%num_pe
326 rdinfo%num_pe_opt = cp_fm_diag_get_optimal_ncpu(rdinfo%matrix_order)
327 rdinfo%num_pe_new = rdinfo%num_pe_opt
328 rdinfo%num_pe_max_nz_col = -1
329 rdinfo%redistribute = .false.
330
331 IF (is_elpa) THEN
332 ! with ELPA we don't have to redistribute if not necessary (scales, unlike ScaLAPACK)
333 rdinfo%num_pe_new = rdinfo%num_pe_old
334
335 ! BUT: Diagonalization with ELPA fails when a processor column has zero width
336 ! Determine the maximum number of CPUs the matrix can be distributed without zero-width columns
337 ! for the current block size.
338 rdinfo%num_pe_max_nz_col = cp_fm_max_ncpu_non_zero_column(matrix)
339
340 ! if the user wants to redistribute to the ScaLAPACK optimal number of CPUs anyway, let him if it's safe.
341 IF (work_redistribute%elpa_force_redistribute .AND. rdinfo%num_pe_opt < rdinfo%num_pe_max_nz_col) THEN
342 ! Use heuristics to determine the need for redistribution (when num_pe_opt is smaller than the safe maximum)
343 ! in this case we can also take the block size used for ScaLAPACK
344 rdinfo%num_pe_new = rdinfo%num_pe_opt
345 ELSE IF (rdinfo%num_pe_old > rdinfo%num_pe_max_nz_col) THEN
346 ! Otherwise, only redistribute if we have to
347 rdinfo%num_pe_new = rdinfo%num_pe_max_nz_col
348 ! do NOT let cp_fm_struct_create automatically adjust the block size because the
349 ! calculated number of processors such that no block has 0 columns wouldn't match (see #578):
350 ! if the automatically chosen block size is larger than the present one we would still end
351 ! up with empty processors
352 END IF
353
354 CALL cp_fm_get_info(matrix, ncol_block=ncol_block, nrow_block=nrow_block)
355
356 ! On GPUs, ELPA requires the block size to be a power of 2
357 blksize = 1
358 DO WHILE (2*blksize <= min(nrow_block, ncol_block))
359 blksize = blksize*2
360 END DO
361 nrow_block = blksize
362 ncol_block = blksize
363 END IF
364
365 ! finally, only redistribute if we're going to use less CPUs than before or changed the block size
366 rdinfo%redistribute = (rdinfo%num_pe_old > rdinfo%num_pe_new) .OR. (blksize >= 0 .AND. &
367 ((blksize /= matrix%matrix_struct%ncol_block) .OR. (blksize /= matrix%matrix_struct%nrow_block)))
368
369 IF (work_redistribute%should_print .AND. io_unit > 0) THEN
370 IF (is_elpa) THEN
371 IF (work_redistribute%elpa_force_redistribute) THEN
372 WRITE (unit=io_unit, fmt="(T2,A,T78,A3)") &
373 "CP_FM_DIAG| Force redistribute (ELPA):", "YES"
374 ELSE
375 WRITE (unit=io_unit, fmt="(T2,A,T79,A2)") &
376 "CP_FM_DIAG| Force redistribute (ELPA):", "NO"
377 END IF
378 END IF
379 CALL rdinfo%write(io_unit)
380 END IF
381 CALL para_env%sync()
382
383 ! if the optimal is smaller than num_pe, we will redistribute the input matrix
384 IF (rdinfo%redistribute) THEN
385 ! split comm, the first num_pe_new tasks will do the work
386 ALLOCATE (work_redistribute%group_distribution(0:rdinfo%num_pe_old - 1))
387 ALLOCATE (work_redistribute%group_partition(0:1))
388 work_redistribute%group_partition = [rdinfo%num_pe_new, rdinfo%num_pe_old - rdinfo%num_pe_new]
389 ALLOCATE (work_redistribute%para_env_new)
390 CALL work_redistribute%para_env_new%from_split( &
391 comm=para_env, ngroups=ngroups, group_distribution=work_redistribute%group_distribution, &
392 n_subgroups=2, group_partition=work_redistribute%group_partition)
393
394 IF (work_redistribute%group_distribution(mepos_old) == 0) THEN
395
396 ! create blacs, should inherit the preferences for the layout and so on, from the higher level
397 NULLIFY (work_redistribute%blacs_env_new)
398 CALL cp_blacs_env_create(blacs_env=work_redistribute%blacs_env_new, para_env=work_redistribute%para_env_new)
399
400 ! create new matrix
401 NULLIFY (fm_struct_new)
402 IF (nrow_block == -1 .OR. ncol_block == -1) THEN
403 CALL cp_fm_struct_create(fmstruct=fm_struct_new, &
404 para_env=work_redistribute%para_env_new, &
405 context=work_redistribute%blacs_env_new, &
406 nrow_global=rdinfo%matrix_order, ncol_global=rdinfo%matrix_order, &
407 ncol_block=ncol_block, nrow_block=nrow_block)
408 ELSE
409 CALL cp_fm_struct_create(fmstruct=fm_struct_new, &
410 para_env=work_redistribute%para_env_new, &
411 context=work_redistribute%blacs_env_new, &
412 nrow_global=rdinfo%matrix_order, ncol_global=rdinfo%matrix_order, &
413 ncol_block=ncol_block, nrow_block=nrow_block, force_block=.true.)
414 END IF
415 CALL cp_fm_create(matrix_new, matrix_struct=fm_struct_new, name="yevd_new_mat")
416 CALL cp_fm_create(eigenvectors_new, matrix_struct=fm_struct_new, name="yevd_new_vec")
417 CALL cp_fm_struct_release(fm_struct_new)
418
419 ! redistribute old
420 CALL pdgemr2d(rdinfo%matrix_order, rdinfo%matrix_order, matrix%local_data(1, 1), 1, 1, &
421 matrix%matrix_struct%descriptor, &
422 matrix_new%local_data(1, 1), 1, 1, matrix_new%matrix_struct%descriptor, &
423 matrix%matrix_struct%context)
424 ELSE
425 ! these tasks must help redistribute (they own part of the data),
426 ! but need fake 'new' data, and their descriptor must indicate this with -1
427 ! see also scalapack comments on pdgemr2d
428 fake_descriptor = -1
429 CALL pdgemr2d(rdinfo%matrix_order, rdinfo%matrix_order, matrix%local_data(1, 1), 1, 1, &
430 matrix%matrix_struct%descriptor, &
431 fake_local_data(1, 1), 1, 1, fake_descriptor, &
432 matrix%matrix_struct%context)
433 END IF
434 ELSE
435 ! No need to redistribute, just return pointers to the original arrays
436 matrix_new = matrix
437 eigenvectors_new = eigenvectors
438 END IF
439
440 IF (PRESENT(redist_info)) THEN
441 redist_info = rdinfo
442 END IF
443#else
444
445 mark_used(matrix)
446 mark_used(eigenvectors)
447 mark_used(matrix_new)
448 mark_used(eigenvectors_new)
449 mark_used(redist_info)
450 cpabort("Routine called in non-parallel case.")
451#endif
452
453 CALL timestop(handle)
454
455 END SUBROUTINE cp_fm_redistribute_start
456
457! **************************************************************************************************
458!> \brief Redistributes eigenvectors and eigenvalues back to the original communicator group
459!> \param matrix the input cp_fm_type matrix to be diagonalized
460!> \param eigenvectors the cp_fm_type matrix that will hold the eigenvectors of the input matrix
461!> \param eig global array holding the eigenvalues of the input matrixmatrix
462!> \param matrix_new the redistributed input matrix which will subsequently be diagonalized,
463!> or a pointer to the original matrix if no redistribution is required
464!> \param eigenvectors_new the redistributed eigenvectors matrix, or a pointer to the original
465!> matrix if no redistribution is required
466!> \par History
467!> - [01.2018] created by moving redistribution related code from cp_fm_syevd here
468!> \author Nico Holmberg [01.2018]
469! **************************************************************************************************
470 SUBROUTINE cp_fm_redistribute_end(matrix, eigenvectors, eig, matrix_new, eigenvectors_new)
471
472 TYPE(cp_fm_type), INTENT(IN) :: matrix, eigenvectors
473 REAL(kind=dp), DIMENSION(:), INTENT(INOUT) :: eig
474 TYPE(cp_fm_type), INTENT(INOUT) :: matrix_new, eigenvectors_new
475
476 CHARACTER(len=*), PARAMETER :: routinen = 'cp_fm_redistribute_end'
477
478 INTEGER :: handle
479#if defined(__parallel)
480 REAL(kind=dp) :: fake_local_data(1, 1)
481 INTEGER :: fake_descriptor(9), mepos_old, n
482 TYPE(mp_para_env_type), POINTER :: para_env
483#endif
484
485 CALL timeset(routinen, handle)
486
487#if defined(__parallel)
488
489 ! Check if matrix was redistributed
490 IF (ASSOCIATED(work_redistribute%group_distribution)) THEN
491 n = matrix%matrix_struct%nrow_global
492 para_env => matrix%matrix_struct%para_env
493 mepos_old = para_env%mepos
494
495 IF (work_redistribute%group_distribution(mepos_old) == 0) THEN
496 ! redistribute results on CPUs that hold the redistributed matrix
497 CALL pdgemr2d(n, n, eigenvectors_new%local_data(1, 1), 1, 1, eigenvectors_new%matrix_struct%descriptor, &
498 eigenvectors%local_data(1, 1), 1, 1, eigenvectors%matrix_struct%descriptor, &
499 eigenvectors%matrix_struct%context)
500 CALL cp_fm_release(matrix_new)
501 CALL cp_fm_release(eigenvectors_new)
502 ELSE
503 ! these tasks must help redistribute (they own part of the data),
504 ! but need fake 'new' data, and their descriptor must indicate this with -1
505 ! see also scalapack comments on pdgemr2d
506 fake_descriptor = -1
507 CALL pdgemr2d(n, n, fake_local_data(1, 1), 1, 1, fake_descriptor, &
508 eigenvectors%local_data(1, 1), 1, 1, eigenvectors%matrix_struct%descriptor, &
509 eigenvectors%matrix_struct%context)
510 END IF
511 ! free work
512 CALL cp_fm_redistribute_work_finalize(work_redistribute%group_distribution(mepos_old) == 0)
513
514 ! finally, also the eigenvalues need to end up on the non-group member tasks
515 CALL para_env%bcast(eig, 0)
516 END IF
517
518#else
519
520 mark_used(matrix)
521 mark_used(eigenvectors)
522 mark_used(eig)
523 mark_used(matrix_new)
524 mark_used(eigenvectors_new)
525 cpabort("Routine called in non-parallel case.")
526#endif
527
528 CALL timestop(handle)
529
530 END SUBROUTINE cp_fm_redistribute_end
531
532#if defined(__parallel)
533! **************************************************************************************************
534!> \brief Determines the largest number of CPUs a matrix can be distributed on without any of the
535!> processors getting a zero-width column (currently only needed for ELPA).
536!> \param matrix the matrix that will be diagonalized
537!> \return the maximum number of CPUs for ELPA
538! **************************************************************************************************
539 FUNCTION cp_cfm_max_ncpu_non_zero_column(matrix) RESULT(ncpu)
540 TYPE(cp_cfm_type), INTENT(IN) :: matrix
541 INTEGER :: ncpu
542
543 INTEGER :: gcd_max, ipe, jpe, ncol_block, &
544 ncol_global, npcol, nrow_block, &
545 nrow_global, num_pe_old, nzero
546 INTEGER, DIMENSION(:), POINTER :: ncol_locals
547 INTEGER, EXTERNAL :: numroc
548
549 NULLIFY (ncol_locals)
550 ! First check if there are any zero width columns in current layout
551 CALL cp_fm_struct_get(matrix%matrix_struct, ncol_locals=ncol_locals, &
552 nrow_global=nrow_global, ncol_global=ncol_global, &
553 nrow_block=nrow_block, ncol_block=ncol_block)
554 nzero = count(ncol_locals == 0)
555 num_pe_old = matrix%matrix_struct%para_env%num_pe
556 ncpu = num_pe_old - nzero
557
558 ! Avoid layouts with odd number of CPUs (blacs grid layout will be square)
559 IF (ncpu > 2) THEN
560 ncpu = ncpu - modulo(ncpu, 2)
561 END IF
562
563 ! if there are no zero-width columns and the number of processors was even, leave it at that
564 IF (ncpu == num_pe_old) THEN
565 RETURN
566 END IF
567
568 ! Iteratively search for the maximum number of CPUs for ELPA
569 ! On each step, we test whether the blacs grid created with ncpu processes
570 ! contains any columns with zero width
571 DO WHILE (ncpu > 1)
572 ! Determine layout of new blacs grid with ncpu CPUs
573 ! (snippet copied from cp_blacs_env.F:cp_blacs_env_create)
574 gcd_max = -1
575 DO ipe = 1, ceiling(sqrt(real(ncpu, dp)))
576 jpe = ncpu/ipe
577 IF (ipe*jpe /= ncpu) THEN
578 cycle
579 END IF
580 IF (gcd(ipe, jpe) >= gcd_max) THEN
581 npcol = jpe
582 gcd_max = gcd(ipe, jpe)
583 END IF
584 END DO
585
586 ! Count the number of processors without any columns
587 ! (snippet copied from cp_fm_struct.F:cp_fm_struct_create)
588 nzero = 0
589 DO ipe = 0, npcol - 1
590 IF (numroc(ncol_global, ncol_block, ipe, 0, npcol) == 0) THEN
591 nzero = nzero + 1
592 END IF
593 END DO
594
595 IF (nzero == 0) THEN
596 EXIT
597 END IF
598
599 ncpu = ncpu - nzero
600
601 IF (ncpu > 2) THEN
602 ncpu = ncpu - modulo(ncpu, 2)
603 END IF
604 END DO
605
606 END FUNCTION cp_cfm_max_ncpu_non_zero_column
607#endif
608
609! **************************************************************************************************
610!> \brief Determines the optimal number of CPUs for matrix diagonalization and redistributes
611!> the input matrices if necessary
612!> \param matrix the input cp_cfm_type matrix to be diagonalized
613!> \param eigenvectors the cp_cfm_type matrix that will hold the eigenvectors of the input matrix
614!> \param matrix_new the redistributed input matrix which will subsequently be diagonalized,
615!> or a pointer to the original matrix if no redistribution is required
616!> \param eigenvectors_new the redistributed eigenvectors matrix, or a pointer to the original
617!> matrix if no redistribution is required
618!> \param caller_is_elpa flag that determines if ELPA is used for diagonalization
619!> \param redist_info get info about the redistribution
620!> \par History
621!> - [08.2026] created by mirroring cp_fm_redistribute_start for complex matrices
622! **************************************************************************************************
623 SUBROUTINE cp_cfm_redistribute_start(matrix, eigenvectors, matrix_new, eigenvectors_new, &
624 caller_is_elpa, redist_info)
625
626 TYPE(cp_cfm_type), INTENT(IN) :: matrix, eigenvectors
627 TYPE(cp_cfm_type), INTENT(OUT) :: matrix_new, eigenvectors_new
628 LOGICAL, OPTIONAL, INTENT(IN) :: caller_is_elpa
629
630 CHARACTER(len=*), PARAMETER :: routinen = 'cp_cfm_redistribute_start'
631
632 INTEGER :: handle
633 LOGICAL :: is_elpa
634 TYPE(cp_fm_redistribute_info), OPTIONAL, INTENT(OUT) :: redist_info
635
636#if defined(__parallel)
637 COMPLEX(KIND=dp) :: fake_local_data(1, 1)
638 INTEGER :: fake_descriptor(9), mepos_old, &
639 io_unit, ngroups, ncol_block, blksize, nrow_block
640 TYPE(cp_fm_struct_type), POINTER :: fm_struct_new
641 TYPE(mp_para_env_type), POINTER :: para_env
642 TYPE(cp_logger_type), POINTER :: logger
643 TYPE(cp_fm_redistribute_info) :: rdinfo
644#endif
645
646 CALL timeset(routinen, handle)
647 is_elpa = .false.
648 IF (PRESENT(caller_is_elpa)) THEN
649#if defined(__ELPA)
650 is_elpa = caller_is_elpa
651#else
652 cpabort("CP2K compiled without the ELPA library.")
653#endif
654 END IF
655
656#if defined(__parallel)
657
658 logger => cp_get_default_logger()
659 io_unit = cp_logger_get_default_io_unit(logger)
660
661 ! first figure out the optimal number of cpus
662 ! this is pure heuristics, the defaults are based on rosa timings
663 ! that demonstrate that timings go up sharply if too many tasks are used
664 ! we take a multiple of 4, and approximately n/60
665 para_env => matrix%matrix_struct%para_env
666 mepos_old = para_env%mepos
667 ncol_block = -1 ! normally we also want to adjust the block size according to the optimal # of CPUs
668 nrow_block = -1
669 blksize = -1
670
671 rdinfo%matrix_order = matrix%matrix_struct%nrow_global
672 rdinfo%num_pe_old = para_env%num_pe
673 rdinfo%num_pe_opt = cp_fm_diag_get_optimal_ncpu(rdinfo%matrix_order)
674 rdinfo%num_pe_new = rdinfo%num_pe_opt
675 rdinfo%num_pe_max_nz_col = -1
676 rdinfo%redistribute = .false.
677
678 IF (is_elpa) THEN
679 ! with ELPA we don't have to redistribute if not necessary (scales, unlike ScaLAPACK)
680 rdinfo%num_pe_new = rdinfo%num_pe_old
681
682 ! BUT: Diagonalization with ELPA fails when a processor column has zero width
683 ! Determine the maximum number of CPUs the matrix can be distributed without zero-width columns
684 ! for the current block size.
685 rdinfo%num_pe_max_nz_col = cp_cfm_max_ncpu_non_zero_column(matrix)
686
687 ! if the user wants to redistribute to the ScaLAPACK optimal number of CPUs anyway, let him if it's safe.
688 IF (work_redistribute%elpa_force_redistribute .AND. rdinfo%num_pe_opt < rdinfo%num_pe_max_nz_col) THEN
689 ! Use heuristics to determine the need for redistribution (when num_pe_opt is smaller than the safe maximum)
690 ! in this case we can also take the block size used for ScaLAPACK
691 rdinfo%num_pe_new = rdinfo%num_pe_opt
692 ELSE IF (rdinfo%num_pe_old > rdinfo%num_pe_max_nz_col) THEN
693 ! Otherwise, only redistribute if we have to
694 rdinfo%num_pe_new = rdinfo%num_pe_max_nz_col
695 ! do NOT let cp_fm_struct_create automatically adjust the block size because the
696 ! calculated number of processors such that no block has 0 columns wouldn't match (see #578):
697 ! if the automatically chosen block size is larger than the present one we would still end
698 ! up with empty processors
699 END IF
700
701 CALL cp_cfm_get_info(matrix, ncol_block=ncol_block, nrow_block=nrow_block)
702
703 ! On GPUs, ELPA requires the block size to be a power of 2
704 blksize = 1
705 DO WHILE (2*blksize <= min(nrow_block, ncol_block))
706 blksize = blksize*2
707 END DO
708 nrow_block = blksize
709 ncol_block = blksize
710 END IF
711
712 ! finally, only redistribute if we're going to use less CPUs than before or changed the block size
713 rdinfo%redistribute = (rdinfo%num_pe_old > rdinfo%num_pe_new) .OR. (blksize >= 0 .AND. &
714 ((blksize /= matrix%matrix_struct%ncol_block) .OR. (blksize /= matrix%matrix_struct%nrow_block)))
715
716 IF (work_redistribute%should_print .AND. io_unit > 0) THEN
717 IF (is_elpa) THEN
718 IF (work_redistribute%elpa_force_redistribute) THEN
719 WRITE (unit=io_unit, fmt="(T2,A,T78,A3)") &
720 "CP_FM_DIAG| Force redistribute (ELPA):", "YES"
721 ELSE
722 WRITE (unit=io_unit, fmt="(T2,A,T79,A2)") &
723 "CP_FM_DIAG| Force redistribute (ELPA):", "NO"
724 END IF
725 END IF
726 CALL rdinfo%write(io_unit)
727 END IF
728 CALL para_env%sync()
729
730 ! if the optimal is smaller than num_pe, we will redistribute the input matrix
731 IF (rdinfo%redistribute) THEN
732 ! split comm, the first num_pe_new tasks will do the work
733 ALLOCATE (work_redistribute%group_distribution(0:rdinfo%num_pe_old - 1))
734 ALLOCATE (work_redistribute%group_partition(0:1))
735 work_redistribute%group_partition = [rdinfo%num_pe_new, rdinfo%num_pe_old - rdinfo%num_pe_new]
736 ALLOCATE (work_redistribute%para_env_new)
737 CALL work_redistribute%para_env_new%from_split( &
738 comm=para_env, ngroups=ngroups, group_distribution=work_redistribute%group_distribution, &
739 n_subgroups=2, group_partition=work_redistribute%group_partition)
740
741 IF (work_redistribute%group_distribution(mepos_old) == 0) THEN
742
743 ! create blacs, should inherit the preferences for the layout and so on, from the higher level
744 NULLIFY (work_redistribute%blacs_env_new)
745 CALL cp_blacs_env_create(blacs_env=work_redistribute%blacs_env_new, para_env=work_redistribute%para_env_new)
746
747 ! create new matrix
748 NULLIFY (fm_struct_new)
749 IF (nrow_block == -1 .OR. ncol_block == -1) THEN
750 CALL cp_fm_struct_create(fmstruct=fm_struct_new, &
751 para_env=work_redistribute%para_env_new, &
752 context=work_redistribute%blacs_env_new, &
753 nrow_global=rdinfo%matrix_order, ncol_global=rdinfo%matrix_order, &
754 ncol_block=ncol_block, nrow_block=nrow_block)
755 ELSE
756 CALL cp_fm_struct_create(fmstruct=fm_struct_new, &
757 para_env=work_redistribute%para_env_new, &
758 context=work_redistribute%blacs_env_new, &
759 nrow_global=rdinfo%matrix_order, ncol_global=rdinfo%matrix_order, &
760 ncol_block=ncol_block, nrow_block=nrow_block, force_block=.true.)
761 END IF
762 CALL cp_cfm_create(matrix_new, matrix_struct=fm_struct_new, name="zheevd_new_mat")
763 CALL cp_cfm_create(eigenvectors_new, matrix_struct=fm_struct_new, name="zheevd_new_vec")
764 CALL cp_fm_struct_release(fm_struct_new)
765
766 ! redistribute old
767 CALL pzgemr2d(rdinfo%matrix_order, rdinfo%matrix_order, matrix%local_data(1, 1), 1, 1, &
768 matrix%matrix_struct%descriptor, &
769 matrix_new%local_data(1, 1), 1, 1, matrix_new%matrix_struct%descriptor, &
770 matrix%matrix_struct%context)
771 ELSE
772 ! these tasks must help redistribute (they own part of the data),
773 ! but need fake 'new' data, and their descriptor must indicate this with -1
774 ! see also scalapack comments on pzgemr2d
775 fake_descriptor = -1
776 CALL pzgemr2d(rdinfo%matrix_order, rdinfo%matrix_order, matrix%local_data(1, 1), 1, 1, &
777 matrix%matrix_struct%descriptor, &
778 fake_local_data(1, 1), 1, 1, fake_descriptor, &
779 matrix%matrix_struct%context)
780 END IF
781 ELSE
782 ! No need to redistribute, just return pointers to the original arrays
783 matrix_new = matrix
784 eigenvectors_new = eigenvectors
785 END IF
786
787 IF (PRESENT(redist_info)) THEN
788 redist_info = rdinfo
789 END IF
790#else
791
792 mark_used(matrix)
793 mark_used(eigenvectors)
794 mark_used(matrix_new)
795 mark_used(eigenvectors_new)
796 mark_used(redist_info)
797 cpabort("Routine called in non-parallel case.")
798#endif
799
800 CALL timestop(handle)
801
802 END SUBROUTINE cp_cfm_redistribute_start
803
804! **************************************************************************************************
805!> \brief Redistributes eigenvectors and eigenvalues back to the original communicator group
806!> \param matrix the input cp_cfm_type matrix to be diagonalized
807!> \param eigenvectors the cp_cfm_type matrix that will hold the eigenvectors of the input matrix
808!> \param eig global array holding the eigenvalues of the input matrixmatrix
809!> \param matrix_new the redistributed input matrix which will subsequently be diagonalized,
810!> or a pointer to the original matrix if no redistribution is required
811!> \param eigenvectors_new the redistributed eigenvectors matrix, or a pointer to the original
812!> matrix if no redistribution is required
813!> \par History
814!> - [08.2026] created by mirroring cp_fm_redistribute_end for complex matrices
815! **************************************************************************************************
816 SUBROUTINE cp_cfm_redistribute_end(matrix, eigenvectors, eig, matrix_new, eigenvectors_new)
817
818 TYPE(cp_cfm_type), INTENT(IN) :: matrix, eigenvectors
819 REAL(kind=dp), DIMENSION(:), INTENT(INOUT) :: eig
820 TYPE(cp_cfm_type), INTENT(INOUT) :: matrix_new, eigenvectors_new
821
822 CHARACTER(len=*), PARAMETER :: routinen = 'cp_cfm_redistribute_end'
823
824 INTEGER :: handle
825#if defined(__parallel)
826 COMPLEX(KIND=dp) :: fake_local_data(1, 1)
827 INTEGER :: fake_descriptor(9), mepos_old, n
828 TYPE(mp_para_env_type), POINTER :: para_env
829#endif
830
831 CALL timeset(routinen, handle)
832
833#if defined(__parallel)
834
835 ! Check if matrix was redistributed
836 IF (ASSOCIATED(work_redistribute%group_distribution)) THEN
837 n = matrix%matrix_struct%nrow_global
838 para_env => matrix%matrix_struct%para_env
839 mepos_old = para_env%mepos
840
841 IF (work_redistribute%group_distribution(mepos_old) == 0) THEN
842 ! redistribute results on CPUs that hold the redistributed matrix
843 CALL pzgemr2d(n, n, eigenvectors_new%local_data(1, 1), 1, 1, eigenvectors_new%matrix_struct%descriptor, &
844 eigenvectors%local_data(1, 1), 1, 1, eigenvectors%matrix_struct%descriptor, &
845 eigenvectors%matrix_struct%context)
846 CALL cp_cfm_release(matrix_new)
847 CALL cp_cfm_release(eigenvectors_new)
848 ELSE
849 ! these tasks must help redistribute (they own part of the data),
850 ! but need fake 'new' data, and their descriptor must indicate this with -1
851 ! see also scalapack comments on pzgemr2d
852 fake_descriptor = -1
853 CALL pzgemr2d(n, n, fake_local_data(1, 1), 1, 1, fake_descriptor, &
854 eigenvectors%local_data(1, 1), 1, 1, eigenvectors%matrix_struct%descriptor, &
855 eigenvectors%matrix_struct%context)
856 END IF
857 ! free work
858 CALL cp_fm_redistribute_work_finalize(work_redistribute%group_distribution(mepos_old) == 0)
859
860 ! finally, also the eigenvalues need to end up on the non-group member tasks
861 CALL para_env%bcast(eig, 0)
862 END IF
863
864#else
865
866 mark_used(matrix)
867 mark_used(eigenvectors)
868 mark_used(eig)
869 mark_used(matrix_new)
870 mark_used(eigenvectors_new)
871 cpabort("Routine called in non-parallel case.")
872#endif
873
874 CALL timestop(handle)
875
876 END SUBROUTINE cp_cfm_redistribute_end
877
878END MODULE cp_fm_diag_utils
static GRID_HOST_DEVICE int modulo(int a, int m)
Equivalent of Fortran's MODULO, which always return a positive number. https://gcc....
methods related to the blacs parallel environment
subroutine, public cp_blacs_env_release(blacs_env)
releases the given blacs_env
subroutine, public cp_blacs_env_create(blacs_env, para_env, blacs_grid_layout, blacs_repeatable, row_major, grid_2d)
allocates and initializes a type that represent a blacs context
Represents a complex full matrix distributed on many processors.
subroutine, public cp_cfm_release(matrix)
Releases a full matrix.
subroutine, public cp_cfm_create(matrix, matrix_struct, name, nrow, ncol, set_zero)
Creates a new full matrix with the given structure.
subroutine, public cp_cfm_get_info(matrix, name, nrow_global, ncol_global, nrow_block, ncol_block, nrow_local, ncol_local, row_indices, col_indices, local_data, context, matrix_struct, para_env)
Returns information about a full matrix.
Auxiliary tools to redistribute cp_fm_type and cp_cfm_type matrices before and after diagonalization....
subroutine, public cp_fm_redistribute_end(matrix, eigenvectors, eig, matrix_new, eigenvectors_new)
Redistributes eigenvectors and eigenvalues back to the original communicator group.
subroutine, public cp_cfm_redistribute_start(matrix, eigenvectors, matrix_new, eigenvectors_new, caller_is_elpa, redist_info)
Determines the optimal number of CPUs for matrix diagonalization and redistributes the input matrices...
subroutine, public cp_cfm_redistribute_end(matrix, eigenvectors, eig, matrix_new, eigenvectors_new)
Redistributes eigenvectors and eigenvalues back to the original communicator group.
subroutine, public cp_fm_redistribute_start(matrix, eigenvectors, matrix_new, eigenvectors_new, caller_is_elpa, redist_info)
Determines the optimal number of CPUs for matrix diagonalization and redistributes the input matrices...
subroutine, public cp_fm_redistribute_init(a, x, should_print, elpa_force_redistribute)
Initializes the parameters that determine how to calculate the optimal number of CPUs for diagonalizi...
represent the structure of a full matrix
subroutine, public cp_fm_struct_create(fmstruct, para_env, context, nrow_global, ncol_global, nrow_block, ncol_block, descriptor, first_p_pos, local_leading_dimension, template_fmstruct, square_blocks, force_block)
allocates and initializes a full matrix structure
subroutine, public cp_fm_struct_get(fmstruct, para_env, context, descriptor, ncol_block, nrow_block, nrow_global, ncol_global, first_p_pos, row_indices, col_indices, nrow_local, ncol_local, nrow_locals, ncol_locals, local_leading_dimension)
returns the values of various attributes of the matrix structure
subroutine, public cp_fm_struct_release(fmstruct)
releases a full matrix structure
represent a full matrix distributed on many processors
Definition cp_fm_types.F:15
subroutine, public cp_fm_get_info(matrix, name, nrow_global, ncol_global, nrow_block, ncol_block, nrow_local, ncol_local, row_indices, col_indices, local_data, context, nrow_locals, ncol_locals, matrix_struct, para_env)
returns all kind of information about the full matrix
subroutine, public cp_fm_create(matrix, matrix_struct, name, nrow, ncol, set_zero)
creates a new full matrix with the given structure
various routines to log and control the output. The idea is that decisions about where to log should ...
integer function, public cp_logger_get_default_io_unit(logger)
returns the unit nr for the ionode (-1 on all other processors) skips as well checks if the procs cal...
type(cp_logger_type) function, pointer, public cp_get_default_logger()
returns the default logger
Defines the basic variable types.
Definition kinds.F:23
integer, parameter, public dp
Definition kinds.F:34
Collection of simple mathematical functions and subroutines.
Definition mathlib.F:15
elemental integer function, public gcd(a, b)
computes the greatest common divisor of two number
Definition mathlib.F:1289
Interface to the message passing library MPI.
represent a blacs multidimensional parallel environment (for the mpi corrispective see cp_paratypes/m...
Represent a complex full matrix.
keeps the information about the structure of a full matrix
represent a full matrix
type of a logger, at the moment it contains just a print level starting at which level it should be l...
stores all the informations relevant to an mpi environment