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 Distribution methods for atoms, particles, or molecules
10 : !> \par History
11 : !> - 1d-distribution of molecules and particles (Sep. 2003, MK)
12 : !> - 2d-distribution for Quickstep updated with molecules (Oct. 2003, MK)
13 : !> \author MK (22.08.2003)
14 : ! **************************************************************************************************
15 : MODULE distribution_methods
16 : USE atomic_kind_types, ONLY: atomic_kind_type,&
17 : get_atomic_kind,&
18 : get_atomic_kind_set
19 : USE basis_set_types, ONLY: get_gto_basis_set,&
20 : gto_basis_set_type
21 : USE cell_types, ONLY: cell_type,&
22 : pbc,&
23 : real_to_scaled,&
24 : scaled_to_real
25 : USE cp_array_utils, ONLY: cp_1d_i_p_type
26 : USE cp_blacs_env, ONLY: cp_blacs_env_type
27 : USE cp_dbcsr_api, ONLY: dbcsr_distribution_get_num_images
28 : USE cp_log_handling, ONLY: cp_get_default_logger,&
29 : cp_logger_get_default_io_unit,&
30 : cp_logger_get_default_unit_nr,&
31 : cp_logger_type
32 : USE cp_min_heap, ONLY: cp_heap_fill,&
33 : cp_heap_get_first,&
34 : cp_heap_new,&
35 : cp_heap_release,&
36 : cp_heap_reset_first,&
37 : cp_heap_type
38 : USE cp_output_handling, ONLY: cp_p_file,&
39 : cp_print_key_finished_output,&
40 : cp_print_key_should_output,&
41 : cp_print_key_unit_nr
42 : USE distribution_1d_types, ONLY: distribution_1d_create,&
43 : distribution_1d_type
44 : USE distribution_2d_types, ONLY: distribution_2d_create,&
45 : distribution_2d_type,&
46 : distribution_2d_write
47 : USE input_constants, ONLY: model_block_count,&
48 : model_block_lmax
49 : USE input_section_types, ONLY: section_vals_get_subs_vals,&
50 : section_vals_type,&
51 : section_vals_val_get
52 : USE kinds, ONLY: dp,&
53 : int_8
54 : USE machine, ONLY: m_flush
55 : USE mathconstants, ONLY: pi
56 : USE mathlib, ONLY: gcd,&
57 : lcm
58 : USE molecule_kind_types, ONLY: get_molecule_kind,&
59 : get_molecule_kind_set,&
60 : molecule_kind_type
61 : USE molecule_types, ONLY: molecule_type
62 : USE parallel_rng_types, ONLY: UNIFORM,&
63 : rng_stream_type
64 : USE particle_types, ONLY: particle_type
65 : USE qs_kind_types, ONLY: get_qs_kind,&
66 : qs_kind_type
67 : USE util, ONLY: sort
68 : #include "./base/base_uses.f90"
69 :
70 : IMPLICIT NONE
71 :
72 : PRIVATE
73 :
74 : ! *** Global parameters (in this module) ***
75 :
76 : CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'distribution_methods'
77 :
78 : ! *** Public subroutines ***
79 :
80 : PUBLIC :: distribute_molecules_1d, &
81 : distribute_molecules_2d
82 :
83 : CONTAINS
84 :
85 : ! **************************************************************************************************
86 : !> \brief Distribute molecules and particles
87 : !> \param atomic_kind_set particle (atomic) kind information
88 : !> \param particle_set particle information
89 : !> \param local_particles distribution of particles created by this routine
90 : !> \param molecule_kind_set molecule kind information
91 : !> \param molecule_set molecule information
92 : !> \param local_molecules distribution of molecules created by this routine
93 : !> \param force_env_section ...
94 : !> \param prev_molecule_kind_set previous molecule kind information, used with
95 : !> prev_local_molecules
96 : !> \param prev_local_molecules previous distribution of molecules, new one will
97 : !> be identical if all the prev_* arguments are present and associated
98 : !> \par History
99 : !> none
100 : !> \author MK (Jun. 2003)
101 : ! **************************************************************************************************
102 12551 : SUBROUTINE distribute_molecules_1d(atomic_kind_set, particle_set, &
103 : local_particles, &
104 : molecule_kind_set, molecule_set, &
105 : local_molecules, force_env_section, &
106 : prev_molecule_kind_set, &
107 : prev_local_molecules)
108 :
109 : TYPE(atomic_kind_type), DIMENSION(:), POINTER :: atomic_kind_set
110 : TYPE(particle_type), DIMENSION(:), POINTER :: particle_set
111 : TYPE(distribution_1d_type), POINTER :: local_particles
112 : TYPE(molecule_kind_type), DIMENSION(:), POINTER :: molecule_kind_set
113 : TYPE(molecule_type), DIMENSION(:), POINTER :: molecule_set
114 : TYPE(distribution_1d_type), POINTER :: local_molecules
115 : TYPE(section_vals_type), POINTER :: force_env_section
116 : TYPE(molecule_kind_type), DIMENSION(:), OPTIONAL, &
117 : POINTER :: prev_molecule_kind_set
118 : TYPE(distribution_1d_type), OPTIONAL, POINTER :: prev_local_molecules
119 :
120 : CHARACTER(len=*), PARAMETER :: routineN = 'distribute_molecules_1d'
121 :
122 : INTEGER :: atom_a, bin, handle, iatom, imolecule, imolecule_kind, imolecule_local, &
123 : imolecule_prev_kind, iparticle_kind, ipe, iw, kind_a, molecule_a, n, natom, nbins, nload, &
124 : nmolecule, nmolecule_kind, nparticle_kind, nsgf, output_unit
125 : INTEGER(int_8) :: bin_price
126 : INTEGER(int_8), ALLOCATABLE, DIMENSION(:) :: workload_count, workload_fill
127 12551 : INTEGER, ALLOCATABLE, DIMENSION(:) :: nmolecule_local, nparticle_local, work
128 12551 : INTEGER, DIMENSION(:), POINTER :: molecule_list
129 : LOGICAL :: found, has_prev_subsys_info, is_local
130 12551 : TYPE(cp_1d_i_p_type), ALLOCATABLE, DIMENSION(:) :: local_molecule
131 : TYPE(cp_heap_type) :: bin_heap_count, bin_heap_fill
132 : TYPE(cp_logger_type), POINTER :: logger
133 : TYPE(molecule_kind_type), POINTER :: molecule_kind
134 :
135 12551 : CALL timeset(routineN, handle)
136 :
137 12551 : has_prev_subsys_info = .FALSE.
138 12551 : IF (PRESENT(prev_local_molecules) .AND. &
139 : PRESENT(prev_molecule_kind_set)) THEN
140 2637 : IF (ASSOCIATED(prev_local_molecules) .AND. &
141 : ASSOCIATED(prev_molecule_kind_set)) THEN
142 44 : has_prev_subsys_info = .TRUE.
143 : END IF
144 : END IF
145 :
146 12551 : logger => cp_get_default_logger()
147 :
148 : ASSOCIATE (group => logger%para_env, mype => logger%para_env%mepos + 1, &
149 : npe => logger%para_env%num_pe)
150 :
151 37653 : ALLOCATE (workload_count(npe))
152 12551 : workload_count(:) = 0
153 :
154 25102 : ALLOCATE (workload_fill(npe))
155 12551 : workload_fill(:) = 0
156 :
157 12551 : nmolecule_kind = SIZE(molecule_kind_set)
158 :
159 37653 : ALLOCATE (nmolecule_local(nmolecule_kind))
160 12551 : nmolecule_local(:) = 0
161 :
162 200135 : ALLOCATE (local_molecule(nmolecule_kind))
163 :
164 12551 : nparticle_kind = SIZE(atomic_kind_set)
165 :
166 37653 : ALLOCATE (nparticle_local(nparticle_kind))
167 12551 : nparticle_local(:) = 0
168 :
169 12551 : nbins = npe
170 :
171 12551 : CALL cp_heap_new(bin_heap_count, nbins)
172 12551 : CALL cp_heap_fill(bin_heap_count, workload_count)
173 :
174 12551 : CALL cp_heap_new(bin_heap_fill, nbins)
175 12551 : CALL cp_heap_fill(bin_heap_fill, workload_fill)
176 :
177 162482 : DO imolecule_kind = 1, nmolecule_kind
178 :
179 149931 : molecule_kind => molecule_kind_set(imolecule_kind)
180 :
181 149931 : NULLIFY (molecule_list)
182 :
183 : ! *** Get the number of molecules and the number of ***
184 : ! *** atoms in each molecule of that molecular kind ***
185 :
186 : CALL get_molecule_kind(molecule_kind=molecule_kind, &
187 : molecule_list=molecule_list, &
188 : natom=natom, &
189 149931 : nsgf=nsgf)
190 :
191 : ! *** Consider the number of atoms or basis ***
192 : ! *** functions which depends on the method ***
193 :
194 149931 : nload = MAX(natom, nsgf)
195 149931 : nmolecule = SIZE(molecule_list)
196 :
197 : ! *** Get the number of local molecules of the current molecule kind ***
198 :
199 468705 : DO imolecule = 1, nmolecule
200 468705 : IF (has_prev_subsys_info) THEN
201 297660 : DO imolecule_prev_kind = 1, SIZE(prev_molecule_kind_set)
202 3188004 : IF (ANY(prev_local_molecules%list(imolecule_prev_kind)%array( &
203 5060 : 1:prev_local_molecules%n_el(imolecule_prev_kind)) == molecule_list(imolecule))) THEN
204 : ! molecule used to be local
205 2530 : nmolecule_local(imolecule_kind) = nmolecule_local(imolecule_kind) + 1
206 : END IF
207 : END DO
208 : ELSE
209 313714 : CALL cp_heap_get_first(bin_heap_count, bin, bin_price, found)
210 313714 : IF (.NOT. found) THEN
211 0 : CPABORT("No topmost heap element found.")
212 : END IF
213 :
214 313714 : ipe = bin
215 313714 : IF (bin_price /= workload_count(ipe)) THEN
216 0 : CPABORT("inconsistent heap")
217 : END IF
218 :
219 313714 : workload_count(ipe) = workload_count(ipe) + nload
220 313714 : IF (ipe == mype) THEN
221 172041 : nmolecule_local(imolecule_kind) = nmolecule_local(imolecule_kind) + 1
222 : END IF
223 :
224 313714 : bin_price = workload_count(ipe)
225 313714 : CALL cp_heap_reset_first(bin_heap_count, bin_price)
226 : END IF
227 : END DO
228 :
229 : ! *** Distribute the molecules ***
230 149931 : n = nmolecule_local(imolecule_kind)
231 :
232 149931 : IF (n > 0) THEN
233 240306 : ALLOCATE (local_molecule(imolecule_kind)%array(n))
234 : ELSE
235 69829 : NULLIFY (local_molecule(imolecule_kind)%array)
236 : END IF
237 :
238 : imolecule_local = 0
239 631187 : DO imolecule = 1, nmolecule
240 318774 : is_local = .FALSE.
241 318774 : IF (has_prev_subsys_info) THEN
242 297660 : DO imolecule_prev_kind = 1, SIZE(prev_molecule_kind_set)
243 3188004 : IF (ANY(prev_local_molecules%list(imolecule_prev_kind)%array( &
244 5060 : 1:prev_local_molecules%n_el(imolecule_prev_kind)) == molecule_list(imolecule))) THEN
245 2530 : is_local = .TRUE.
246 : END IF
247 : END DO
248 : ELSE
249 313714 : CALL cp_heap_get_first(bin_heap_fill, bin, bin_price, found)
250 313714 : IF (.NOT. found) THEN
251 0 : CPABORT("No topmost heap element found.")
252 : END IF
253 :
254 313714 : ipe = bin
255 313714 : IF (bin_price /= workload_fill(ipe)) THEN
256 0 : CPABORT("inconsistent heap")
257 : END IF
258 :
259 313714 : workload_fill(ipe) = workload_fill(ipe) + nload
260 313714 : is_local = (ipe == mype)
261 : END IF
262 318774 : IF (is_local) THEN
263 174571 : imolecule_local = imolecule_local + 1
264 174571 : molecule_a = molecule_list(imolecule)
265 174571 : local_molecule(imolecule_kind)%array(imolecule_local) = molecule_a
266 595747 : DO iatom = 1, natom
267 421176 : atom_a = molecule_set(molecule_a)%first_atom + iatom - 1
268 :
269 : CALL get_atomic_kind(atomic_kind=particle_set(atom_a)%atomic_kind, &
270 421176 : kind_number=kind_a)
271 595747 : nparticle_local(kind_a) = nparticle_local(kind_a) + 1
272 : END DO
273 : END IF
274 468705 : IF (.NOT. has_prev_subsys_info) THEN
275 313714 : bin_price = workload_fill(ipe)
276 313714 : CALL cp_heap_reset_first(bin_heap_fill, bin_price)
277 : END IF
278 : END DO
279 :
280 : END DO
281 :
282 36594 : IF (ANY(workload_fill /= workload_count)) THEN
283 0 : CPABORT("Inconsistent heaps encountered")
284 : END IF
285 :
286 12551 : CALL cp_heap_release(bin_heap_count)
287 12551 : CALL cp_heap_release(bin_heap_fill)
288 :
289 : ! *** Create the local molecule structure ***
290 :
291 : CALL distribution_1d_create(local_molecules, &
292 : n_el=nmolecule_local, &
293 12551 : para_env=logger%para_env)
294 :
295 : ! *** Create the local particle structure ***
296 :
297 : CALL distribution_1d_create(local_particles, &
298 : n_el=nparticle_local, &
299 12551 : para_env=logger%para_env)
300 :
301 : ! *** Store the generated local molecule and particle distributions ***
302 :
303 12551 : nparticle_local(:) = 0
304 :
305 162482 : DO imolecule_kind = 1, nmolecule_kind
306 :
307 149931 : IF (nmolecule_local(imolecule_kind) == 0) CYCLE
308 :
309 : local_molecules%list(imolecule_kind)%array(:) = &
310 254673 : local_molecule(imolecule_kind)%array(:)
311 :
312 80102 : molecule_kind => molecule_kind_set(imolecule_kind)
313 :
314 : CALL get_molecule_kind(molecule_kind=molecule_kind, &
315 80102 : natom=natom)
316 :
317 267224 : DO imolecule = 1, nmolecule_local(imolecule_kind)
318 174571 : molecule_a = local_molecule(imolecule_kind)%array(imolecule)
319 745678 : DO iatom = 1, natom
320 421176 : atom_a = molecule_set(molecule_a)%first_atom + iatom - 1
321 : CALL get_atomic_kind(atomic_kind=particle_set(atom_a)%atomic_kind, &
322 421176 : kind_number=kind_a)
323 421176 : nparticle_local(kind_a) = nparticle_local(kind_a) + 1
324 595747 : local_particles%list(kind_a)%array(nparticle_local(kind_a)) = atom_a
325 : END DO
326 : END DO
327 :
328 : END DO
329 :
330 : ! *** Print distribution, if requested ***
331 :
332 12551 : IF (BTEST(cp_print_key_should_output(logger%iter_info, &
333 37653 : force_env_section, "PRINT%DISTRIBUTION1D"), cp_p_file)) THEN
334 :
335 : output_unit = cp_print_key_unit_nr(logger, force_env_section, "PRINT%DISTRIBUTION1D", &
336 158 : extension=".Log")
337 :
338 158 : iw = output_unit
339 158 : IF (output_unit < 0) iw = cp_logger_get_default_unit_nr(logger, LOCAL=.TRUE.)
340 :
341 : ! *** Print molecule distribution ***
342 :
343 474 : ALLOCATE (work(npe))
344 158 : work(:) = 0
345 :
346 586 : work(mype) = SUM(nmolecule_local)
347 158 : CALL group%sum(work)
348 :
349 158 : IF (output_unit > 0) THEN
350 : WRITE (UNIT=output_unit, &
351 : FMT="(/, T2, A, T51, A, /, (T52, I6, T73, I8))") &
352 79 : "DISTRIBUTION OF THE MOLECULES", &
353 79 : "Process Number of molecules", &
354 316 : (ipe - 1, work(ipe), ipe=1, npe)
355 : WRITE (UNIT=output_unit, FMT="(T55, A3, T73, I8)") &
356 237 : "Sum", SUM(work)
357 79 : CALL m_flush(output_unit)
358 : END IF
359 :
360 158 : CALL group%sync()
361 :
362 474 : DO ipe = 1, npe
363 316 : IF (ipe == mype) THEN
364 : WRITE (UNIT=iw, FMT="(/, T3, A)") &
365 158 : "Process Kind Local molecules (global indices)"
366 586 : DO imolecule_kind = 1, nmolecule_kind
367 586 : IF (imolecule_kind == 1) THEN
368 : WRITE (UNIT=iw, FMT="(T4, I6, 2X, I5, (T21, 10I6))") &
369 158 : ipe - 1, imolecule_kind, &
370 372 : (local_molecules%list(imolecule_kind)%array(imolecule), &
371 688 : imolecule=1, nmolecule_local(imolecule_kind))
372 : ELSE
373 : WRITE (UNIT=iw, FMT="(T12, I5, (T21, 10I6))") &
374 270 : imolecule_kind, &
375 405 : (local_molecules%list(imolecule_kind)%array(imolecule), &
376 945 : imolecule=1, nmolecule_local(imolecule_kind))
377 : END IF
378 : END DO
379 : END IF
380 316 : CALL m_flush(iw)
381 474 : CALL group%sync()
382 : END DO
383 :
384 : ! *** Print particle distribution ***
385 :
386 158 : work(:) = 0
387 :
388 530 : work(mype) = SUM(nparticle_local)
389 158 : CALL group%sum(work)
390 :
391 158 : IF (output_unit > 0) THEN
392 : WRITE (UNIT=output_unit, &
393 : FMT="(/, T2, A, T51, A, /, (T52, I6, T73, I8))") &
394 79 : "DISTRIBUTION OF THE PARTICLES", &
395 79 : "Process Number of particles", &
396 316 : (ipe - 1, work(ipe), ipe=1, npe)
397 : WRITE (UNIT=output_unit, FMT="(T55, A3, T73, I8)") &
398 237 : "Sum", SUM(work)
399 79 : CALL m_flush(output_unit)
400 : END IF
401 :
402 158 : CALL group%sync()
403 :
404 474 : DO ipe = 1, npe
405 316 : IF (ipe == mype) THEN
406 : WRITE (UNIT=iw, FMT="(/, T3, A)") &
407 158 : "Process Kind Local particles (global indices)"
408 530 : DO iparticle_kind = 1, nparticle_kind
409 530 : IF (iparticle_kind == 1) THEN
410 : WRITE (UNIT=iw, FMT="(T4, I6, 2X, I5, (T20, 10I6))") &
411 158 : ipe - 1, iparticle_kind, &
412 658 : (local_particles%list(iparticle_kind)%array(iatom), &
413 974 : iatom=1, nparticle_local(iparticle_kind))
414 : ELSE
415 : WRITE (UNIT=iw, FMT="(T12, I5, (T20, 10I6))") &
416 214 : iparticle_kind, &
417 1149 : (local_particles%list(iparticle_kind)%array(iatom), &
418 1577 : iatom=1, nparticle_local(iparticle_kind))
419 : END IF
420 : END DO
421 : END IF
422 316 : CALL m_flush(iw)
423 474 : CALL group%sync()
424 : END DO
425 158 : DEALLOCATE (work)
426 :
427 : CALL cp_print_key_finished_output(output_unit, logger, force_env_section, &
428 158 : "PRINT%DISTRIBUTION1D")
429 : END IF
430 : END ASSOCIATE
431 : ! *** Release work storage ***
432 :
433 12551 : DEALLOCATE (workload_count)
434 :
435 12551 : DEALLOCATE (workload_fill)
436 :
437 12551 : DEALLOCATE (nmolecule_local)
438 :
439 12551 : DEALLOCATE (nparticle_local)
440 :
441 162482 : DO imolecule_kind = 1, nmolecule_kind
442 162482 : IF (ASSOCIATED(local_molecule(imolecule_kind)%array)) THEN
443 80102 : DEALLOCATE (local_molecule(imolecule_kind)%array)
444 : END IF
445 : END DO
446 12551 : DEALLOCATE (local_molecule)
447 :
448 12551 : CALL timestop(handle)
449 :
450 25102 : END SUBROUTINE distribute_molecules_1d
451 :
452 : ! **************************************************************************************************
453 : !> \brief Distributes the particle pairs creating a 2d distribution optimally
454 : !> suited for quickstep
455 : !> \param cell ...
456 : !> \param atomic_kind_set ...
457 : !> \param particle_set ...
458 : !> \param qs_kind_set ...
459 : !> \param molecule_kind_set ...
460 : !> \param molecule_set ...
461 : !> \param distribution_2d the distribution that will be created by this
462 : !> method
463 : !> \param blacs_env the parallel environment at the basis of the
464 : !> distribution
465 : !> \param force_env_section ...
466 : !> \par History
467 : !> - local_rows & cols blocksize optimizations (Aug. 2003, MK)
468 : !> - cleanup of distribution_2d (Sep. 2003, fawzi)
469 : !> - update for molecules (Oct. 2003, MK)
470 : !> \author fawzi (Feb. 2003)
471 : !> \note
472 : !> Intermediate generation of a 2d distribution of the molecules, but
473 : !> only the corresponding particle (atomic) distribution is currently
474 : !> used. The 2d distribution of the molecules is deleted, but may easily
475 : !> be recovered (MK).
476 : ! **************************************************************************************************
477 9724 : SUBROUTINE distribute_molecules_2d(cell, atomic_kind_set, particle_set, &
478 : qs_kind_set, molecule_kind_set, molecule_set, &
479 : distribution_2d, blacs_env, force_env_section)
480 : TYPE(cell_type), POINTER :: cell
481 : TYPE(atomic_kind_type), DIMENSION(:), POINTER :: atomic_kind_set
482 : TYPE(particle_type), DIMENSION(:), POINTER :: particle_set
483 : TYPE(qs_kind_type), DIMENSION(:), POINTER :: qs_kind_set
484 : TYPE(molecule_kind_type), DIMENSION(:), POINTER :: molecule_kind_set
485 : TYPE(molecule_type), DIMENSION(:), POINTER :: molecule_set
486 : TYPE(distribution_2d_type), POINTER :: distribution_2d
487 : TYPE(cp_blacs_env_type), POINTER :: blacs_env
488 : TYPE(section_vals_type), POINTER :: force_env_section
489 :
490 : CHARACTER(len=*), PARAMETER :: routineN = 'distribute_molecules_2d'
491 :
492 : INTEGER :: cluster_price, cost_model, handle, iatom, iatom_mol, iatom_one, ikind, imol, &
493 : imolecule, imolecule_kind, iparticle_kind, ipcol, iprow, iw, kind_a, n, natom, natom_mol, &
494 : nclusters, nmolecule, nmolecule_kind, nparticle_kind, nsgf, output_unit
495 9724 : INTEGER, ALLOCATABLE, DIMENSION(:) :: cluster_list, cluster_prices, &
496 9724 : nparticle_local_col, &
497 9724 : nparticle_local_row, work
498 9724 : INTEGER, DIMENSION(:), POINTER :: lmax_basis, molecule_list
499 9724 : INTEGER, DIMENSION(:, :), POINTER :: cluster_col_distribution, &
500 9724 : cluster_row_distribution, &
501 9724 : col_distribution, row_distribution
502 : LOGICAL :: basic_cluster_optimization, basic_optimization, basic_spatial_optimization, &
503 : molecular_distribution, skip_optimization
504 9724 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :) :: coords, pbc_scaled_coords
505 : REAL(KIND=dp), DIMENSION(3) :: center
506 9724 : TYPE(cp_1d_i_p_type), DIMENSION(:), POINTER :: local_particle_col, local_particle_row
507 : TYPE(cp_logger_type), POINTER :: logger
508 : TYPE(gto_basis_set_type), POINTER :: orb_basis_set
509 : TYPE(molecule_kind_type), POINTER :: molecule_kind
510 : TYPE(section_vals_type), POINTER :: distribution_section
511 :
512 : !...
513 :
514 9724 : CALL timeset(routineN, handle)
515 :
516 9724 : logger => cp_get_default_logger()
517 :
518 9724 : distribution_section => section_vals_get_subs_vals(force_env_section, "DFT%QS%DISTRIBUTION")
519 :
520 9724 : CALL section_vals_val_get(distribution_section, "2D_MOLECULAR_DISTRIBUTION", l_val=molecular_distribution)
521 9724 : CALL section_vals_val_get(distribution_section, "SKIP_OPTIMIZATION", l_val=skip_optimization)
522 9724 : CALL section_vals_val_get(distribution_section, "BASIC_OPTIMIZATION", l_val=basic_optimization)
523 9724 : CALL section_vals_val_get(distribution_section, "BASIC_SPATIAL_OPTIMIZATION", l_val=basic_spatial_optimization)
524 9724 : CALL section_vals_val_get(distribution_section, "BASIC_CLUSTER_OPTIMIZATION", l_val=basic_cluster_optimization)
525 :
526 9724 : CALL section_vals_val_get(distribution_section, "COST_MODEL", i_val=cost_model)
527 : !
528 :
529 : ASSOCIATE (group => blacs_env%para_env, myprow => blacs_env%mepos(1) + 1, mypcol => blacs_env%mepos(2) + 1, &
530 : nprow => blacs_env%num_pe(1), npcol => blacs_env%num_pe(2))
531 :
532 9724 : nmolecule_kind = SIZE(molecule_kind_set)
533 9724 : CALL get_molecule_kind_set(molecule_kind_set, nmolecule=nmolecule)
534 :
535 9724 : nparticle_kind = SIZE(atomic_kind_set)
536 9724 : CALL get_atomic_kind_set(atomic_kind_set=atomic_kind_set, natom=natom)
537 :
538 : !
539 : ! we need to generate two representations of the distribution, one as a straight array with global particles
540 : ! one ordered wrt to kinds and only listing the local particles
541 : !
542 29172 : ALLOCATE (row_distribution(natom, 2))
543 19448 : ALLOCATE (col_distribution(natom, 2))
544 : ! Initialize the distributions to -1, as the second dimension only gets set with cluster optimization
545 : ! but the information is needed by dbcsr
546 270008 : row_distribution = -1; col_distribution = -1
547 :
548 47433 : ALLOCATE (local_particle_col(nparticle_kind))
549 37709 : ALLOCATE (local_particle_row(nparticle_kind))
550 29172 : ALLOCATE (nparticle_local_row(nparticle_kind))
551 19448 : ALLOCATE (nparticle_local_col(nparticle_kind))
552 :
553 9724 : IF (basic_optimization .OR. basic_spatial_optimization .OR. basic_cluster_optimization) THEN
554 :
555 9724 : IF (molecular_distribution) THEN
556 2 : nclusters = nmolecule
557 : ELSE
558 : nclusters = natom
559 : END IF
560 :
561 29172 : ALLOCATE (cluster_list(nclusters))
562 19448 : ALLOCATE (cluster_prices(nclusters))
563 29172 : ALLOCATE (cluster_row_distribution(nclusters, 2))
564 19448 : ALLOCATE (cluster_col_distribution(nclusters, 2))
565 279700 : cluster_row_distribution = -1; cluster_col_distribution = -1
566 :
567 : ! Fill in the clusters and their prices
568 9724 : CALL section_vals_val_get(distribution_section, "COST_MODEL", i_val=cost_model)
569 9724 : IF (.NOT. molecular_distribution) THEN
570 65057 : DO iatom = 1, natom
571 55335 : IF (iatom > nclusters) THEN
572 0 : CPABORT("Bounds error")
573 : END IF
574 55335 : CALL get_atomic_kind(particle_set(iatom)%atomic_kind, kind_number=ikind)
575 55335 : cluster_list(iatom) = iatom
576 55335 : SELECT CASE (cost_model)
577 : CASE (model_block_count)
578 55335 : CALL get_qs_kind(qs_kind_set(ikind), nsgf=nsgf)
579 55335 : cluster_price = nsgf
580 : CASE (model_block_lmax)
581 0 : CALL get_qs_kind(qs_kind_set(ikind), basis_set=orb_basis_set)
582 0 : CALL get_gto_basis_set(orb_basis_set, lmax=lmax_basis)
583 0 : cluster_price = MAXVAL(lmax_basis)
584 : CASE default
585 0 : CALL get_qs_kind(qs_kind_set(ikind), basis_set=orb_basis_set)
586 0 : CALL get_gto_basis_set(orb_basis_set, lmax=lmax_basis)
587 55335 : cluster_price = 8 + (MAXVAL(lmax_basis)**2)
588 : END SELECT
589 120392 : cluster_prices(iatom) = cluster_price
590 : END DO
591 : ELSE
592 : imol = 0
593 4 : DO imolecule_kind = 1, nmolecule_kind
594 2 : molecule_kind => molecule_kind_set(imolecule_kind)
595 2 : CALL get_molecule_kind(molecule_kind=molecule_kind, molecule_list=molecule_list, natom=natom_mol)
596 8 : DO imolecule = 1, SIZE(molecule_list)
597 4 : imol = imol + 1
598 4 : cluster_list(imol) = imol
599 4 : cluster_price = 0
600 16 : DO iatom_mol = 1, natom_mol
601 12 : iatom = molecule_set(molecule_list(imolecule))%first_atom + iatom_mol - 1
602 12 : CALL get_atomic_kind(particle_set(iatom)%atomic_kind, kind_number=ikind)
603 16 : SELECT CASE (cost_model)
604 : CASE (model_block_count)
605 12 : CALL get_qs_kind(qs_kind_set(ikind), nsgf=nsgf)
606 12 : cluster_price = cluster_price + nsgf
607 : CASE (model_block_lmax)
608 0 : CALL get_qs_kind(qs_kind_set(ikind), basis_set=orb_basis_set)
609 0 : CALL get_gto_basis_set(orb_basis_set, lmax=lmax_basis)
610 0 : cluster_price = cluster_price + MAXVAL(lmax_basis)
611 : CASE default
612 0 : CALL get_qs_kind(qs_kind_set(ikind), basis_set=orb_basis_set)
613 0 : CALL get_gto_basis_set(orb_basis_set, lmax=lmax_basis)
614 12 : cluster_price = cluster_price + 8 + (MAXVAL(lmax_basis)**2)
615 : END SELECT
616 : END DO
617 6 : cluster_prices(imol) = cluster_price
618 : END DO
619 : END DO
620 : END IF
621 :
622 : ! And distribute
623 9724 : IF (basic_optimization) THEN
624 : CALL make_basic_distribution(cluster_list, cluster_prices, &
625 9556 : nprow, cluster_row_distribution(:, 1), npcol, cluster_col_distribution(:, 1))
626 : ELSE
627 168 : IF (basic_cluster_optimization) THEN
628 4 : IF (molecular_distribution) THEN
629 0 : CPABORT("clustering and molecular blocking NYI")
630 : END IF
631 16 : ALLOCATE (pbc_scaled_coords(3, natom), coords(3, natom))
632 118 : DO iatom = 1, natom
633 114 : CALL real_to_scaled(pbc_scaled_coords(:, iatom), pbc(particle_set(iatom)%r(:), cell), cell)
634 118 : coords(:, iatom) = pbc(particle_set(iatom)%r(:), cell)
635 : END DO
636 : CALL make_cluster_distribution(coords, pbc_scaled_coords, cell, cluster_prices, &
637 4 : nprow, cluster_row_distribution, npcol, cluster_col_distribution)
638 : ELSE ! basic_spatial_optimization
639 492 : ALLOCATE (pbc_scaled_coords(3, nclusters))
640 164 : IF (.NOT. molecular_distribution) THEN
641 : ! just scaled coords
642 2658 : DO iatom = 1, natom
643 2658 : CALL real_to_scaled(pbc_scaled_coords(:, iatom), pbc(particle_set(iatom)%r(:), cell), cell)
644 : END DO
645 : ELSE
646 : ! use scaled coords of geometric center, folding when appropriate
647 : imol = 0
648 0 : DO imolecule_kind = 1, nmolecule_kind
649 0 : molecule_kind => molecule_kind_set(imolecule_kind)
650 0 : CALL get_molecule_kind(molecule_kind=molecule_kind, molecule_list=molecule_list, natom=natom_mol)
651 0 : DO imolecule = 1, SIZE(molecule_list)
652 0 : imol = imol + 1
653 0 : iatom_one = molecule_set(molecule_list(imolecule))%first_atom
654 0 : center = 0.0_dp
655 0 : DO iatom_mol = 1, natom_mol
656 0 : iatom = molecule_set(molecule_list(imolecule))%first_atom + iatom_mol - 1
657 : center = center + &
658 0 : pbc(particle_set(iatom)%r(:) - particle_set(iatom_one)%r(:), cell) + particle_set(iatom_one)%r(:)
659 : END DO
660 0 : center = center/natom_mol
661 0 : CALL real_to_scaled(pbc_scaled_coords(:, imol), pbc(center, cell), cell)
662 : END DO
663 : END DO
664 : END IF
665 :
666 : CALL make_basic_spatial_distribution(pbc_scaled_coords, cluster_prices, &
667 164 : nprow, cluster_row_distribution(:, 1), npcol, cluster_col_distribution(:, 1))
668 :
669 164 : DEALLOCATE (pbc_scaled_coords)
670 : END IF
671 : END IF
672 :
673 : ! And assign back
674 9724 : IF (.NOT. molecular_distribution) THEN
675 279672 : row_distribution = cluster_row_distribution
676 279672 : col_distribution = cluster_col_distribution
677 : ELSE
678 : imol = 0
679 4 : DO imolecule_kind = 1, nmolecule_kind
680 2 : molecule_kind => molecule_kind_set(imolecule_kind)
681 2 : CALL get_molecule_kind(molecule_kind=molecule_kind, molecule_list=molecule_list, natom=natom_mol)
682 8 : DO imolecule = 1, SIZE(molecule_list)
683 4 : imol = imol + 1
684 18 : DO iatom_mol = 1, natom_mol
685 12 : iatom = molecule_set(molecule_list(imolecule))%first_atom + iatom_mol - 1
686 72 : row_distribution(iatom, :) = cluster_row_distribution(imol, :)
687 76 : col_distribution(iatom, :) = cluster_col_distribution(imol, :)
688 : END DO
689 : END DO
690 : END DO
691 : END IF
692 :
693 : ! cleanup
694 9724 : DEALLOCATE (cluster_list)
695 9724 : DEALLOCATE (cluster_prices)
696 9724 : DEALLOCATE (cluster_row_distribution)
697 19448 : DEALLOCATE (cluster_col_distribution)
698 :
699 : ELSE
700 : ! expects nothing else
701 0 : CPABORT("Invalid optimization for DFT%QS%DISTRIBUTION")
702 : END IF
703 :
704 : ! prepare the lists of local particles
705 :
706 : ! count local particles of a given kind
707 9724 : nparticle_local_col = 0
708 9724 : nparticle_local_row = 0
709 65071 : DO iatom = 1, natom
710 55347 : CALL get_atomic_kind(atomic_kind=particle_set(iatom)%atomic_kind, kind_number=kind_a)
711 55347 : IF (row_distribution(iatom, 1) == myprow) nparticle_local_row(kind_a) = nparticle_local_row(kind_a) + 1
712 120418 : IF (col_distribution(iatom, 1) == mypcol) nparticle_local_col(kind_a) = nparticle_local_col(kind_a) + 1
713 : END DO
714 :
715 : ! allocate space
716 27985 : DO iparticle_kind = 1, nparticle_kind
717 18261 : n = nparticle_local_row(iparticle_kind)
718 49435 : ALLOCATE (local_particle_row(iparticle_kind)%array(n))
719 :
720 18261 : n = nparticle_local_col(iparticle_kind)
721 64507 : ALLOCATE (local_particle_col(iparticle_kind)%array(n))
722 : END DO
723 :
724 : ! store
725 9724 : nparticle_local_col = 0
726 9724 : nparticle_local_row = 0
727 65071 : DO iatom = 1, natom
728 55347 : CALL get_atomic_kind(atomic_kind=particle_set(iatom)%atomic_kind, kind_number=kind_a)
729 55347 : IF (row_distribution(iatom, 1) == myprow) THEN
730 28749 : nparticle_local_row(kind_a) = nparticle_local_row(kind_a) + 1
731 28749 : local_particle_row(kind_a)%array(nparticle_local_row(kind_a)) = iatom
732 : END IF
733 120418 : IF (col_distribution(iatom, 1) == mypcol) THEN
734 55347 : nparticle_local_col(kind_a) = nparticle_local_col(kind_a) + 1
735 55347 : local_particle_col(kind_a)%array(nparticle_local_col(kind_a)) = iatom
736 : END IF
737 : END DO
738 :
739 : ! *** Generate the 2d distribution structure but take care of the zero offsets required
740 65071 : row_distribution(:, 1) = row_distribution(:, 1) - 1
741 65071 : col_distribution(:, 1) = col_distribution(:, 1) - 1
742 : CALL distribution_2d_create(distribution_2d, &
743 : row_distribution_ptr=row_distribution, &
744 : col_distribution_ptr=col_distribution, &
745 : local_rows_ptr=local_particle_row, &
746 : local_cols_ptr=local_particle_col, &
747 9724 : blacs_env=blacs_env)
748 :
749 9724 : NULLIFY (local_particle_row)
750 9724 : NULLIFY (local_particle_col)
751 9724 : NULLIFY (row_distribution)
752 9724 : NULLIFY (col_distribution)
753 :
754 : ! *** Print distribution, if requested ***
755 9724 : IF (BTEST(cp_print_key_should_output(logger%iter_info, &
756 9724 : force_env_section, "PRINT%DISTRIBUTION"), cp_p_file)) THEN
757 :
758 : output_unit = cp_print_key_unit_nr(logger, force_env_section, "PRINT%DISTRIBUTION", &
759 108 : extension=".Log")
760 :
761 : ! *** Print row distribution ***
762 :
763 324 : ALLOCATE (work(nprow))
764 108 : work(:) = 0
765 :
766 322 : IF (mypcol == 1) work(myprow) = SUM(distribution_2d%n_local_rows)
767 :
768 108 : CALL group%sum(work)
769 :
770 108 : IF (output_unit > 0) THEN
771 : WRITE (UNIT=output_unit, &
772 : FMT="(/, T2, A, /, T15, A, /, (T16, I10, T41, I10, T71, I10))") &
773 54 : "DISTRIBUTION OF THE PARTICLES (ROWS)", &
774 54 : "Process row Number of particles Number of matrix rows", &
775 214 : (iprow - 1, work(iprow), -1, iprow=1, nprow)
776 : WRITE (UNIT=output_unit, FMT="(T23, A3, T41, I10, T71, I10)") &
777 160 : "Sum", SUM(work), -1
778 54 : CALL m_flush(output_unit)
779 : END IF
780 :
781 108 : DEALLOCATE (work)
782 :
783 : ! *** Print column distribution ***
784 :
785 324 : ALLOCATE (work(npcol))
786 108 : work(:) = 0
787 :
788 217 : IF (myprow == 1) work(mypcol) = SUM(distribution_2d%n_local_cols)
789 :
790 108 : CALL group%sum(work)
791 :
792 108 : IF (output_unit > 0) THEN
793 : WRITE (UNIT=output_unit, &
794 : FMT="(/, T2, A, /, T15, A, /, (T16, I10, T41, I10, T71, I10))") &
795 54 : "DISTRIBUTION OF THE PARTICLES (COLUMNS)", &
796 54 : "Process col Number of particles Number of matrix columns", &
797 162 : (ipcol - 1, work(ipcol), -1, ipcol=1, npcol)
798 : WRITE (UNIT=output_unit, FMT="(T23, A3, T41, I10, T71, I10)") &
799 108 : "Sum", SUM(work), -1
800 54 : CALL m_flush(output_unit)
801 : END IF
802 :
803 108 : DEALLOCATE (work)
804 :
805 : CALL cp_print_key_finished_output(output_unit, logger, force_env_section, &
806 108 : "PRINT%DISTRIBUTION")
807 : END IF
808 : END ASSOCIATE
809 :
810 9724 : IF (BTEST(cp_print_key_should_output(logger%iter_info, &
811 : force_env_section, "PRINT%DISTRIBUTION2D"), cp_p_file)) THEN
812 :
813 98 : iw = cp_logger_get_default_unit_nr(logger, LOCAL=.TRUE.)
814 : CALL distribution_2d_write(distribution_2d, &
815 : unit_nr=iw, &
816 : local=.TRUE., &
817 98 : long_description=.TRUE.)
818 :
819 : END IF
820 :
821 : ! *** Release work storage ***
822 :
823 9724 : DEALLOCATE (nparticle_local_row)
824 :
825 9724 : DEALLOCATE (nparticle_local_col)
826 :
827 9724 : CALL timestop(handle)
828 :
829 29172 : END SUBROUTINE distribute_molecules_2d
830 :
831 : ! **************************************************************************************************
832 : !> \brief Creates a basic distribution
833 : !> \param cluster_list ...
834 : !> \param cluster_prices ...
835 : !> \param nprows ...
836 : !> \param row_distribution ...
837 : !> \param npcols ...
838 : !> \param col_distribution ...
839 : !> \par History
840 : !> - Created 2010-08-06 UB
841 : ! **************************************************************************************************
842 9556 : SUBROUTINE make_basic_distribution(cluster_list, cluster_prices, &
843 9556 : nprows, row_distribution, npcols, col_distribution)
844 : INTEGER, DIMENSION(:), INTENT(INOUT) :: cluster_list, cluster_prices
845 : INTEGER, INTENT(IN) :: nprows
846 : INTEGER, DIMENSION(:), INTENT(OUT) :: row_distribution
847 : INTEGER, INTENT(IN) :: npcols
848 : INTEGER, DIMENSION(:), INTENT(OUT) :: col_distribution
849 :
850 : CHARACTER(len=*), PARAMETER :: routineN = 'make_basic_distribution'
851 :
852 : INTEGER :: bin, cluster, cluster_index, &
853 : cluster_price, nbins, nclusters, pcol, &
854 : pgrid_gcd, prow, timing_handle
855 : INTEGER(int_8) :: bin_price
856 : LOGICAL :: found
857 : TYPE(cp_heap_type) :: bin_heap
858 :
859 : ! ---------------------------------------------------------------------------
860 :
861 9556 : CALL timeset(routineN, timing_handle)
862 9556 : nbins = lcm(nprows, npcols)
863 9556 : pgrid_gcd = gcd(nprows, npcols)
864 9556 : CALL sort(cluster_prices, SIZE(cluster_list), cluster_list)
865 9556 : CALL cp_heap_new(bin_heap, nbins)
866 46288 : CALL cp_heap_fill(bin_heap, [(0_int_8, bin=1, nbins)])
867 : !
868 9556 : nclusters = SIZE(cluster_list)
869 : ! Put the most expensive cluster in the bin with the smallest
870 : ! price and repeat.
871 62287 : DO cluster_index = nclusters, 1, -1
872 52731 : cluster = cluster_list(cluster_index)
873 52731 : CALL cp_heap_get_first(bin_heap, bin, bin_price, found)
874 52731 : IF (.NOT. found) THEN
875 0 : CPABORT("No topmost heap element found.")
876 : END IF
877 : !
878 52731 : prow = INT((bin - 1)*pgrid_gcd/npcols)
879 52731 : IF (prow >= nprows) THEN
880 0 : CPABORT("Invalid process row.")
881 : END IF
882 52731 : pcol = INT((bin - 1)*pgrid_gcd/nprows)
883 52731 : IF (pcol >= npcols) THEN
884 0 : CPABORT("Invalid process column.")
885 : END IF
886 52731 : row_distribution(cluster) = prow + 1
887 52731 : col_distribution(cluster) = pcol + 1
888 : !
889 52731 : cluster_price = cluster_prices(cluster_index)
890 52731 : bin_price = bin_price + cluster_price
891 115018 : CALL cp_heap_reset_first(bin_heap, bin_price)
892 : END DO
893 9556 : CALL cp_heap_release(bin_heap)
894 9556 : CALL timestop(timing_handle)
895 9556 : END SUBROUTINE make_basic_distribution
896 :
897 : ! **************************************************************************************************
898 : !> \brief Creates a basic spatial distribution
899 : !> that tries to make the corresponding blocks as homogeneous as possible
900 : !> \param pbc_scaled_coords ...
901 : !> \param costs ...
902 : !> \param nprows ...
903 : !> \param row_distribution ...
904 : !> \param npcols ...
905 : !> \param col_distribution ...
906 : !> \par History
907 : !> - Created 2010-11-11 Joost VandeVondele
908 : ! **************************************************************************************************
909 164 : SUBROUTINE make_basic_spatial_distribution(pbc_scaled_coords, costs, &
910 164 : nprows, row_distribution, npcols, col_distribution)
911 : REAL(KIND=dp), DIMENSION(:, :), INTENT(IN) :: pbc_scaled_coords
912 : INTEGER, DIMENSION(:), INTENT(IN) :: costs
913 : INTEGER, INTENT(IN) :: nprows
914 : INTEGER, DIMENSION(:), INTENT(OUT) :: row_distribution
915 : INTEGER, INTENT(IN) :: npcols
916 : INTEGER, DIMENSION(:), INTENT(OUT) :: col_distribution
917 :
918 : CHARACTER(len=*), PARAMETER :: routineN = 'make_basic_spatial_distribution'
919 :
920 : INTEGER :: handle, iatom, natoms, nbins, pgrid_gcd
921 : INTEGER, ALLOCATABLE, DIMENSION(:) :: bin_costs, distribution
922 :
923 164 : CALL timeset(routineN, handle)
924 :
925 164 : natoms = SIZE(costs)
926 164 : nbins = lcm(nprows, npcols)
927 164 : pgrid_gcd = gcd(nprows, npcols)
928 820 : ALLOCATE (bin_costs(nbins), distribution(natoms))
929 164 : bin_costs = 0
930 :
931 5152 : CALL spatial_recurse(pbc_scaled_coords, costs, [(iatom, iatom=1, natoms)], bin_costs, distribution, 0)
932 :
933 : ! WRITE(*, *) "Final bin costs: ", bin_costs
934 :
935 : ! final row_distribution / col_distribution
936 2658 : DO iatom = 1, natoms
937 2494 : row_distribution(iatom) = (distribution(iatom) - 1)*pgrid_gcd/npcols + 1
938 2658 : col_distribution(iatom) = (distribution(iatom) - 1)*pgrid_gcd/nprows + 1
939 : END DO
940 :
941 164 : DEALLOCATE (bin_costs, distribution)
942 :
943 164 : CALL timestop(handle)
944 :
945 164 : END SUBROUTINE make_basic_spatial_distribution
946 :
947 : ! **************************************************************************************************
948 : !> \brief ...
949 : !> \param pbc_scaled_coords ...
950 : !> \param costs ...
951 : !> \param indices ...
952 : !> \param bin_costs ...
953 : !> \param distribution ...
954 : !> \param level ...
955 : ! **************************************************************************************************
956 3168 : RECURSIVE SUBROUTINE spatial_recurse(pbc_scaled_coords, costs, indices, bin_costs, distribution, level)
957 : REAL(KIND=dp), DIMENSION(:, :), INTENT(IN) :: pbc_scaled_coords
958 : INTEGER, DIMENSION(:), INTENT(IN) :: costs, indices
959 : INTEGER, DIMENSION(:), INTENT(INOUT) :: bin_costs, distribution
960 : INTEGER, INTENT(IN) :: level
961 :
962 : INTEGER :: iatom, ibin, natoms, nbins, nhalf
963 3168 : INTEGER, ALLOCATABLE, DIMENSION(:) :: atom_costs_sorted, atom_permutation, &
964 3168 : bin_costs_sorted, permutation
965 3168 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:) :: coord
966 :
967 3168 : natoms = SIZE(costs)
968 3168 : nbins = SIZE(bin_costs)
969 3168 : nhalf = (natoms + 1)/2
970 :
971 3168 : IF (natoms <= nbins) THEN
972 : ! assign the most expensive atom to the least costly bin
973 6664 : ALLOCATE (bin_costs_sorted(nbins), permutation(nbins))
974 4998 : bin_costs_sorted(:) = bin_costs
975 1666 : CALL sort(bin_costs_sorted, nbins, permutation)
976 6664 : ALLOCATE (atom_costs_sorted(natoms), atom_permutation(natoms))
977 4160 : atom_costs_sorted(:) = costs
978 1666 : CALL sort(atom_costs_sorted, natoms, atom_permutation)
979 1666 : ibin = 0
980 : ! WRITE(*, *) "Dealing with a new bunch of atoms "
981 4160 : DO iatom = natoms, 1, -1
982 2494 : ibin = ibin + 1
983 : ! WRITE(*, *) "atom", indices(atom_permutation(iatom)), "cost", atom_costs_sorted(iatom), &
984 : ! "bin", permutation(ibin), "its cost", bin_costs(permutation(ibin))
985 : ! WRITE(100, '(A, I0, 3F12.6)') "A", permutation(ibin), pbc_scaled_coords(:, atom_permutation(iatom))
986 2494 : bin_costs(permutation(ibin)) = bin_costs(permutation(ibin)) + atom_costs_sorted(iatom)
987 4160 : distribution(indices(atom_permutation(iatom))) = permutation(ibin)
988 : END DO
989 1666 : DEALLOCATE (bin_costs_sorted, permutation, atom_costs_sorted, atom_permutation)
990 : ELSE
991 : ! divide atoms in two subsets, sorting according to their coordinates, alternatively x, y, z
992 : ! recursively do this for both subsets
993 7510 : ALLOCATE (coord(natoms), permutation(natoms))
994 12216 : coord(:) = pbc_scaled_coords(MOD(level, 3) + 1, :)
995 1502 : CALL sort(coord, natoms, permutation)
996 : CALL spatial_recurse(pbc_scaled_coords(:, permutation(1:nhalf)), costs(permutation(1:nhalf)), &
997 36098 : indices(permutation(1:nhalf)), bin_costs, distribution, level + 1)
998 : CALL spatial_recurse(pbc_scaled_coords(:, permutation(nhalf + 1:)), costs(permutation(nhalf + 1:)), &
999 31190 : indices(permutation(nhalf + 1:)), bin_costs, distribution, level + 1)
1000 1502 : DEALLOCATE (coord, permutation)
1001 : END IF
1002 :
1003 3168 : END SUBROUTINE spatial_recurse
1004 :
1005 : ! **************************************************************************************************
1006 : !> \brief creates a distribution placing close by atoms into clusters and
1007 : !> putting them on the same processors. Load balancing is
1008 : !> performed by balancing sum of the cluster costs per processor
1009 : !> \param coords coordinates of the system
1010 : !> \param scaled_coords scaled coordinates
1011 : !> \param cell the cell_type
1012 : !> \param costs costs per atomic block
1013 : !> \param nprows number of precessors per row on the 2d grid
1014 : !> \param row_distribution the resulting distribution over proc_rows of atomic blocks
1015 : !> \param npcols number of precessors per col on the 2d grid
1016 : !> \param col_distribution the resulting distribution over proc_cols of atomic blocks
1017 : ! **************************************************************************************************
1018 4 : SUBROUTINE make_cluster_distribution(coords, scaled_coords, cell, costs, &
1019 4 : nprows, row_distribution, npcols, col_distribution)
1020 : REAL(KIND=dp), DIMENSION(:, :), INTENT(IN) :: coords, scaled_coords
1021 : TYPE(cell_type), POINTER :: cell
1022 : INTEGER, DIMENSION(:), INTENT(IN) :: costs
1023 : INTEGER, INTENT(IN) :: nprows
1024 : INTEGER, DIMENSION(:, :), INTENT(OUT) :: row_distribution
1025 : INTEGER, INTENT(IN) :: npcols
1026 : INTEGER, DIMENSION(:, :), INTENT(OUT) :: col_distribution
1027 :
1028 : CHARACTER(len=*), PARAMETER :: routineN = 'make_cluster_distribution'
1029 :
1030 : INTEGER :: handle, i, icluster, level, natom, &
1031 : output_unit
1032 : INTEGER(KIND=int_8) :: ncluster
1033 : INTEGER, ALLOCATABLE, DIMENSION(:) :: atom_to_cluster, cluster_cost, &
1034 4 : cluster_count, cluster_to_col, &
1035 : cluster_to_row, piv_cost, proc_cost, &
1036 4 : sorted_cost
1037 : REAL(KIND=dp) :: fold(3)
1038 4 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :) :: cluster_center, cluster_high, cluster_low
1039 :
1040 4 : CALL timeset(routineN, handle)
1041 :
1042 4 : output_unit = cp_logger_get_default_io_unit()
1043 :
1044 4 : natom = SIZE(costs)
1045 118 : ncluster = dbcsr_distribution_get_num_images(SUM(costs), natom, nprows, npcols)
1046 12 : ALLOCATE (atom_to_cluster(natom))
1047 12 : ALLOCATE (cluster_cost(ncluster))
1048 8 : ALLOCATE (cluster_to_row(ncluster))
1049 8 : ALLOCATE (cluster_to_col(ncluster))
1050 8 : ALLOCATE (sorted_cost(ncluster))
1051 8 : ALLOCATE (piv_cost(ncluster))
1052 4 : cluster_cost(:) = 0
1053 :
1054 4 : icluster = 0
1055 4 : CALL cluster_recurse(coords, scaled_coords, cell, costs, atom_to_cluster, ncluster, icluster, cluster_cost)
1056 :
1057 16 : sorted_cost(:) = cluster_cost(:)
1058 4 : CALL sort(sorted_cost, INT(ncluster), piv_cost)
1059 :
1060 12 : ALLOCATE (proc_cost(nprows))
1061 4 : proc_cost = 0; level = 1
1062 4 : CALL assign_clusters(cluster_cost, piv_cost, proc_cost, cluster_to_row, nprows)
1063 :
1064 12 : DEALLOCATE (proc_cost); ALLOCATE (proc_cost(npcols))
1065 4 : proc_cost = 0; level = 1
1066 4 : CALL assign_clusters(cluster_cost, piv_cost, proc_cost, cluster_to_col, npcols)
1067 :
1068 118 : DO i = 1, natom
1069 114 : row_distribution(i, 1) = cluster_to_row(atom_to_cluster(i))
1070 114 : row_distribution(i, 2) = atom_to_cluster(i)
1071 114 : col_distribution(i, 1) = cluster_to_col(atom_to_cluster(i))
1072 118 : col_distribution(i, 2) = atom_to_cluster(i)
1073 : END DO
1074 :
1075 : ! generate some statistics on clusters
1076 12 : ALLOCATE (cluster_center(3, ncluster))
1077 8 : ALLOCATE (cluster_low(3, ncluster))
1078 8 : ALLOCATE (cluster_high(3, ncluster))
1079 8 : ALLOCATE (cluster_count(ncluster))
1080 4 : cluster_count = 0
1081 118 : DO i = 1, natom
1082 114 : cluster_count(atom_to_cluster(i)) = cluster_count(atom_to_cluster(i)) + 1
1083 460 : cluster_center(:, atom_to_cluster(i)) = coords(:, i)
1084 : END DO
1085 52 : cluster_low = HUGE(0.0_dp)/2
1086 52 : cluster_high = -HUGE(0.0_dp)/2
1087 118 : DO i = 1, natom
1088 798 : fold = pbc(coords(:, i) - cluster_center(:, atom_to_cluster(i)), cell) + cluster_center(:, atom_to_cluster(i))
1089 456 : cluster_low(:, atom_to_cluster(i)) = MIN(cluster_low(:, atom_to_cluster(i)), fold(:))
1090 460 : cluster_high(:, atom_to_cluster(i)) = MAX(cluster_high(:, atom_to_cluster(i)), fold(:))
1091 : END DO
1092 4 : IF (output_unit > 0) THEN
1093 2 : WRITE (output_unit, *)
1094 2 : WRITE (output_unit, '(T2,A)') "Cluster distribution information"
1095 2 : WRITE (output_unit, '(T2,A,T48,I8)') "Number of atoms", natom
1096 2 : WRITE (output_unit, '(T2,A,T48,I8)') "Number of clusters", ncluster
1097 8 : WRITE (output_unit, '(T2,A,T48,I8)') "Largest cluster in atoms", MAXVAL(cluster_count)
1098 8 : WRITE (output_unit, '(T2,A,T48,I8)') "Smallest cluster in atoms", MINVAL(cluster_count)
1099 2 : WRITE (output_unit, '(T2,A,T48,F8.3,I8)') "Largest cartesian extend [a.u.]/cluster x=", &
1100 8 : MAXVAL(cluster_high(1, :) - cluster_low(1, :), MASK=(cluster_count > 0)), &
1101 14 : MAXLOC(cluster_high(1, :) - cluster_low(1, :), MASK=(cluster_count > 0))
1102 2 : WRITE (output_unit, '(T2,A,T48,F8.3,I8)') "Largest cartesian extend [a.u.]/cluster y=", &
1103 8 : MAXVAL(cluster_high(2, :) - cluster_low(2, :), MASK=(cluster_count > 0)), &
1104 14 : MAXLOC(cluster_high(2, :) - cluster_low(2, :), MASK=(cluster_count > 0))
1105 2 : WRITE (output_unit, '(T2,A,T48,F8.3,I8)') "Largest cartesian extend [a.u.]/cluster z=", &
1106 8 : MAXVAL(cluster_high(3, :) - cluster_low(3, :), MASK=(cluster_count > 0)), &
1107 14 : MAXLOC(cluster_high(3, :) - cluster_low(3, :), MASK=(cluster_count > 0))
1108 : END IF
1109 :
1110 4 : DEALLOCATE (atom_to_cluster, cluster_cost, cluster_to_row, cluster_to_col, sorted_cost, piv_cost, proc_cost)
1111 4 : CALL timestop(handle)
1112 :
1113 8 : END SUBROUTINE make_cluster_distribution
1114 :
1115 : ! **************************************************************************************************
1116 : !> \brief assigns the clusters to processors, tryimg to balance the cost on the nodes
1117 : !> \param cluster_cost vector with the cost of each cluster
1118 : !> \param piv_cost pivoting vector sorting the cluster_cost
1119 : !> \param proc_cost cost per processor, on input 0 everywhere
1120 : !> \param cluster_assign assgnment of clusters on proc
1121 : !> \param nproc number of processor over which clusters are distributed
1122 : ! **************************************************************************************************
1123 8 : SUBROUTINE assign_clusters(cluster_cost, piv_cost, proc_cost, cluster_assign, nproc)
1124 : INTEGER, ALLOCATABLE, DIMENSION(:) :: cluster_cost, piv_cost, proc_cost, &
1125 : cluster_assign
1126 : INTEGER :: nproc
1127 :
1128 : CHARACTER(len=*), PARAMETER :: routineN = 'assign_clusters'
1129 :
1130 : INTEGER :: handle, i, ilevel, offset, &
1131 16 : piv_pcost(nproc), sort_proc_cost(nproc)
1132 :
1133 8 : CALL timeset(routineN, handle)
1134 :
1135 26 : DO ilevel = 1, SIZE(cluster_cost)/nproc
1136 42 : sort_proc_cost(:) = proc_cost(:)
1137 18 : CALL sort(sort_proc_cost, nproc, piv_pcost)
1138 :
1139 18 : offset = (SIZE(cluster_cost)/nproc - ilevel + 1)*nproc + 1
1140 50 : DO i = 1, nproc
1141 24 : cluster_assign(piv_cost(offset - i)) = piv_pcost(i)
1142 42 : proc_cost(piv_pcost(i)) = proc_cost(piv_pcost(i)) + cluster_cost(piv_cost(offset - i))
1143 : END DO
1144 : END DO
1145 :
1146 8 : CALL timestop(handle)
1147 :
1148 8 : END SUBROUTINE assign_clusters
1149 :
1150 : ! **************************************************************************************************
1151 : !> \brief recursive routine to cluster atoms.
1152 : !> Low level uses a modified KMEANS algorithm
1153 : !> recursion is used to reduce cost.
1154 : !> each level will subdivide a cluster into smaller clusters
1155 : !> If only a single split is necessary atoms are assigned to the current cluster
1156 : !> \param coord coordinates of the system
1157 : !> \param scaled_coord scaled coordinates
1158 : !> \param cell the cell_type
1159 : !> \param costs costs per atomic block
1160 : !> \param cluster_inds the atom_to cluster mapping
1161 : !> \param ncluster number of clusters still to be created on a given recursion level
1162 : !> \param icluster the index of the current cluster to be created
1163 : !> \param fin_cluster_cost total cost of the final clusters
1164 : ! **************************************************************************************************
1165 16 : RECURSIVE SUBROUTINE cluster_recurse(coord, scaled_coord, cell, costs, cluster_inds, ncluster, icluster, fin_cluster_cost)
1166 : REAL(KIND=dp), DIMENSION(:, :), INTENT(IN) :: coord, scaled_coord
1167 : TYPE(cell_type), POINTER :: cell
1168 : INTEGER, DIMENSION(:), INTENT(IN) :: costs
1169 : INTEGER, DIMENSION(:), INTENT(INOUT) :: cluster_inds
1170 : INTEGER(KIND=int_8), INTENT(INOUT) :: ncluster
1171 : INTEGER, INTENT(INOUT) :: icluster
1172 : INTEGER, DIMENSION(:), INTENT(INOUT) :: fin_cluster_cost
1173 :
1174 : INTEGER :: i, ibeg, iend, maxv(1), min_seed, &
1175 : natoms, nleft, nsplits, seed, tot_cost
1176 16 : INTEGER(KIND=int_8), ALLOCATABLE, DIMENSION(:) :: ncluster_new
1177 16 : INTEGER, ALLOCATABLE, DIMENSION(:) :: cluster_cost, inds_tmp, nat_cluster, piv
1178 : LOGICAL :: found
1179 : REAL(KIND=dp) :: balance, balance_new, conv
1180 :
1181 16 : natoms = SIZE(coord, 2)
1182 : ! This is a bit of an arbitrary choice, simply a try to avoid too many clusters on large systems and too few for balancing on
1183 : ! small systems or subclusters
1184 16 : IF (natoms <= 1) THEN
1185 2 : nsplits = 1
1186 : ELSE
1187 14 : nsplits = MIN(INT(MIN(INT(MAX(6, INT(60.00/LOG(REAL(natoms, KIND=dp)))), KIND=int_8), ncluster)), natoms)
1188 : END IF
1189 16 : IF (nsplits == 1) THEN
1190 12 : icluster = icluster + 1
1191 126 : cluster_inds = icluster
1192 126 : fin_cluster_cost(icluster) = SUM(costs)
1193 : ELSE
1194 36 : ALLOCATE (cluster_cost(nsplits), ncluster_new(nsplits), inds_tmp(natoms), piv(natoms), nat_cluster(nsplits))
1195 : ! initialise some values
1196 4 : cluster_cost = 0; seed = 300; found = .TRUE.; min_seed = seed
1197 4 : CALL kmeans(nsplits, coord, scaled_coord, cell, cluster_inds, nat_cluster, seed, conv)
1198 28 : balance = MAXVAL(REAL(nat_cluster, KIND=dp))/MINVAL(REAL(nat_cluster, KIND=dp))
1199 :
1200 : ! If the system is small enough try to do better in terms of balancing number of atoms per cluster
1201 : ! by changing the seed for the initial guess
1202 4 : IF (natoms < 1000 .AND. balance > 1.1) THEN
1203 24 : found = .FALSE.
1204 24 : DO i = 1, 5
1205 24 : IF (balance > 1.1) THEN
1206 20 : CALL kmeans(nsplits, coord, scaled_coord, cell, cluster_inds, nat_cluster, seed + i*40, conv)
1207 140 : balance_new = MAXVAL(REAL(nat_cluster, KIND=dp))/MINVAL(REAL(nat_cluster, KIND=dp))
1208 20 : IF (balance_new < balance) THEN
1209 0 : balance = balance_new
1210 0 : min_seed = seed + i*40
1211 : END IF
1212 : ELSE
1213 : found = .TRUE.
1214 : EXIT
1215 : END IF
1216 : END DO
1217 : END IF
1218 : !If we do not match the convergence than recompute at least the best assignment
1219 4 : IF (.NOT. found) CALL kmeans(nsplits, coord, scaled_coord, cell, cluster_inds, nat_cluster, min_seed, conv)
1220 :
1221 : ! compute the cost of each cluster to decide how many splits have to be performed on the next lower level
1222 118 : DO i = 1, natoms
1223 118 : cluster_cost(cluster_inds(i)) = cluster_cost(cluster_inds(i)) + costs(i)
1224 : END DO
1225 16 : tot_cost = SUM(cluster_cost)
1226 : ! compute new splitting, can be done more elegant
1227 16 : ncluster_new(:) = ncluster*cluster_cost(:)/tot_cost
1228 16 : nleft = INT(ncluster - SUM(ncluster_new))
1229 : ! As we won't have empty clusters, we can not have 0 as new size, so we correct for this at first
1230 16 : DO i = 1, nsplits
1231 16 : IF (ncluster_new(i) == 0) THEN
1232 6 : ncluster_new(i) = 1
1233 6 : nleft = nleft - 1
1234 : END IF
1235 : END DO
1236 : ! now comes the next part that the number of clusters will not match anymore, so try to correct in a meaningful way without
1237 : ! introducing 0 sized blocks again
1238 4 : IF (nleft /= 0) THEN
1239 0 : DO i = 1, ABS(nleft)
1240 0 : IF (nleft < 0) THEN
1241 0 : maxv = MINLOC(cluster_cost/ncluster_new)
1242 0 : IF (ncluster_new(maxv(1)) /= 1) THEN
1243 0 : ncluster_new(maxv) = ncluster_new(maxv) - 1
1244 : ELSE
1245 0 : maxv = MAXLOC(ncluster_new)
1246 0 : ncluster_new(maxv) = ncluster_new(maxv) - 1
1247 : END IF
1248 : ELSE
1249 0 : maxv = MAXLOC(cluster_cost/ncluster_new)
1250 0 : ncluster_new(maxv) = ncluster_new(maxv) + 1
1251 : END IF
1252 : END DO
1253 : END IF
1254 :
1255 : !Now get the permutations to sort the atoms in the nsplits clusters for the next level of iteration
1256 118 : inds_tmp(:) = cluster_inds(:)
1257 4 : CALL sort(inds_tmp, natoms, piv)
1258 :
1259 4 : ibeg = 1; iend = 0
1260 16 : DO i = 1, nsplits
1261 12 : IF (nat_cluster(i) == 0) CYCLE
1262 12 : iend = iend + nat_cluster(i)
1263 : CALL cluster_recurse(coord(:, piv(ibeg:iend)), scaled_coord(:, piv(ibeg:iend)), cell, costs(piv(ibeg:iend)), &
1264 1038 : inds_tmp(ibeg:iend), ncluster_new(i), icluster, fin_cluster_cost)
1265 16 : ibeg = ibeg + nat_cluster(i)
1266 : END DO
1267 : ! copy the sorted cluster IDs on the old layout, inds_tmp gets set at the lowest level of recursion
1268 118 : cluster_inds(piv(:)) = inds_tmp
1269 4 : DEALLOCATE (cluster_cost, ncluster_new, inds_tmp, piv, nat_cluster)
1270 :
1271 : END IF
1272 :
1273 16 : END SUBROUTINE cluster_recurse
1274 :
1275 : ! **************************************************************************************************
1276 : !> \brief A modified version of the kmeans algorithm.
1277 : !> The assignment has a penalty function in case clusters become
1278 : !> larger than average. Like this more even sized clusters are created
1279 : !> trading it for locality
1280 : !> \param ncent number of centers to be created
1281 : !> \param coord coordinates
1282 : !> \param scaled_coord scaled coord
1283 : !> \param cell the cell_type
1284 : !> \param cluster atom to cluster assignment
1285 : !> \param nat_cl atoms per cluster
1286 : !> \param seed seed for the RNG. Algorithm might need multiple tries to deliver best results
1287 : !> \param tot_var the total variance of the clusters around the centers
1288 : ! **************************************************************************************************
1289 28 : SUBROUTINE kmeans(ncent, coord, scaled_coord, cell, cluster, nat_cl, seed, tot_var)
1290 : INTEGER :: ncent
1291 : REAL(KIND=dp), DIMENSION(:, :) :: coord, scaled_coord
1292 : TYPE(cell_type), POINTER :: cell
1293 : INTEGER, DIMENSION(:) :: cluster, nat_cl
1294 : INTEGER :: seed
1295 : REAL(KIND=dp) :: tot_var
1296 :
1297 : CHARACTER(len=*), PARAMETER :: routineN = 'kmeans'
1298 :
1299 : INTEGER :: handle, i, ind, itn, j, nat, oldc
1300 : LOGICAL :: changed
1301 56 : REAL(KIND=dp) :: average(3, ncent, 2), cent_coord(3, ncent), devi, deviat(ncent), dist, &
1302 56 : dvec(3), old_var, rn, scaled_cent(3, ncent), var_cl(ncent)
1303 28 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :) :: dmat
1304 : REAL(KIND=dp), DIMENSION(3, 2) :: initial_seed
1305 : TYPE(rng_stream_type) :: rng_stream
1306 :
1307 28 : CALL timeset(routineN, handle)
1308 :
1309 252 : initial_seed = REAL(seed, dp); nat = SIZE(coord, 2)
1310 112 : ALLOCATE (dmat(ncent, nat))
1311 :
1312 : rng_stream = rng_stream_type(name="kmeans uniform distribution [0,1]", &
1313 28 : distribution_type=UNIFORM, seed=initial_seed)
1314 :
1315 : ! try to find a clever initial guess with centers being somewhat distributed
1316 28 : rn = rng_stream%next()
1317 28 : ind = CEILING(rn*nat)
1318 112 : cent_coord(:, 1) = coord(:, ind)
1319 84 : DO i = 2, ncent
1320 28 : DO
1321 928 : rn = rng_stream%next()
1322 928 : ind = CEILING(rn*nat)
1323 3712 : cent_coord(:, i) = coord(:, ind)
1324 : devi = HUGE(1.0_dp)
1325 2004 : DO j = 1, i - 1
1326 1076 : dvec = pbc(cent_coord(:, j), cent_coord(:, i), cell)
1327 4304 : dist = NORM2(dvec)
1328 928 : IF (dist < devi) devi = dist
1329 : END DO
1330 928 : rn = rng_stream%next()
1331 928 : IF (rn < devi**2/169.0) EXIT
1332 : END DO
1333 : END DO
1334 :
1335 : ! Now start the KMEANS but penalise it in case it starts packing too many atoms into a single set
1336 : ! Unfoirtunatelz as this is dependent on what happened before it cant be parallel
1337 826 : cluster = 0; old_var = HUGE(1.0_dp)
1338 106 : DO itn = 1, 1000
1339 1210 : changed = .FALSE.; var_cl = 0.0_dp; tot_var = 0.0_dp; nat_cl = 0; deviat = 0.0_dp
1340 : ! !$OMP PARALLEL DO PRIVATE(i,j,dvec)
1341 4402 : DO i = 1, nat
1342 21418 : DO j = 1, ncent
1343 17016 : dvec = pbc(cent_coord(:, j), coord(:, i), cell)
1344 72360 : dmat(j, i) = DOT_PRODUCT(dvec, dvec)
1345 : END DO
1346 : END DO
1347 4402 : DO i = 1, nat
1348 4296 : devi = HUGE(1.0_dp); oldc = cluster(i)
1349 21312 : DO j = 1, ncent
1350 17016 : dist = dmat(j, i) + MAX(nat_cl(j)**2/nat*ncent, nat/ncent)
1351 21312 : IF (dist < devi) THEN
1352 8760 : devi = dist; cluster(i) = j
1353 : END IF
1354 : END DO
1355 4296 : deviat(cluster(i)) = deviat(cluster(i)) + SQRT(devi)
1356 4296 : nat_cl(cluster(i)) = nat_cl(cluster(i)) + 1
1357 4296 : tot_var = tot_var + devi
1358 4402 : IF (oldc /= cluster(i)) changed = .TRUE.
1359 : END DO
1360 : ! get the update of the centers done, add a new one in case one center lost all its atoms
1361 : ! the algorithm would survive, but its nice to really create what you demand
1362 106 : IF (tot_var >= old_var) EXIT
1363 106 : IF (changed) THEN
1364 : ! Here misery of computing the center of geometry of the clusters in PBC.
1365 : ! The mapping on the unit circle allows to circumvent all problems
1366 2506 : average = 0.0_dp
1367 3576 : DO i = 1, SIZE(coord, 2)
1368 13992 : average(:, cluster(i), 1) = average(:, cluster(i), 1) + COS(scaled_coord(:, i)*2.0_dp*pi)
1369 14070 : average(:, cluster(i), 2) = average(:, cluster(i), 2) + SIN(scaled_coord(:, i)*2.0_dp*pi)
1370 : END DO
1371 :
1372 362 : DO i = 1, ncent
1373 362 : IF (nat_cl(i) == 0) THEN
1374 0 : rn = rng_stream%next()
1375 0 : scaled_cent(:, i) = scaled_coord(:, CEILING(rn*nat))
1376 : ELSE
1377 1136 : average(:, i, 1) = average(:, i, 1)/REAL(nat_cl(i), dp)
1378 1136 : average(:, i, 2) = average(:, i, 2)/REAL(nat_cl(i), dp)
1379 1136 : scaled_cent(:, i) = (ATAN2(-average(:, i, 2), -average(:, i, 1)) + pi)/(2.0_dp*pi)
1380 284 : CALL scaled_to_real(cent_coord(:, i), scaled_cent(:, i), cell)
1381 : END IF
1382 : END DO
1383 : ELSE
1384 : EXIT
1385 : END IF
1386 : END DO
1387 :
1388 28 : CALL timestop(handle)
1389 :
1390 728 : END SUBROUTINE kmeans
1391 :
1392 : END MODULE distribution_methods
|