Branch data Line data Source code
1 : : !!****m* ABINIT/m_invovl
2 : : !! NAME
3 : : !! m_invovl
4 : : !!
5 : : !! FUNCTION
6 : : !! Provides functions to invert the overlap matrix S. Used by Chebyshev in PAW
7 : : !! See paper by A. Levitt and M. Torrent for details
8 : : !! S = 1 + projs * s_projs * projs'
9 : : !! S^-1 = 1 + projs * inv_s_projs * projs', with
10 : : !! inv_s_projs = - (s_projs^-1 + projs'*projs)^-1
11 : : !!
12 : : !! COPYRIGHT
13 : : !! Copyright (C) 2013-2026 ABINIT group (AL)
14 : : !! This file is distributed under the terms of the
15 : : !! GNU General Public License, see ~abinit/COPYING
16 : : !! or http://www.gnu.org/copyleft/gpl.txt .
17 : : !!
18 : : !! SOURCE
19 : :
20 : : #if defined HAVE_CONFIG_H
21 : : #include "config.h"
22 : : #endif
23 : :
24 : : #include "abi_common.h"
25 : :
26 : : ! nvtx related macro definition
27 : : #include "nvtx_macros.h"
28 : :
29 : : MODULE m_invovl
30 : :
31 : : use defs_basis
32 : : use m_errors
33 : : use m_xmpi
34 : : use m_xomp
35 : : use m_abicore
36 : : use m_abi_linalg
37 : :
38 : : use defs_abitypes, only : mpi_type
39 : : use m_time, only : timab
40 : : use m_hamiltonian, only : gs_hamiltonian_type
41 : : use m_bandfft_kpt, only : bandfft_kpt_get_ikpt
42 : : use m_pawcprj, only : pawcprj_type, pawcprj_alloc, pawcprj_free, pawcprj_axpby
43 : : use m_gemm_nonlop_projectors, only : gemm_nonlop_use_gemm
44 : : use m_nonlop, only : nonlop
45 : : use m_prep_kgb, only : prep_nonlop
46 : :
47 : : #ifdef HAVE_FC_ISO_C_BINDING
48 : : ! FIXME Don't know what's wrong with GCC when OpenMP GPU Offload is enabled here...
49 : : #ifdef FC_GNU
50 : : use, intrinsic :: iso_c_binding, only : c_int32_t, c_int64_t, c_float, c_double, c_size_t, c_loc
51 : : #else
52 : : use, intrinsic :: iso_c_binding, only : c_ptr, c_int32_t, c_int64_t, c_float, c_double, c_size_t, c_loc
53 : : #endif
54 : : #endif
55 : :
56 : : #if defined(HAVE_GPU_MARKERS)
57 : : use m_nvtx_data
58 : : #endif
59 : :
60 : : #ifdef HAVE_GPU
61 : : use m_gpu_toolbox
62 : : #endif
63 : :
64 : : #ifdef HAVE_KOKKOS
65 : : use m_manage_kokkos, only : add_array_kokkos
66 : : #endif
67 : :
68 : : implicit none
69 : :
70 : : private
71 : :
72 : : !public procedures.
73 : : public :: init_invovl
74 : : public :: make_invovl
75 : : public :: apply_invovl
76 : : public :: destroy_invovl
77 : :
78 : : ! Those routines are here to assess memory requirements
79 : : public :: invovl_ompgpu_work_mem
80 : : public :: invovl_ompgpu_static_mem
81 : : !!***
82 : :
83 : : !!****t* m_invovl/invovl_kpt_type
84 : : !! NAME
85 : : !! invovl_kpt_type
86 : : !!
87 : : !! FUNCTION
88 : : !! Contains information needed to invert the overlap matrix S
89 : : !!
90 : : !! SOURCE
91 : :
92 : : #if defined(HAVE_FC_ISO_C_BINDING) && defined(HAVE_GPU_CUDA)
93 : :
94 : : type, public :: invovl_kpt_type
95 : :
96 : : integer(kind=c_int32_t) :: nprojs
97 : : ! total number of projectors
98 : : ! nlmn for a specific atom = count(indlmn(3,:,itypat)>0)
99 : : ! A value of -1 means that the following arrays are not allocated
100 : :
101 : : real(kind=c_double), allocatable :: gram_projs(:,:,:)
102 : : ! gram_projs(cplx, nprojs, nprojs)
103 : : ! projs' * projs
104 : :
105 : : real(kind=c_double), allocatable :: inv_sij(:,:,:,:)
106 : : ! inv_sij(cplx, lmnmax, lmnmax, ntypat)
107 : : ! inverse of ham%sij
108 : :
109 : : real(kind=c_double), allocatable :: inv_s_approx(:,:,:,:)
110 : : ! inv_s_approx(cplx, lmnmax, lmnmax, ntypat)
111 : : ! preconditionner
112 : :
113 : : end type invovl_kpt_type
114 : :
115 : : !> companion type to invovl_kpt_type to pass data to gpu/cuda
116 : : type, bind(c), public :: invovl_kpt_gpu_type
117 : :
118 : : integer(kind=c_int32_t) :: nprojs
119 : : ! total number of projectors
120 : : ! nlmn for a specific atom = count(indlmn(3,:,itypat)>0)
121 : : ! A value of -1 means that the following arrays are not allocated
122 : :
123 : : type(c_ptr) :: gram_projs
124 : : ! gram_projs(cplx, nprojs, nprojs)
125 : : ! projs' * projs
126 : :
127 : : integer(kind=c_int32_t) :: gram_projs_dim(3)
128 : :
129 : : type(c_ptr) :: inv_sij
130 : : ! inv_sij(cplx, lmnmax, lmnmax, ntypat)
131 : : ! inverse of ham%sij
132 : :
133 : : integer(kind=c_int32_t) :: inv_sij_dim(4)
134 : :
135 : : type(c_ptr) :: inv_s_approx
136 : : ! inv_s_approx(cplx, lmnmax, lmnmax, ntypat)
137 : : ! preconditionner
138 : :
139 : : integer(kind=c_int32_t) :: inv_s_approx_dim(4)
140 : :
141 : : end type invovl_kpt_gpu_type
142 : :
143 : : #else
144 : :
145 : : type, public :: invovl_kpt_type
146 : :
147 : : integer :: nprojs
148 : : ! total number of projectors
149 : : ! nlmn for a specific atom = count(indlmn(3,:,itypat)>0)
150 : : ! A value of -1 means that the following arrays are not allocated
151 : :
152 : : real(dp), allocatable :: gram_projs(:,:,:)
153 : : ! gram_projs(cplx, nprojs, nprojs)
154 : : ! projs' * projs
155 : :
156 : : real(dp), allocatable :: inv_sij(:,:,:,:)
157 : : ! inv_sij(cplx, lmnmax, lmnmax, ntypat)
158 : : ! inverse of ham%sij
159 : :
160 : : real(dp), allocatable :: inv_s_approx(:,:,:,:)
161 : : ! inv_s_approx(cplx, lmnmax, lmnmax, ntypat)
162 : : ! preconditionner
163 : :
164 : : end type invovl_kpt_type
165 : :
166 : : #endif
167 : :
168 : : !!***
169 : :
170 : : integer, parameter :: tim_nonlop = 13
171 : :
172 : : integer, parameter :: &
173 : : & timer_apply_inv_ovl_opernla = 1630, &
174 : : & timer_apply_inv_ovl_opernlb = 1631, &
175 : : & timer_apply_inv_ovl_inv_s = 1632
176 : :
177 : : type(invovl_kpt_type), public,save,allocatable, target :: invovl_kpt(:)
178 : : #ifdef HAVE_OPENMP_OFFLOAD
179 : : real(dp), ABI_CONTIGUOUS pointer :: current_gram_projs(:,:,:)
180 : : real(dp), ABI_CONTIGUOUS pointer :: current_inv_sij(:,:,:,:)
181 : : real(dp), ABI_CONTIGUOUS pointer :: current_inv_s_approx(:,:,:,:)
182 : : real(dp),allocatable, target :: proj_ompgpu(:,:,:)
183 : : real(dp),allocatable, target :: sm1proj_ompgpu(:,:,:)
184 : : real(dp),allocatable, target :: PtPsm1proj_ompgpu(:,:,:)
185 : : !Module variable keeping track of which K-point data is so=tored on GPU
186 : : integer, save :: current_ikpt_in_gpu=-1
187 : : integer, save :: gpu_initialized=0
188 : : integer, save :: mod__cplx=0, mod__nprojs=0
189 : : #endif
190 : :
191 : : #if defined(HAVE_GPU_CUDA)
192 : :
193 : : !> this interface is only useful when gpu is enabled
194 : : !! these functions are defined in 46_manage_gpu/gpu_apply_invovl_inner.cu
195 : : !! these functions are entry point for calling cuda implemented functions
196 : : interface
197 : :
198 : : !> allocate GPU workspace for apply_invovl
199 : : subroutine f_gpu_apply_invovl_inner_alloc(proj_dim, ntypat, realloc) bind(c, name='gpu_apply_invovl_inner_alloc')
200 : : use, intrinsic :: iso_c_binding
201 : : integer(kind=c_int32_t), intent(in) :: proj_dim(3)
202 : : integer(kind=c_int32_t), value, intent(in) :: ntypat
203 : : integer(kind=c_int32_t), value, intent(in) :: realloc
204 : : end subroutine f_gpu_apply_invovl_inner_alloc
205 : :
206 : : !> deallocate GPU workspace for apply_invovl
207 : : subroutine f_gpu_apply_invovl_inner_dealloc() bind(c, name='gpu_apply_invovl_inner_dealloc')
208 : : end subroutine f_gpu_apply_invovl_inner_dealloc
209 : :
210 : : !> allocate GPU workspace for make_invovl (sij and s_approx)
211 : : subroutine f_gpu_apply_invovl_matrix_alloc(cplx, nprojs, lmnmax, ntypat, realloc) bind(c, name='gpu_apply_invovl_matrix_alloc')
212 : : use, intrinsic :: iso_c_binding
213 : : integer(kind=c_int32_t), value, intent(in) :: cplx
214 : : integer(kind=c_int32_t), value, intent(in) :: nprojs
215 : : integer(kind=c_int32_t), value, intent(in) :: ntypat
216 : : integer(kind=c_int32_t), value, intent(in) :: lmnmax
217 : : integer(kind=c_int32_t), value, intent(in) :: realloc
218 : : end subroutine f_gpu_apply_invovl_matrix_alloc
219 : :
220 : : !> deallocate GPU workspace for make_invovl (sij and s_approx)
221 : : subroutine f_gpu_apply_invovl_matrix_dealloc() bind(c, name='gpu_apply_invovl_matrix_dealloc')
222 : : end subroutine f_gpu_apply_invovl_matrix_dealloc
223 : :
224 : : !> init data for GPU
225 : : subroutine f_gpu_init_invovl_data(indlmn_dim, indlmn_ptr) bind(c, name='init_invovl_data')
226 : : use, intrinsic :: iso_c_binding
227 : : integer(kind=c_int32_t), intent(in) :: indlmn_dim(3)
228 : : type(c_ptr) , value :: indlmn_ptr
229 : : end subroutine f_gpu_init_invovl_data
230 : :
231 : : !> upload inverse overlap matrices
232 : : subroutine f_upload_inverse_overlap(invovl_gpu, cplx, nprojs, lmnmax, ntypat) bind(c, name='upload_inverse_overlap')
233 : : use, intrinsic :: iso_c_binding
234 : : import invovl_kpt_gpu_type
235 : : type(invovl_kpt_gpu_type), value, intent(in) :: invovl_gpu
236 : : integer(kind=c_int32_t), value, intent(in) :: cplx
237 : : integer(kind=c_int32_t), value, intent(in) :: nprojs
238 : : integer(kind=c_int32_t), value, intent(in) :: lmnmax
239 : : integer(kind=c_int32_t), value, intent(in) :: ntypat
240 : : end subroutine f_upload_inverse_overlap
241 : :
242 : : !> solve_inner on GPU
243 : : subroutine f_solve_inner_gpu(proj_dim, proj_ptr, sm1proj_ptr, ptp_sm1proj_ptr, &
244 : : & nattyp_dim, nattyp_ptr, ntypat, lmnmax, cplx, block_sliced) bind(c, name='solve_inner_gpu')
245 : :
246 : : use, intrinsic :: iso_c_binding
247 : : integer(kind=c_int32_t), intent(in) :: proj_dim(3)
248 : : type(c_ptr) , value :: proj_ptr
249 : : type(c_ptr) , value :: sm1proj_ptr
250 : : type(c_ptr) , value :: ptp_sm1proj_ptr
251 : : integer(kind=c_int32_t), value, intent(in) :: nattyp_dim
252 : : type(c_ptr) , value :: nattyp_ptr
253 : : integer(kind=c_int32_t), value, intent(in) :: ntypat
254 : : integer(kind=c_int32_t), value, intent(in) :: lmnmax
255 : : integer(kind=c_int32_t), value, intent(in) :: cplx
256 : : integer(kind=c_int32_t), value, intent(in) :: block_sliced
257 : : end subroutine f_solve_inner_gpu
258 : :
259 : : end interface
260 : :
261 : : #endif
262 : :
263 : : CONTAINS
264 : :
265 : : #if defined(HAVE_FC_ISO_C_BINDING) && defined(HAVE_GPU_CUDA)
266 : : !!****f* m_invovl/make_invovl_kpt_gpu
267 : : !! NAME
268 : : !! make_invovl_kpt
269 : : !!
270 : : !! FUNCTION
271 : : !! Create a invovl_pkt_gpu_type from a cpu counter part for cuda interoperability
272 : : !! SOURCE
273 : : function make_invovl_kpt_gpu(invovl) result(invovl_gpu)
274 : : type(invovl_kpt_type), intent(inout),target :: invovl
275 : : type(invovl_kpt_gpu_type) :: invovl_gpu
276 : :
277 : : invovl_gpu%nprojs = invovl%nprojs
278 : :
279 : : invovl_gpu%gram_projs = c_loc(invovl%gram_projs(1,1,1))
280 : : invovl_gpu%gram_projs_dim = (/ &
281 : : & size(invovl%gram_projs,1), &
282 : : & size(invovl%gram_projs,2), &
283 : : & size(invovl%gram_projs,3) &
284 : : & /)
285 : :
286 : : invovl_gpu%inv_sij = c_loc(invovl%inv_sij(1,1,1,1))
287 : : invovl_gpu%inv_sij_dim = (/ &
288 : : & size(invovl%inv_sij,1), &
289 : : & size(invovl%inv_sij,2), &
290 : : & size(invovl%inv_sij,3), &
291 : : & size(invovl%inv_sij,4) &
292 : : & /)
293 : :
294 : : invovl_gpu%inv_s_approx = c_loc(invovl%inv_s_approx(1,1,1,1))
295 : : invovl_gpu%inv_s_approx_dim = (/ &
296 : : & size(invovl%inv_s_approx,1), &
297 : : & size(invovl%inv_s_approx,2), &
298 : : & size(invovl%inv_s_approx,3), &
299 : : & size(invovl%inv_s_approx,4) &
300 : : & /)
301 : :
302 : : end function make_invovl_kpt_gpu
303 : : !!***
304 : : #endif
305 : :
306 : : #ifdef HAVE_OPENMP_OFFLOAD
307 : :
308 : : subroutine alloc_ompgpu_buffers(cplx,nprojs,nspinor,ndat)
309 : : integer,intent(in) :: cplx,nprojs,nspinor,ndat
310 : :
311 : : if(gpu_initialized == 0 .or. mod__cplx/=cplx .or. mod__nprojs/=nprojs) then
312 : :
313 : : if(gpu_initialized==1) then
314 : : ABI_FREE(proj_ompgpu)
315 : : ABI_FREE(sm1proj_ompgpu)
316 : : ABI_FREE(PtPsm1proj_ompgpu)
317 : : end if
318 : :
319 : : ABI_MALLOC(proj_ompgpu, (cplx,nprojs,nspinor*ndat))
320 : : ABI_MALLOC(sm1proj_ompgpu, (cplx,nprojs,nspinor*ndat))
321 : : ABI_MALLOC(PtPsm1proj_ompgpu, (cplx,nprojs,nspinor*ndat))
322 : :
323 : : mod__cplx=cplx; mod__nprojs=nprojs
324 : :
325 : : !FIXME Smater buffer management ?
326 : : !!$OMP TARGET ENTER DATA MAP(alloc:proj_ompgpu,sm1proj_ompgpu,PtPsm1proj_ompgpu)
327 : : gpu_initialized=1
328 : : end if
329 : :
330 : : end subroutine alloc_ompgpu_buffers
331 : :
332 : : subroutine refresh_invovl_ompgpu_kpt(ikpt)
333 : : integer,intent(in) :: ikpt
334 : :
335 : : ! *************************************************************************
336 : :
337 : : if(ikpt < 0) then
338 : : ABI_BUG("Requested GPU upload of a negative K-point index !")
339 : : end if
340 : :
341 : : if(current_ikpt_in_gpu /= -1) then
342 : : !$OMP TARGET EXIT DATA MAP(delete:current_gram_projs)
343 : : !$OMP TARGET EXIT DATA MAP(delete:current_inv_sij)
344 : : !$OMP TARGET EXIT DATA MAP(delete:current_inv_s_approx)
345 : : end if
346 : :
347 : : current_gram_projs => invovl_kpt(ikpt)%gram_projs
348 : : current_inv_sij => invovl_kpt(ikpt)%inv_sij
349 : : current_inv_s_approx => invovl_kpt(ikpt)%inv_s_approx
350 : :
351 : : !$OMP TARGET ENTER DATA MAP(to:current_gram_projs)
352 : : !$OMP TARGET ENTER DATA MAP(to:current_inv_sij)
353 : : !$OMP TARGET ENTER DATA MAP(to:current_inv_s_approx)
354 : : current_ikpt_in_gpu=ikpt
355 : :
356 : : end subroutine refresh_invovl_ompgpu_kpt
357 : : #endif
358 : :
359 : : !!****f* m_invovl/init_invovl
360 : : !! NAME
361 : : !! init_invovl
362 : : !!
363 : : !! FUNCTION
364 : : !! Initalization of the invovl_kpt array
365 : : !!
366 : : !! INPUTS
367 : : !! nkpt= number of k-points
368 : : !!
369 : : !! SOURCE
370 : :
371 : 109 : subroutine init_invovl(nkpt)
372 : :
373 : : integer, intent(in) :: nkpt
374 : : integer :: ikpt
375 : :
376 : : ! *************************************************************************
377 : :
378 [ - + + - : 1633 : ABI_MALLOC(invovl_kpt, (nkpt))
+ - + - -
+ - + - +
+ + ]
379 : : ! TODO add cycling if kpt parallelism
380 [ + + ]: 1415 : do ikpt=1,nkpt
381 : 1415 : invovl_kpt(ikpt)%nprojs = -1
382 : : end do
383 : :
384 : 109 : end subroutine init_invovl
385 : : !!***
386 : :
387 : : !!****f* m_invovl/destroy_invovl_ikpt
388 : : !! NAME
389 : : !! destroy_invovl_ikpt
390 : : !!
391 : : !! FUNCTION
392 : : !! Destruction of the ikpt-th member of invovl array
393 : : !!
394 : : !! INPUTS
395 : : !! ikpt= index of k-point
396 : : !!
397 : : !! SOURCE
398 : 16611 : subroutine destroy_invovl_ikpt(ikpt, gpu_option)
399 : :
400 : : integer, intent(in) :: ikpt
401 : : integer, intent(in) :: gpu_option
402 : :
403 : : ! *************************************************************************
404 : :
405 : : if(gpu_option==ABI_GPU_OPENMP) then
406 : : #ifdef HAVE_OPENMP_OFFLOAD
407 : : if(gpu_initialized==1 .and. current_ikpt_in_gpu == ikpt) then
408 : : !$OMP TARGET EXIT DATA MAP(delete:current_gram_projs)
409 : : !$OMP TARGET EXIT DATA MAP(delete:current_inv_sij)
410 : : !$OMP TARGET EXIT DATA MAP(delete:current_inv_s_approx)
411 : : nullify(current_gram_projs)
412 : : nullify(current_inv_sij)
413 : : nullify(current_inv_s_approx)
414 : : current_ikpt_in_gpu = -1
415 : : !FIXME Smater buffer management ?
416 : : !!$OMP TARGET EXIT DATA MAP(delete:proj_ompgpu,sm1proj_ompgpu,PtPsm1proj_ompgpu)
417 : : ABI_FREE(proj_ompgpu)
418 : : ABI_FREE(sm1proj_ompgpu)
419 : : ABI_FREE(PtPsm1proj_ompgpu)
420 : : gpu_initialized = 0
421 : : end if
422 : : #endif
423 : : end if
424 : :
425 [ - + ]: 16611 : ABI_FREE(invovl_kpt(ikpt)%gram_projs)
426 [ - + ]: 16611 : ABI_FREE(invovl_kpt(ikpt)%inv_sij)
427 [ - + ]: 16611 : ABI_FREE(invovl_kpt(ikpt)%inv_s_approx)
428 : 16611 : invovl_kpt(ikpt)%nprojs = -1
429 : :
430 : : #if defined(HAVE_GPU_CUDA) && defined(HAVE_FC_ISO_C_BINDING)
431 : : if (gpu_option == ABI_GPU_LEGACY .or. gpu_option == ABI_GPU_KOKKOS) then
432 : : call f_gpu_apply_invovl_inner_dealloc()
433 : : call f_gpu_apply_invovl_matrix_dealloc()
434 : : end if
435 : : #endif
436 : :
437 : 16611 : end subroutine destroy_invovl_ikpt
438 : : !!***
439 : :
440 : : !!****f* m_invovl/destroy_invovl
441 : : !! NAME
442 : : !! destroy_invovl
443 : : !!
444 : : !! FUNCTION
445 : : !! Destruction of the invovl_kpt array
446 : : !!
447 : : !! INPUTS
448 : : !! nkpt= number of k-points
449 : : !!
450 : : !! SOURCE
451 : 108 : subroutine destroy_invovl(nkpt, gpu_option)
452 : :
453 : : integer, intent(in) :: nkpt
454 : : integer, intent(in) :: gpu_option
455 : : integer :: ikpt
456 : :
457 : : ! *************************************************************************
458 : :
459 : : ! TODO add cycling if kpt parallelism
460 [ + + ]: 1386 : do ikpt=1,nkpt
461 [ + + ]: 1278 : if(invovl_kpt(ikpt)%nprojs == -1) then
462 : : ! write(0, *) 'ERROR invovl_kpt is unallocated'
463 : : cycle
464 : : end if
465 : 1386 : call destroy_invovl_ikpt(ikpt, gpu_option)
466 : : end do
467 : :
468 [ + - + + : 1386 : ABI_FREE(invovl_kpt)
- + - + -
+ - + ]
469 : :
470 : 108 : end subroutine destroy_invovl
471 : : !!***
472 : :
473 : : !!****f* m_invovl/make_invovl
474 : : !! NAME
475 : : !! make_invovl
476 : : !!
477 : : !! FUNCTION
478 : : !! Builds of the invovl structure
479 : : !!
480 : : !! INPUTS
481 : : !!
482 : : !! SOURCE
483 : :
484 : 16639 : subroutine make_invovl(ham, dimffnl, ffnl, ph3d, mpi_enreg)
485 : :
486 : : use m_abi_linalg
487 : :
488 : : type(gs_hamiltonian_type),intent(in), target :: ham
489 : : integer, intent(in) :: dimffnl
490 : : real(dp),intent(in) :: ffnl(ham%npw_k,dimffnl,ham%lmnmax,ham%ntypat)
491 : : real(dp),intent(in) :: ph3d(2,ham%npw_k,ham%matblk)
492 : : type(mpi_type) :: mpi_enreg
493 : :
494 : 33278 : real(dp) :: atom_projs(2, ham%npw_k, ham%lmnmax)
495 : 33278 : real(dp) :: temp(ham%npw_k)
496 : 16639 : complex(dp), allocatable :: work(:)
497 : 16639 : real(dp), allocatable,target :: projs(:,:,:)
498 : 16639 : real(dp), allocatable :: gram_proj(:,:,:)
499 : 16639 : integer, allocatable :: ipiv(:)
500 : :
501 : : integer :: itypat, ilmn, nlmn, jlmn, ia, iaph3d, shift
502 : : integer :: il, ilm, jlm, ipw, info, ierr, cplx
503 : : integer :: ikpt_this_proc,cplex_dij
504 : : logical :: parity
505 : : real(dp) :: tsec(2)
506 : : #if defined(HAVE_FC_ISO_C_BINDING) && defined(HAVE_GPU_CUDA)
507 : : character(len=500) :: message
508 : : #endif
509 : : character :: blas_transpose
510 : :
511 : : type(invovl_kpt_type), pointer :: invovl
512 : : #if defined(HAVE_FC_ISO_C_BINDING) && defined(HAVE_GPU_CUDA)
513 : : type(invovl_kpt_gpu_type) :: invovl_gpu
514 : : #endif
515 : 33278 : integer :: array_nprojs_pp(mpi_enreg%nproc_fft)
516 : : integer :: iproc, slice_size
517 : 16639 : real(dp), allocatable :: gramwork(:,:,:)
518 : : #ifdef HAVE_OPENMP_OFFLOAD
519 : : real(dp), ABI_CONTIGUOUS pointer :: invovl_gram_projs(:,:,:)
520 : : #endif
521 : :
522 : : integer, parameter :: timer_mkinvovl = 1620, timer_mkinvovl_build_d = 1621, timer_mkinvovl_build_ptp = 1622
523 : :
524 : : ! *************************************************************************
525 : :
526 : : !! S = 1 + PDP', so
527 : : !! S^-1 = 1 + P inv_s_projs P', with
528 : : !! inv_s_projs = - (D^-1 + P'*P)^-1
529 : :
530 : : ABI_NVTX_START_RANGE(NVTX_MAKE_INVOVL)
531 : :
532 [ + + ]: 16639 : if(ham%istwf_k == 1) then
533 : 16511 : cplx = 2
534 : 16511 : blas_transpose = 'c'
535 : : else
536 : 128 : cplx = 1
537 : 128 : blas_transpose = 't'
538 : : end if
539 : :
540 : 16639 : ikpt_this_proc=bandfft_kpt_get_ikpt()
541 : 16639 : invovl => invovl_kpt(ikpt_this_proc)
542 : :
543 [ + + ]: 16639 : if(invovl%nprojs /= -1) then
544 : : ! We have been here before, cleanup before remaking
545 : 15736 : call destroy_invovl_ikpt(ikpt_this_proc, ham%gpu_option)
546 : : end if
547 : :
548 : 16639 : iaph3d = 1
549 : :
550 : 16639 : call timab(timer_mkinvovl,1,tsec)
551 : 16639 : call timab(timer_mkinvovl_build_d,1,tsec)
552 : :
553 : : ! build nprojs
554 : 16639 : invovl%nprojs = 0
555 [ + + ]: 33736 : do itypat=1,ham%ntypat
556 [ + + + + ]: 170882 : invovl%nprojs = invovl%nprojs + count(ham%indlmn(3,:,itypat)>0)*ham%nattyp(itypat)
557 : : end do
558 : :
559 [ - + + - : 66556 : ABI_MALLOC(projs, (2, ham%npw_k, invovl%nprojs))
- + + - +
- + - - +
- + ]
560 [ - + + - : 99834 : ABI_MALLOC(invovl%inv_sij, (cplx, ham%lmnmax, ham%lmnmax, ham%ntypat))
- + + - -
+ + - - +
+ - + - +
- - + - +
- + ]
561 [ - + + - : 99834 : ABI_MALLOC(invovl%inv_s_approx, (cplx, ham%lmnmax, ham%lmnmax, ham%ntypat))
- + + - -
+ + - - +
+ - + - +
- - + - +
- + ]
562 : : ! workspace for inversion
563 [ - + + - : 49917 : ABI_MALLOC(ipiv, (ham%lmnmax))
+ - + - -
+ - + ]
564 [ + - - + : 49917 : ABI_MALLOC(work, (64*ham%lmnmax))
+ - + - +
- - + -
+ ]
565 : :
566 [ + + + + : 132942015 : projs = zero
+ + ]
567 [ + + + + : 3465982 : invovl%inv_sij = zero
+ + + + ]
568 [ + + + + : 3465982 : invovl%inv_s_approx = zero
+ + + + ]
569 : :
570 : : shift = 0
571 [ + + ]: 33736 : do itypat = 1, ham%ntypat
572 [ + + + + ]: 154243 : nlmn = count(ham%indlmn(3,:,itypat)>0)
573 [ + + ]: 17097 : if (size(ham%sij(:,itypat))==ham%lmnmax*(ham%lmnmax+1)/2) then
574 : : cplex_dij = 1
575 [ - + ]: 176 : else if (size(ham%sij(:,itypat))==ham%lmnmax*(ham%lmnmax+1)) then
576 : : cplex_dij = 2
577 : : else
578 : 0 : ABI_ERROR('sij size not recognize')
579 : : end if
580 : : !! unpack ham%sij into inv_sij
581 [ + + ]: 154223 : do jlmn = 1, nlmn
582 [ + + ]: 137126 : if (cplex_dij==1) then
583 : 135718 : invovl%inv_sij(1, jlmn, jlmn, itypat) = ham%sij(jlmn*(jlmn-1)/2 + jlmn, itypat)
584 : : else
585 : 1408 : invovl%inv_sij(1, jlmn, jlmn, itypat) = ham%sij(jlmn*(jlmn-1) + 2*jlmn-1, itypat)
586 : : end if
587 [ + + ]: 637314 : do ilmn = 1, jlmn-1
588 [ + + ]: 620217 : if (cplex_dij==1) then
589 : 478163 : invovl%inv_sij(1, ilmn, jlmn, itypat) = ham%sij(jlmn*(jlmn-1)/2 + ilmn, itypat)
590 : 478163 : invovl%inv_sij(1, jlmn, ilmn, itypat) = ham%sij(jlmn*(jlmn-1)/2 + ilmn, itypat)
591 : : else
592 : 4928 : invovl%inv_sij(1, ilmn, jlmn, itypat) = ham%sij(jlmn*(jlmn-1) + 2*ilmn-1, itypat)
593 : 4928 : invovl%inv_sij(1, jlmn, ilmn, itypat) = ham%sij(jlmn*(jlmn-1) + 2*ilmn-1, itypat)
594 : :
595 : : end if
596 : : end do
597 : : end do
598 : :
599 : : ! Invert sij
600 [ + + ]: 17097 : if(cplx == 2) then
601 : 16841 : call ZHETRF('U', nlmn, invovl%inv_sij(:,:,:,itypat), ham%lmnmax, ipiv, work, (64*nlmn), info)
602 : 16841 : call ZHETRI('U', nlmn, invovl%inv_sij(:,:,:,itypat), ham%lmnmax, ipiv, work, info)
603 : : else
604 : 256 : call DSYTRF('U', nlmn, invovl%inv_sij(:,:,:,itypat), ham%lmnmax, ipiv, work, (64*nlmn), info)
605 : 256 : call DSYTRI('U', nlmn, invovl%inv_sij(:,:,:,itypat), ham%lmnmax, ipiv, work, info)
606 : : end if
607 : : ! complete the matrix
608 [ + + ]: 154223 : do ilm=1, nlmn
609 [ + + ]: 637314 : do jlm=1, ilm-1
610 : 620217 : invovl%inv_sij(1,ilm,jlm,itypat) = invovl%inv_sij(1,jlm,ilm,itypat)
611 : : end do
612 : : end do
613 : :
614 : : !! loop on atoms to build atom_projs and fill projs, s_projs
615 [ + + ]: 67339 : do ia = 1, ham%nattyp(itypat)
616 : :
617 : : !! build atom_projs, from opernlb
618 : : !! P = 4pi/sqrt(ucvol)* conj(diag(ph3d)) * ffnl * diag(parity), with parity = (-i)^l
619 [ + + + + : 133024979 : atom_projs(:,:,:) = zero
+ + ]
620 : :
621 : : ! start from 4pi/sqrt(ucvol)*ffnl
622 : : ! atom_projs(1, :, 1:nlmn) = four_pi/sqrt(ham%ucvol) * ffnl(:, 1, 1:nlmn)
623 : : ! TODO vectorize (DCOPY with stride)
624 [ + + ]: 5074316 : do ipw=1, ham%npw_k
625 [ + + ]: 49291890 : atom_projs(1,ipw, 1:nlmn) = four_pi/sqrt(ham%ucvol) * ffnl(ipw, 1, 1:nlmn, itypat)
626 : : end do
627 : :
628 : : ! multiply by (-i)^l
629 [ + + ]: 306257 : do ilmn=1,nlmn
630 : 272654 : il=mod(ham%indlmn(1,ilmn, itypat),4);
631 : 272654 : parity=(mod(il,2)==0)
632 [ + + ]: 272654 : if (il>1) then
633 : : ! multiply by -1
634 [ + + + + ]: 11679440 : atom_projs(:,:,ilmn) = -atom_projs(:,:,ilmn)
635 : : end if
636 [ + + ]: 306257 : if(.not. parity) then
637 : : ! multiply by -i
638 [ + + ]: 30445896 : temp = atom_projs(2,:,ilmn)
639 [ + + ]: 30445896 : atom_projs(2,:,ilmn) = -atom_projs(1,:,ilmn)
640 [ + + ]: 30445896 : atom_projs(1,:,ilmn) = temp
641 : : end if
642 : : end do
643 : :
644 : : ! multiply by conj(ph3d)
645 [ + + ]: 306257 : do ilmn=1,nlmn
646 [ + + ]: 44490228 : temp = atom_projs(1, :, ilmn)
647 [ + + ]: 44490228 : atom_projs(1, :, ilmn) = atom_projs(1, :, ilmn) * ph3d(1, :, iaph3d) + atom_projs(2, :, ilmn) * ph3d(2, :, iaph3d)
648 [ + + ]: 44523831 : atom_projs(2, :, ilmn) = atom_projs(2, :, ilmn) * ph3d(1, :, iaph3d) - temp * ph3d(2, :, iaph3d)
649 : : end do
650 : :
651 : : ! me_g0 trick
652 [ + + + + ]: 33603 : if(ham%istwf_k == 2 .and. mpi_enreg%me_g0 == 1) then
653 [ + + ]: 126 : atom_projs(1,1,:) = atom_projs(1,1,:) / sqrt2
654 [ + + ]: 126 : atom_projs(2,1,:) = zero
655 : : end if
656 [ + + ]: 33603 : if(ham%istwf_k > 1) then
657 [ + + + + : 198240 : atom_projs(:,:,:) = atom_projs(:,:,:) * sqrt2
+ + ]
658 : : end if
659 : :
660 : :
661 : : !! atom_projs and typat_s_projs are built, copy them to projs and inv_s_projs
662 [ + + + + : 132958979 : projs(:, :, shift+1:shift+nlmn) = atom_projs(:, :, 1:nlmn)
+ + ]
663 : 33603 : shift = shift + nlmn
664 : :
665 : : ! build inv_s_approx = (D^-1+PtP)^-1 restricted to a single atom block
666 : : ! can be optimized (real, build directly from ffnl)
667 [ + + ]: 33603 : if(ia == 1) then
668 : : ! D^-1
669 [ + + + + ]: 1257487 : invovl%inv_s_approx(1, :, :, itypat) = invovl%inv_sij(1, :, :, itypat)
670 : : ! + PtP
671 [ - + + - : 85445 : ABI_MALLOC(gram_proj, (cplx, nlmn, nlmn))
- + + - -
+ + - + -
+ - - + -
+ - + ]
672 : : call abi_xgemm(blas_transpose,'N', nlmn, nlmn, (3-cplx)*ham%npw_k, cone, atom_projs(:,:,1), (3-cplx)*ham%npw_k, &
673 : 17089 : & atom_projs(:,:,1), (3-cplx)*ham%npw_k, czero, gram_proj(:,:,1), nlmn,x_cplx=cplx)
674 : 17089 : call xmpi_sum(gram_proj,mpi_enreg%comm_bandspinorfft,ierr)
675 [ + + + + : 3446155 : invovl%inv_s_approx(:,1:nlmn,1:nlmn,itypat) = invovl%inv_s_approx(:,1:nlmn,1:nlmn,itypat) + gram_proj(:,:,:)
+ + ]
676 [ - + ]: 17089 : ABI_FREE(gram_proj)
677 : : ! ^-1
678 [ + + ]: 17089 : if(cplx == 2) then
679 : 16833 : call ZHETRF('U', nlmn, invovl%inv_s_approx(:,:,:,itypat), ham%lmnmax, ipiv, work, (64*nlmn), info)
680 : 16833 : call ZHETRI('U', nlmn, invovl%inv_s_approx(:,:,:,itypat), ham%lmnmax, ipiv, work, info)
681 : : else
682 : 256 : call DSYTRF('U', nlmn, invovl%inv_s_approx(:,:,:,itypat), ham%lmnmax, ipiv, work, (64*nlmn), info)
683 : 256 : call DSYTRI('U', nlmn, invovl%inv_s_approx(:,:,:,itypat), ham%lmnmax, ipiv, work, info)
684 : : end if
685 : : ! complete lower triangle of matrix
686 [ + + ]: 171240 : do ilm=1, nlmn
687 [ + + ]: 637018 : do jlm=1, ilm-1
688 : 482867 : invovl%inv_s_approx(1, ilm, jlm, itypat) = invovl%inv_s_approx(1, jlm, ilm, itypat)
689 [ + + ]: 619929 : if(cplx == 2) then
690 : 475699 : invovl%inv_s_approx(2, ilm, jlm, itypat) = -invovl%inv_s_approx(2, jlm, ilm, itypat)
691 : : end if
692 : : end do
693 : : end do
694 : : end if
695 : :
696 : 50700 : iaph3d = iaph3d + 1
697 : : end do
698 : : end do
699 [ - + ]: 16639 : ABI_FREE(ipiv)
700 [ - + ]: 16639 : ABI_FREE(work)
701 : :
702 : 16639 : call timab(timer_mkinvovl_build_d, 2, tsec)
703 : 16639 : call timab(timer_mkinvovl_build_ptp, 1, tsec)
704 : :
705 : : ! Compute P'P one column slice at a time (might be too big to fit in one proc)
706 [ + + ]: 16639 : if(mpi_enreg%paral_kgb == 1) then
707 : : ! Split the work evenly the fft processors
708 [ + + ]: 13464 : array_nprojs_pp(:) = invovl%nprojs / mpi_enreg%nproc_fft
709 : : ! not enough work, there's MOD(nprojs,mpi_enreg%nproc_fft) tasks left
710 : : ! assign them to the first ones
711 [ - + ]: 6728 : array_nprojs_pp(1:MOD(invovl%nprojs,mpi_enreg%nproc_fft)) = array_nprojs_pp(1:MOD(invovl%nprojs,mpi_enreg%nproc_fft)) + 1
712 : : else
713 [ + + ]: 19822 : array_nprojs_pp = invovl%nprojs
714 : : end if
715 [ - + + - : 83195 : ABI_MALLOC(invovl%gram_projs, (cplx,invovl%nprojs,array_nprojs_pp(mpi_enreg%me_fft+1)))
- + + - -
+ + - + -
+ - - + -
+ - + ]
716 : 16639 : shift = 0
717 [ - + - - ]: 16639 : if (ham%gpu_option==ABI_GPU_OPENMP .and. mpi_enreg%nproc_fft==1) then
718 : : #ifdef HAVE_OPENMP_OFFLOAD
719 : : ! compute gram_projs in one GEMM, only one FFT proc expected in GPU mode
720 : : slice_size = array_nprojs_pp(1)
721 : : invovl_gram_projs => invovl%gram_projs
722 : : !$OMP TARGET ENTER DATA MAP(alloc:invovl_gram_projs)
723 : : !$OMP TARGET ENTER DATA MAP(to:projs)
724 : :
725 : : call abi_xgemm(blas_transpose,'N', invovl%nprojs, slice_size, (3-cplx)*ham%npw_k, cone, &
726 : : & projs, (3-cplx)*ham%npw_k, &
727 : : & projs, (3-cplx)*ham%npw_k, czero, &
728 : : & invovl_gram_projs, invovl%nprojs, &
729 : : & x_cplx=cplx, gpu_option=ham%gpu_option)
730 : : call xmpi_sum(invovl%gram_projs,mpi_enreg%comm_band,ierr,use_omp_map=.true.)
731 : : !$OMP TARGET EXIT DATA MAP(from:invovl_gram_projs)
732 : : !$OMP TARGET EXIT DATA MAP(delete:projs)
733 : : #endif
734 : : else
735 [ + + ]: 33286 : do iproc = 1, mpi_enreg%nproc_fft
736 : : ! compute local contribution to slice iproc of gram_projs
737 : 16647 : slice_size = array_nprojs_pp(iproc)
738 [ - + + - : 83235 : ABI_MALLOC(gramwork, (cplx,invovl%nprojs,slice_size))
- + + - -
+ + - + -
+ - - + -
+ - + ]
739 : : call abi_xgemm(blas_transpose,'N', invovl%nprojs, slice_size, (3-cplx)*ham%npw_k, cone,&
740 : : & projs(:,:,1), (3-cplx)*ham%npw_k, &
741 : : & projs(:, :, shift+1), (3-cplx)*ham%npw_k, czero, &
742 : : & gramwork(:,:,1), invovl%nprojs,&
743 : 16647 : & x_cplx=cplx)
744 : 16647 : shift = shift + slice_size
745 : : ! reduce on proc i
746 : 16647 : call xmpi_sum_master(gramwork, iproc-1, mpi_enreg%comm_fft, ierr)
747 [ + + ]: 16647 : if(iproc == mpi_enreg%me_fft+1) then
748 [ + - + - : 21888376 : invovl%gram_projs = gramwork
+ - - + -
- - - - -
- - + + +
+ + + ]
749 : : end if
750 [ - + ]: 33286 : ABI_FREE(gramwork)
751 : : end do
752 : 16639 : call xmpi_sum(invovl%gram_projs,mpi_enreg%comm_band,ierr)
753 : : end if
754 : :
755 : 16639 : call timab(timer_mkinvovl_build_ptp, 2, tsec)
756 : 16639 : call timab(timer_mkinvovl,2,tsec)
757 : :
758 [ - + ]: 16639 : ABI_FREE(projs)
759 : :
760 : : #if defined(HAVE_FC_ISO_C_BINDING) && defined(HAVE_GPU_CUDA)
761 : :
762 : : ! upload inverse overlap matrices (sij and s_approx) to GPU memory
763 : : if (ham%gpu_option==ABI_GPU_LEGACY .or. ham%gpu_option==ABI_GPU_KOKKOS) then
764 : : ! allocate memory for sij and s_approx on GPU
765 : : write(message,'(a,a,i12,a,a,i6,a,a,i6,a,a,es12.4,a)') &
766 : : & 'Allocate GPU memory for inverse overlap computations (sij and s_approx) : ',&
767 : : & 'nprojs=',invovl%nprojs,ch10,&
768 : : & 'nlmnax=',ham%lmnmax,ch10,&
769 : : & 'ntypat=',ham%ntypat,ch10,&
770 : : & 'gram_projs_gpu_size (GBytes)=',1e-9*cplx*invovl%nprojs*invovl%nprojs*dp,ch10
771 : : call wrtout(std_out,message,'COLL')
772 : : call f_gpu_apply_invovl_matrix_alloc(cplx, invovl%nprojs, ham%lmnmax, ham%ntypat, 0)
773 : :
774 : : invovl_gpu = make_invovl_kpt_gpu(invovl)
775 : : call f_upload_inverse_overlap(invovl_gpu, cplx, invovl%nprojs, ham%lmnmax, ham%ntypat)
776 : : write(message,*) 'Invovl uploaded to GPU memory'
777 : : call wrtout(std_out,message,'COLL')
778 : : end if
779 : :
780 : : #endif
781 : :
782 : : #ifdef HAVE_OPENMP_OFFLOAD
783 : : if (ham%gpu_option==ABI_GPU_OPENMP) then
784 : : call refresh_invovl_ompgpu_kpt(ikpt_this_proc)
785 : : end if
786 : : #endif
787 : :
788 : : ! LB-10/06/24: This message is too verbose on some cases (for example many k-points)
789 : : ! write(message,*) 'Invovl built'
790 : : ! call wrtout(std_out,message,'COLL')
791 : :
792 : : ABI_NVTX_END_RANGE()
793 : :
794 [ - + - + ]: 16639 : end subroutine make_invovl
795 : : !!***
796 : :
797 : : !!****f* m_invovl/apply_invovl
798 : : !! NAME
799 : : !! apply_invovl
800 : : !!
801 : : !! FUNCTION
802 : : !! Applies the inverse of the overlap matrix to cwavef
803 : : !!
804 : : !! INPUTS
805 : : !!
806 : : !! SOURCE
807 : :
808 [ - + ]: 160806 : subroutine apply_invovl(ham, cwavef, sm1cwavef, cwaveprj, npw, ndat, mpi_enreg, nspinor, block_sliced)
809 : :
810 : : #if defined(HAVE_FC_ISO_C_BINDING) && defined(HAVE_GPU_CUDA)
811 : : use, intrinsic :: iso_c_binding
812 : : #endif
813 : :
814 : : ! args
815 : : type(gs_hamiltonian_type), intent(in), target :: ham
816 : : integer, intent(in) :: npw, ndat
817 : : integer, intent(in) :: nspinor
818 : : integer, intent(in) :: block_sliced
819 : : real(dp), intent(inout), target :: cwavef(2, npw*nspinor*ndat) ! TODO should be in, fix nonlop
820 : : type(mpi_type) :: mpi_enreg
821 : : real(dp), intent(inout), target :: sm1cwavef(2, npw*nspinor*ndat)
822 : : type(pawcprj_type), intent(inout) :: cwaveprj(:,:)
823 : :
824 : 160806 : real(dp),allocatable, target :: proj(:,:,:), sm1proj(:,:,:), PtPsm1proj(:,:,:)
825 : :
826 : : ! used to pass proj dimensions to cuda
827 : : integer(kind=c_int32_t) :: proj_dim(3)
828 : : integer(kind=c_int32_t) :: nattyp_dim
829 : : integer(kind=c_int32_t) :: indlmn_dim(3)
830 : : #if defined(HAVE_GPU_CUDA) && defined(HAVE_KOKKOS) && defined(HAVE_YAKL)
831 : : integer(kind=c_int32_t) :: cwavef_size
832 : : #endif
833 : :
834 : : integer :: idat, iatom, nlmn, shift
835 : : real(dp) :: tsec(2)
836 : :
837 : : integer :: choice, cpopt, paw_opt , cplx
838 : : character :: blas_transpose
839 : 160806 : type(pawcprj_type),allocatable :: cwaveprj_in(:,:)
840 : :
841 : : integer :: ikpt_this_proc
842 : : ! dummies
843 : 321612 : real(dp) :: enlout(ndat), lambda_block(1), gvnlxc(1,1)
844 : : integer, parameter :: nnlout = 0, idir = 0, signs = 2
845 : :
846 : : type(invovl_kpt_type), pointer :: invovl
847 : :
848 : : ! *************************************************************************
849 : :
850 : : if(ham%gpu_option==ABI_GPU_OPENMP) then
851 : : #ifdef HAVE_OPENMP_OFFLOAD
852 : : call apply_invovl_ompgpu(ham, cwavef, sm1cwavef, cwaveprj, npw, ndat, mpi_enreg, nspinor, block_sliced)
853 : : return
854 : : #endif
855 : : end if
856 : :
857 : : ABI_NVTX_START_RANGE(NVTX_INVOVL_PREP)
858 : 160806 : ikpt_this_proc=bandfft_kpt_get_ikpt()
859 : 160806 : invovl => invovl_kpt(ikpt_this_proc)
860 : :
861 [ + + ]: 160806 : if(ham%istwf_k == 1) then
862 : 143995 : cplx = 2
863 : 143995 : blas_transpose = 'c'
864 : : else
865 : 16811 : cplx = 1
866 : 16811 : blas_transpose = 't'
867 : : end if
868 : :
869 [ - + + - : 804030 : ABI_MALLOC(proj, (cplx,invovl%nprojs,nspinor*ndat))
- + + - -
+ + - + -
+ - - + -
+ ]
870 [ - + + - : 643224 : ABI_MALLOC(sm1proj, (cplx,invovl%nprojs,nspinor*ndat))
- + + - -
+ + - - +
- + ]
871 [ - + + - : 643224 : ABI_MALLOC(PtPsm1proj, (cplx,invovl%nprojs,nspinor*ndat))
- + + - -
+ + - - +
- + ]
872 [ + + + + : 65308502 : proj = zero
+ + ]
873 [ + + + + : 65308502 : sm1proj = zero
+ + ]
874 [ + + + + : 65308502 : PtPsm1proj = zero
+ + ]
875 : :
876 : 160806 : proj_dim = (/ size(proj,1), size(proj,2), size(proj,3) /)
877 : :
878 : 160806 : nattyp_dim = size(ham%nattyp)
879 : :
880 : 160806 : indlmn_dim = (/ size(ham%indlmn,1), size(ham%indlmn,2), size(ham%indlmn,3) /)
881 : :
882 : : #if defined(HAVE_GPU_CUDA) && defined(HAVE_FC_ISO_C_BINDING)
883 : :
884 : : !! memory allocation of data used in solve_inner_gpu
885 : : !! note : this is actually done only once
886 : : if (ham%gpu_option==ABI_GPU_LEGACY .or. ham%gpu_option==ABI_GPU_KOKKOS) then
887 : :
888 : : #ifdef DEBUG_VERBOSE_GPU
889 : : if(xmpi_comm_rank(xmpi_world) == 0) then
890 : : call check_gpu_mem("gpu_apply_invovl_inner_alloc begin")
891 : : end if
892 : : #endif
893 : :
894 : : ! make sure to use sizes from apply_invovl
895 : : call f_gpu_apply_invovl_inner_alloc(proj_dim, ham%ntypat, 0)
896 : :
897 : : #ifdef DEBUG_VERBOSE_GPU
898 : : if(xmpi_comm_rank(xmpi_world) == 0) then
899 : : call check_gpu_mem("gpu_apply_invovl_inner_alloc end")
900 : : end if
901 : : #endif
902 : :
903 : : ! TODO find a better place to put that initialization
904 : : call f_gpu_init_invovl_data(indlmn_dim, c_loc(ham%indlmn(1,1,1)))
905 : :
906 : : end if
907 : :
908 : : #endif
909 : :
910 : :
911 : 160806 : call timab(timer_apply_inv_ovl_opernla, 1, tsec)
912 : :
913 : : ! cwaveprj may be dummy or unused if gemm nonlop is turned on
914 [ + + + + : 482418 : if((.not. gemm_nonlop_use_gemm) .or. size(cwaveprj) > 1) then
+ + ]
915 [ - + + - : 4200552 : ABI_MALLOC(cwaveprj_in, (ham%natom,nspinor*ndat))
- + + - +
- + - - +
- + - + +
+ + + ]
916 : 160518 : call pawcprj_alloc(cwaveprj_in,0,ham%dimcprj)
917 : : else
918 [ - + - + : 864 : ABI_MALLOC(cwaveprj_in, (1,1))
+ + + + ]
919 : 288 : call pawcprj_alloc(cwaveprj_in,0,(/1/))
920 : : end if
921 : : ABI_NVTX_END_RANGE()
922 : :
923 : : ! get the cprj
924 : : ABI_NVTX_START_RANGE(NVTX_INVOVL_NONLOP1)
925 : 160806 : choice = 0 ! only compute cprj, nothing else
926 : 160806 : cpopt = 0 ! compute and save cprj
927 : 160806 : paw_opt = 3 ! S nonlocal operator
928 [ + + ]: 160806 : if (mpi_enreg%paral_kgb==1) then
929 : : call prep_nonlop(choice,cpopt,cwaveprj_in,enlout,ham,idir,lambda_block,ndat,mpi_enreg,&
930 : : & nnlout,paw_opt,signs,sm1cwavef,tim_nonlop,cwavef,gvnlxc,&
931 : : & already_transposed=.true.,&
932 : : & gpu_option=ham%gpu_option,&
933 : 62460 : & vectproj=proj)
934 : : else
935 : : call nonlop(choice,cpopt,cwaveprj_in,enlout,ham,idir,lambda_block,mpi_enreg,ndat,&
936 : 98346 : & nnlout,paw_opt,signs,sm1cwavef,tim_nonlop,cwavef,gvnlxc,vectproj=proj)
937 : : end if
938 : : ABI_NVTX_END_RANGE()
939 : :
940 : 160806 : call timab(timer_apply_inv_ovl_opernla, 2, tsec)
941 : 160806 : call timab(timer_apply_inv_ovl_inv_s, 1, tsec)
942 : :
943 : : ! If using GEMM nonlop, proj array is used directly instead of writing in cwaveprj_in content, so skip this copy
944 [ + + ]: 160806 : if(.not. gemm_nonlop_use_gemm) then
945 : : ! copy cwaveprj_in to proj(:,:)
946 [ + + ]: 1288502 : do idat=1, ndat*nspinor
947 : 1128272 : shift = 0
948 [ + + ]: 3713526 : do iatom = 1, ham%natom
949 : 2425024 : nlmn = cwaveprj_in(iatom, idat)%nlmn
950 [ + + + + ]: 66317440 : proj(1:cplx, shift+1:shift+nlmn, idat) = cwaveprj_in(iatom, idat)%cp(1:cplx, 1:nlmn)
951 : 3553296 : shift = shift + nlmn
952 : : end do
953 : : end do
954 : : end if
955 : :
956 : : !multiply by S^1
957 : : ABI_NVTX_START_RANGE(NVTX_INVOVL_INNER)
958 : : ! TODO : when solve_inner_gpu is ready, update the following to activate GPU computation
959 [ + - ]: 160806 : if (ham%gpu_option == ABI_GPU_LEGACY .or. ham%gpu_option==ABI_GPU_KOKKOS) then
960 : :
961 : : #if defined(HAVE_FC_ISO_C_BINDING) && defined(HAVE_GPU_CUDA)
962 : :
963 : : if (mpi_enreg%nproc_fft /= 1) then
964 : : ABI_ERROR("[66_wfs/m_invovl.F90:apply_invovl] nproc_fft must be 1, when GPU/CUDA is activated")
965 : : end if
966 : :
967 : : call f_solve_inner_gpu(proj_dim, c_loc(proj(1,1,1)), &
968 : : & c_loc(sm1proj(1,1,1)), c_loc(PtPsm1proj(1,1,1)), &
969 : : & nattyp_dim, c_loc(ham%nattyp(1)), ham%ntypat, &
970 : : & ham%lmnmax, cplx, block_sliced)
971 : :
972 : : #endif
973 : :
974 : : else
975 : :
976 : 160806 : call solve_inner(invovl, ham, cplx, mpi_enreg, proj, ndat*nspinor, sm1proj, PtPsm1proj, block_sliced)
977 [ + + + + : 65308502 : sm1proj = - sm1proj
+ + ]
978 [ + + + + : 65308502 : PtPsm1proj = - PtPsm1proj
+ + ]
979 : : end if
980 : :
981 : : ABI_NVTX_END_RANGE()
982 : :
983 : : ! If using GEMM nonlop, sm1proj array is used directly instead of reading cwaveprj content, so skip this copy
984 [ + + ]: 160806 : if(.not. gemm_nonlop_use_gemm) then
985 : : ! copy sm1proj to cwaveprj(:,:)
986 [ + + ]: 1288502 : do idat=1, ndat*nspinor
987 : 1128272 : shift = 0
988 [ + + ]: 3713526 : do iatom = 1, ham%natom
989 : 2425024 : nlmn = cwaveprj(iatom, idat)%nlmn
990 [ + + + + ]: 66317440 : cwaveprj(iatom, idat)%cp(1:cplx, 1:nlmn) = sm1proj(1:cplx, shift+1:shift+nlmn, idat)
991 : 3553296 : shift = shift + nlmn
992 : : end do
993 : : end do
994 : : end if
995 : 160806 : call timab(timer_apply_inv_ovl_inv_s, 2, tsec)
996 : 160806 : call timab(timer_apply_inv_ovl_opernlb, 1, tsec)
997 : :
998 : : ! get the corresponding wf
999 : : ABI_NVTX_START_RANGE(NVTX_INVOVL_NONLOP2)
1000 : 160806 : cpopt = 2 ! reuse cprj
1001 : 160806 : choice = 7 ! get wf from cprj, without the application of S
1002 : : paw_opt = 3
1003 [ + + ]: 160806 : if (mpi_enreg%paral_kgb==1) then
1004 : : call prep_nonlop(choice,cpopt,cwaveprj,enlout,ham,idir,lambda_block,ndat,mpi_enreg,nnlout,&
1005 : : & paw_opt,signs,sm1cwavef,tim_nonlop,cwavef,gvnlxc,already_transposed=.true.,&
1006 : 62460 : & gpu_option=ham%gpu_option,vectproj=sm1proj)
1007 : : else
1008 : : call nonlop(choice,cpopt,cwaveprj,enlout,ham,idir,lambda_block,mpi_enreg,ndat,nnlout,paw_opt,&
1009 : 98346 : & signs,sm1cwavef,tim_nonlop,cwavef,gvnlxc,vectproj=sm1proj)
1010 : : end if
1011 : : ABI_NVTX_END_RANGE()
1012 : :
1013 : 160806 : call timab(timer_apply_inv_ovl_opernlb, 2, tsec)
1014 : :
1015 : : ABI_NVTX_START_RANGE(NVTX_INVOVL_POST2)
1016 [ + + + + ]: 482418 : if(size(cwaveprj) > 1) then
1017 : : ! copy PtPSm1proj to cwaveprj(:,:)
1018 [ + + ]: 1290518 : do idat=1, ndat*nspinor
1019 : 1130000 : shift = 0
1020 [ + + ]: 3718998 : do iatom = 1, ham%natom
1021 : 2428480 : nlmn = cwaveprj(iatom, idat)%nlmn
1022 [ + + + + ]: 66403840 : cwaveprj(iatom, idat)%cp(1:cplx, 1:nlmn) = PtPsm1proj(1:cplx, shift+1:shift+nlmn, idat)
1023 : 3558480 : shift = shift + nlmn
1024 : : end do
1025 : : end do
1026 : : !cwaveprj_in is empty if GEMM nonlop is being used, so populate it here
1027 [ + + ]: 160518 : if(gemm_nonlop_use_gemm) then
1028 [ + + ]: 2016 : do idat=1, ndat*nspinor
1029 : 1728 : shift = 0
1030 [ + + ]: 5472 : do iatom = 1, ham%natom
1031 : 3456 : nlmn = cwaveprj_in(iatom, idat)%nlmn
1032 [ + + + + ]: 86400 : cwaveprj_in(iatom, idat)%cp(1:cplx, 1:nlmn) = proj(1:cplx, shift+1:shift+nlmn, idat)
1033 : 5184 : shift = shift + nlmn
1034 : : end do
1035 : : end do
1036 : : end if
1037 : 160518 : call pawcprj_axpby(one, one, cwaveprj_in, cwaveprj)
1038 : : end if
1039 : 160806 : call pawcprj_free(cwaveprj_in)
1040 [ + - + + : 2589574 : ABI_FREE(cwaveprj_in)
- + - + -
+ ]
1041 : :
1042 [ + - ]: 160806 : if (ham%gpu_option == ABI_GPU_LEGACY .or. ham%gpu_option==ABI_GPU_KOKKOS) then
1043 : : #if defined(HAVE_GPU_CUDA) && defined(HAVE_KOKKOS) && defined(HAVE_YAKL)
1044 : : cwavef_size = 2*npw*nspinor*ndat
1045 : : call add_array_kokkos(c_loc(sm1cwavef), c_loc(cwavef), cwavef_size)
1046 : : #endif
1047 : : else
1048 [ + + + + ]: 669394662 : sm1cwavef = cwavef + sm1cwavef
1049 : : end if
1050 : :
1051 : : ABI_NVTX_END_RANGE()
1052 : :
1053 [ - + ]: 160806 : ABI_FREE(proj)
1054 [ - + ]: 160806 : ABI_FREE(sm1proj)
1055 [ - + ]: 160806 : ABI_FREE(PtPsm1proj)
1056 : :
1057 [ - + - - : 160806 : end subroutine apply_invovl
- - - - ]
1058 : : !!***
1059 : :
1060 : : !!****f* m_invovl/solve_inner
1061 : : !! NAME
1062 : : !! solve_inner
1063 : : !!
1064 : : !! FUNCTION
1065 : : !! Helper function: iteratively solves the inner system
1066 : : !!
1067 : : !! INPUTS
1068 : : !!
1069 : : !! SOURCE
1070 : 160806 : subroutine solve_inner(invovl, ham, cplx, mpi_enreg, proj, ndat, sm1proj, PtPsm1proj, block_sliced)
1071 : :
1072 : : use m_abi_linalg
1073 : :
1074 : : integer,intent(in) :: ndat,cplx
1075 : : type(invovl_kpt_type), intent(in) :: invovl
1076 : : real(dp), intent(inout) :: proj(cplx, invovl%nprojs,ndat)
1077 : : real(dp), intent(inout) :: sm1proj(cplx, invovl%nprojs, ndat)
1078 : : real(dp), intent(inout) :: PtPsm1proj(cplx, invovl%nprojs, ndat)
1079 : 160806 : real(dp), allocatable :: temp_proj(:,:,:)
1080 : : type(mpi_type), intent(in) :: mpi_enreg
1081 : : type(gs_hamiltonian_type),intent(in) :: ham
1082 : : integer, intent(in) :: block_sliced
1083 : :
1084 : 321612 : integer :: array_nlmntot_pp(mpi_enreg%nproc_fft)
1085 : : integer :: nlmntot_this_proc, ibeg, iend, ierr, i, nprojs
1086 : 321612 : real(dp) :: resid(cplx, invovl%nprojs,ndat), precondresid(cplx, invovl%nprojs,ndat)
1087 : 321612 : real(dp) :: normprojs(ndat), errs(ndat), maxerr, previous_maxerr
1088 : : character(len=500) :: message
1089 : :
1090 : : real(dp), parameter :: precision = 1e-16 ! maximum relative error. TODO: use tolwfr ?
1091 : : real(dp) :: convergence_rate
1092 : : integer :: additional_steps_to_take
1093 : :
1094 : : ! *************************************************************************
1095 : :
1096 : 160806 : nprojs = invovl%nprojs
1097 [ + + + + : 65308502 : normprojs = SUM(SUM(proj**2, 1),1)
+ + ]
1098 : 160806 : call xmpi_sum(normprojs, mpi_enreg%comm_fft, ierr)
1099 : :
1100 : : ! Compute work distribution : split nprojs evenly between the fft processors
1101 [ + + ]: 160806 : if(mpi_enreg%paral_kgb == 1) then
1102 [ + + ]: 125208 : array_nlmntot_pp(:) = nprojs / mpi_enreg%nproc_fft
1103 : : ! not enough work, there's MOD(nprojs,mpi_enreg%nproc_fft) tasks left
1104 : : ! assign them to the first ones
1105 [ - + ]: 62460 : array_nlmntot_pp(1:MOD(nprojs,mpi_enreg%nproc_fft)) = array_nlmntot_pp(1:MOD(nprojs,mpi_enreg%nproc_fft)) + 1
1106 [ + + ]: 62604 : ibeg = SUM(array_nlmntot_pp(1:mpi_enreg%me_fft)) + 1
1107 : 62460 : iend = ibeg + array_nlmntot_pp(1+mpi_enreg%me_fft) - 1
1108 : 62460 : nlmntot_this_proc = iend - ibeg + 1
1109 : : else
1110 : 98346 : ibeg = 1
1111 : 98346 : iend = nprojs
1112 : 98346 : nlmntot_this_proc = nprojs
1113 : : end if
1114 : :
1115 [ - + + - : 804030 : ABI_MALLOC(temp_proj, (cplx, nlmntot_this_proc, ndat))
- + + - -
+ + - + -
+ - - + -
+ ]
1116 : :
1117 : : ! first guess for sm1proj
1118 : 160806 : call apply_block(ham, cplx, invovl%inv_s_approx, nprojs, ndat, proj, sm1proj, block_sliced)
1119 : :
1120 : : ! Iterative refinement
1121 : : ! TODO use a more efficient iterative algorithm than iterative refinement, use locking
1122 : 160806 : additional_steps_to_take = -1
1123 [ + - ]: 1476115 : do i=1, 30
1124 : : ! compute resid = proj - (D^-1 + PtP)sm1proj
1125 : 1476115 : call apply_block(ham, cplx, invovl%inv_sij, nprojs, ndat, sm1proj, resid, block_sliced)
1126 [ + - - + : 724585084 : temp_proj = sm1proj(:,ibeg:iend,:)
- - + + +
+ + + ]
1127 : :
1128 : : ! compute matrix multiplication : PtPsm1proj(:,:,1) = invovl%gram * temp_proj(:,:,1)
1129 : : call abi_xgemm('N', 'N', nprojs, ndat, nlmntot_this_proc, cone, &
1130 : : & invovl%gram_projs(:,:,1), nprojs, &
1131 : : & temp_proj(:,:,1), nlmntot_this_proc, czero, &
1132 : : & PtPsm1proj(:,:,1), nprojs, &
1133 : 1476115 : & x_cplx=cplx)
1134 : 1476115 : call xmpi_sum(PtPsm1proj, mpi_enreg%comm_fft, ierr)
1135 [ + + + + : 723593097 : resid = proj - resid - Ptpsm1proj
+ + ]
1136 : : ! exit check
1137 [ + + + + : 723593097 : errs = SUM(SUM(resid**2, 1),1)
+ + ]
1138 : 1476115 : call xmpi_sum(errs, mpi_enreg%comm_fft, ierr)
1139 : :
1140 [ + - - + : 13553758 : maxerr = sqrt(MAXVAL(errs/normprojs))
- - + + ]
1141 [ + + + + ]: 1476115 : if(maxerr < precision .or. additional_steps_to_take == 1) then
1142 : : exit
1143 : : ! We might stall and never get to the specified precision because of machine errors.
1144 : : ! If we got to 1e-10, extrapolate convergence rate and determine the number of additional
1145 : : ! steps to take to reach precision
1146 [ + + + + ]: 1398471 : else if(maxerr < 1e-10 .and. additional_steps_to_take == -1) then
1147 : 160806 : convergence_rate = -LOG(1e-10) / i
1148 [ - + ]: 160806 : additional_steps_to_take = CEILING(-LOG(precision/1e-10)/convergence_rate) + 1
1149 [ + + ]: 1237665 : else if(additional_steps_to_take > 0) then
1150 [ + + ]: 525573 : if(previous_maxerr<maxerr)exit
1151 : 442411 : additional_steps_to_take = additional_steps_to_take - 1
1152 : : end if
1153 : 1315309 : previous_maxerr=maxerr
1154 : :
1155 : : ! add preconditionned residual
1156 : 1315309 : call apply_block(ham, cplx, invovl%inv_s_approx, nprojs, ndat, resid, precondresid, block_sliced)
1157 [ + + + + : 658445401 : sm1proj = sm1proj + precondresid
+ + ]
1158 : : end do
1159 : :
1160 [ + + - + ]: 160806 : if(maxerr >= precision .and. maxerr >= 1e-10) then
1161 : 0 : write(message, *) 'In invovl, max error was', maxerr, ' after 30 iterations'
1162 : 0 : ABI_WARNING(message)
1163 : : else
1164 : : ! write(message,'(a,i2,a,es13.5)') 'Iterative solver in invovl finished in ', i, ' iterations, error', maxerr
1165 : : ! call wrtout(std_out,message,'COLL')
1166 : : end if
1167 : :
1168 [ - + ]: 160806 : ABI_FREE(temp_proj)
1169 : :
1170 : 160806 : end subroutine solve_inner
1171 : : !!***
1172 : :
1173 : : !!****f* m_invovl/apply_block
1174 : : !! NAME
1175 : : !! apply_block
1176 : : !!
1177 : : !! FUNCTION
1178 : : !! Helper function: applies a block-diagonal matrix mat(lmnmax, lmnmax, ntypat)
1179 : : !!
1180 : : !! INPUTS
1181 : : !!
1182 : : !! SOURCE
1183 : 2952230 : subroutine apply_block(ham, cplx, mat, nprojs, ndat, x, y, block_sliced)
1184 : :
1185 : : use m_abi_linalg
1186 : :
1187 : : integer,intent(in) :: ndat, nprojs, cplx
1188 : : real(dp), intent(inout), target :: x(cplx, nprojs, ndat), y(cplx, nprojs, ndat)
1189 : : type(gs_hamiltonian_type),intent(in) :: ham
1190 : : real(dp), intent(in) :: mat(cplx, ham%lmnmax, ham%lmnmax, ham%ntypat)
1191 : : integer, intent(in) :: block_sliced
1192 : :
1193 : : integer :: nlmn, shift, itypat, idat
1194 : 2952230 : real(dp),pointer :: work_x(:,:),work_y(:,:)
1195 : :
1196 : : ! *************************************************************************
1197 : :
1198 : 2952230 : if (block_sliced == 1) then
1199 : :
1200 [ + + ]: 24155286 : do idat = 1, ndat
1201 : 21203056 : shift = 1
1202 [ + + ]: 52113246 : do itypat=1, ham%ntypat
1203 [ + + + + ]: 271874380 : nlmn = count(ham%indlmn(3,:,itypat)>0)
1204 : : !! apply mat to all atoms at once
1205 : : ! perform natom multiplications of size nlmn
1206 : : ! compute y = mat*x
1207 : 27957960 : work_x => x(:, shift:shift+nlmn*ham%nattyp(itypat)-1, idat)
1208 : 27957960 : work_y => y(:, shift:shift+nlmn*ham%nattyp(itypat)-1, idat)
1209 [ + + ]: 27957960 : if(cplx == 2) then
1210 : : call ZHEMM('L','U', nlmn, ham%nattyp(itypat), cone, &
1211 : : & mat(:, :, :, itypat), ham%lmnmax, &
1212 : : & work_x, nlmn, czero, &
1213 [ - + - + ]: 25098432 : & work_y, nlmn)
1214 : : else
1215 : : call DSYMM('L','U', nlmn, ham%nattyp(itypat), one, &
1216 : : & mat(:, :, :, itypat), ham%lmnmax, &
1217 : : & work_x, nlmn, zero, &
1218 [ - + - + ]: 2859528 : & work_y, nlmn)
1219 : : end if
1220 : 49161016 : shift = shift + nlmn*ham%nattyp(itypat)
1221 : : end do
1222 : : end do
1223 : :
1224 : : else ! block_sliced = 0
1225 : :
1226 : : shift = 1
1227 [ # # ]: 0 : do itypat=1, ham%ntypat
1228 [ # # # # ]: 0 : nlmn = count(ham%indlmn(3,:,itypat)>0)
1229 : : !! apply mat to all atoms at once, all idat at once
1230 : : ! perform natom multiplications of size nlmn
1231 : : ! be careful here matrix extracted from x and y are not memory contiguous
1232 : : ! ==> so in the GPU version we will need to adapt leading dimension
1233 [ # # ]: 0 : if(cplx == 2) then
1234 : : call ZHEMM('L','U', nlmn, ham%nattyp(itypat)*ndat, cone, &
1235 : : & mat(:, :, :, itypat), ham%lmnmax, &
1236 : : & x(:, 1:nlmn*ham%nattyp(itypat), 1:ndat), nlmn, czero, &
1237 [ # # # # : 0 : & y(:, 1:shift+nlmn*ham%nattyp(itypat)-1, 1:ndat), nlmn)
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1238 : : else
1239 : : call DSYMM('L','U', nlmn, ham%nattyp(itypat)*ndat, one, &
1240 : : & mat(:, :, :, itypat), ham%lmnmax, &
1241 : : & x(:, shift:shift+nlmn*ham%nattyp(itypat)-1, 1:ndat), nlmn, zero, &
1242 [ # # # # : 0 : & y(:, shift:shift+nlmn*ham%nattyp(itypat)-1, 1:ndat), nlmn)
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
1243 : : end if
1244 : 0 : shift = shift + nlmn*ham%nattyp(itypat)
1245 : : end do
1246 : :
1247 : : end if
1248 : :
1249 [ - + ]: 2952230 : end subroutine apply_block
1250 : : !!***
1251 : :
1252 : 0 : function invovl_ompgpu_work_mem(ham, ndat) result(req_mem)
1253 : :
1254 : : type(gs_hamiltonian_type), intent(in) :: ham
1255 : : integer, intent(in) :: ndat
1256 : : integer :: nprojs, cplx, itypat
1257 : : integer(kind=c_size_t) :: req_mem
1258 : :
1259 : 0 : nprojs = 0
1260 [ # # ]: 0 : do itypat=1,ham%ntypat
1261 [ # # # # ]: 0 : nprojs = nprojs + count(ham%indlmn(3,:,itypat)>0)*ham%nattyp(itypat)
1262 : : end do
1263 [ # # ]: 0 : cplx = 2; if(ham%istwf_k == 2) cplx = 1
1264 : :
1265 : 0 : req_mem = 0
1266 : 0 : req_mem = req_mem + dp * cplx * int(nprojs, c_size_t) * int(ndat, c_size_t) ! proj
1267 : : req_mem = req_mem + dp * cplx * int(nprojs, c_size_t) * int(ndat, c_size_t) ! sm1proj
1268 : : req_mem = req_mem + dp * cplx * int(nprojs, c_size_t) * int(ndat, c_size_t) ! PtPsm1proj
1269 : 0 : req_mem = req_mem + dp * cplx * int(nprojs, c_size_t) * int(ndat, c_size_t) ! resid (solve_inner)
1270 : 0 : req_mem = req_mem + dp * cplx * int(nprojs, c_size_t) * int(ndat, c_size_t) ! precondresid (solve_inner)
1271 : :
1272 : 0 : end function invovl_ompgpu_work_mem
1273 : :
1274 : 0 : function invovl_ompgpu_static_mem(ham) result(req_mem)
1275 : :
1276 : : type(gs_hamiltonian_type), intent(in) :: ham
1277 : : integer :: nprojs, cplx, itypat
1278 : : integer(kind=c_size_t) :: req_mem
1279 : :
1280 : 0 : nprojs = 0
1281 [ # # ]: 0 : do itypat=1,ham%ntypat
1282 [ # # # # ]: 0 : nprojs = nprojs + count(ham%indlmn(3,:,itypat)>0)*ham%nattyp(itypat)
1283 : : end do
1284 [ # # ]: 0 : cplx = 2; if(ham%istwf_k > 1) cplx = 1
1285 : :
1286 : 0 : req_mem = 0
1287 : 0 : req_mem = req_mem + dp * cplx * int(nprojs, c_size_t) * int(nprojs, c_size_t) ! gram_projs
1288 : : req_mem = req_mem + dp * cplx * int(ham%lmnmax, c_size_t) * &
1289 : 0 : & int(ham%lmnmax, c_size_t) * int(ham%ntypat, c_size_t) ! inv_sij
1290 : : req_mem = req_mem + dp * cplx * int(ham%lmnmax, c_size_t) * &
1291 : 0 : & int(ham%lmnmax, c_size_t) * int(ham%ntypat, c_size_t) ! inv_s_approx
1292 : :
1293 : 0 : end function invovl_ompgpu_static_mem
1294 : :
1295 : : #ifdef HAVE_OPENMP_OFFLOAD
1296 : : !*******************************************************************************************************************************!
1297 : : ! OpenMP GPU routines !
1298 : : !*******************************************************************************************************************************!
1299 : :
1300 : : !!****f* m_invovl/apply_invovl_ompgpu
1301 : : !! NAME
1302 : : !! apply_invovl_ompgpu
1303 : : !!
1304 : : !! FUNCTION
1305 : : !! Applies the inverse of the overlap matrix to cwavef (OpenMP GPU implementation)
1306 : : !!
1307 : : !! INPUTS
1308 : : !!
1309 : : !! SOURCE
1310 : :
1311 : : subroutine apply_invovl_ompgpu(ham, cwavef, sm1cwavef, cwaveprj, npw, ndat, mpi_enreg, nspinor, block_sliced)
1312 : :
1313 : : #if defined(HAVE_FC_ISO_C_BINDING) && defined(HAVE_GPU)
1314 : : use, intrinsic :: iso_c_binding
1315 : : #endif
1316 : :
1317 : : ! args
1318 : : type(gs_hamiltonian_type), intent(in), target :: ham
1319 : : integer, intent(in) :: npw, ndat
1320 : : integer, intent(in) :: nspinor
1321 : : integer, intent(in) :: block_sliced
1322 : : real(dp), intent(inout), target :: cwavef(2, npw*nspinor*ndat) ! TODO should be in, fix nonlop
1323 : : type(mpi_type) :: mpi_enreg
1324 : : real(dp), intent(inout), target :: sm1cwavef(2, npw*nspinor*ndat)
1325 : : type(pawcprj_type), intent(inout) :: cwaveprj(:,:)
1326 : : logical :: transfer_omp_args
1327 : :
1328 : : real(dp), ABI_CONTIGUOUS pointer :: proj(:,:,:),sm1proj(:,:,:),PtPsm1proj(:,:,:)
1329 : :
1330 : : integer :: idat, iatom, icplx, iproj, nprojs, nlmn, shift
1331 : : real(dp) :: tsec(2)
1332 : :
1333 : : integer :: choice, cpopt, paw_opt , cplx, old_me_g0
1334 : : type(pawcprj_type),allocatable :: cwaveprj_in(:,:)
1335 : :
1336 : : integer :: ikpt_this_proc
1337 : : ! dummies
1338 : : real(dp) :: enlout(ndat), lambda_block(1), gvnlxc(1,1)
1339 : : integer, parameter :: nnlout = 0, idir = 0, signs = 2
1340 : :
1341 : : type(invovl_kpt_type), pointer :: invovl
1342 : :
1343 : : ! *************************************************************************
1344 : :
1345 : : ikpt_this_proc=bandfft_kpt_get_ikpt()
1346 : : invovl => invovl_kpt(ikpt_this_proc)
1347 : : nprojs=invovl%nprojs
1348 : : if(ikpt_this_proc /= current_ikpt_in_gpu) call refresh_invovl_ompgpu_kpt(ikpt_this_proc)
1349 : :
1350 : : if(ham%istwf_k == 1) then
1351 : : cplx = 2
1352 : : else
1353 : : cplx = 1
1354 : : end if
1355 : : call alloc_ompgpu_buffers(cplx,nprojs,nspinor,ndat)
1356 : : proj => proj_ompgpu
1357 : : sm1proj => sm1proj_ompgpu
1358 : : PtPsm1proj => PtPsm1proj_ompgpu
1359 : : !$OMP TARGET ENTER DATA MAP(alloc:proj,sm1proj,PtPsm1proj)
1360 : :
1361 : : transfer_omp_args = .not. ( xomp_target_is_present(c_loc(sm1cwavef)) &
1362 : : .and. xomp_target_is_present(c_loc(cwavef)))
1363 : : !$OMP TARGET ENTER DATA MAP(alloc:gvnlxc)
1364 : : if(transfer_omp_args) then
1365 : : !$OMP TARGET ENTER DATA MAP(alloc:sm1cwavef,cwavef)
1366 : : !$OMP TARGET UPDATE TO(sm1cwavef,cwavef)
1367 : : end if
1368 : :
1369 : : call timab(timer_apply_inv_ovl_opernla, 1, tsec)
1370 : :
1371 : : ! get the cprj
1372 : : ABI_NVTX_START_RANGE(NVTX_INVOVL_NONLOP1)
1373 : : choice = 0 ! only compute cprj, nothing else
1374 : : cpopt = 0 ! compute and save cprj
1375 : : paw_opt = 3 ! S nonlocal operator
1376 : :
1377 : : if(ham%istwf_k==2) then
1378 : : old_me_g0=mpi_enreg%me_g0
1379 : : if (mpi_enreg%me_fft==0) then
1380 : : mpi_enreg%me_g0=1
1381 : : else
1382 : : mpi_enreg%me_g0=0
1383 : : end if
1384 : : end if
1385 : : call nonlop(choice,cpopt,cwaveprj_in,enlout,ham,idir,lambda_block,mpi_enreg,ndat,nnlout,&
1386 : : paw_opt,signs,sm1cwavef,tim_nonlop,cwavef,gvnlxc,vectproj=proj)
1387 : : ABI_NVTX_END_RANGE()
1388 : :
1389 : : call timab(timer_apply_inv_ovl_opernla, 2, tsec)
1390 : : call timab(timer_apply_inv_ovl_inv_s, 1, tsec)
1391 : :
1392 : : !multiply by S^1
1393 : : ABI_NVTX_START_RANGE(NVTX_INVOVL_INNER)
1394 : : call solve_inner_ompgpu(invovl, ham, cplx, mpi_enreg, proj, ndat*nspinor, sm1proj, PtPsm1proj, block_sliced)
1395 : : !$OMP TARGET TEAMS DISTRIBUTE MAP(to:sm1proj,PtPsm1proj)
1396 : : do idat =1, ndat*nspinor
1397 : : !$OMP PARALLEL DO COLLAPSE(2) PRIVATE(iproj,icplx)
1398 : : do iproj = 1, nprojs
1399 : : do icplx = 1, cplx
1400 : : sm1proj(icplx,iproj,idat) = - sm1proj(icplx,iproj,idat)
1401 : : PtPsm1proj(icplx,iproj,idat) = - PtPsm1proj(icplx,iproj,idat)
1402 : : end do
1403 : : end do
1404 : : end do
1405 : :
1406 : : ABI_NVTX_END_RANGE()
1407 : :
1408 : : call timab(timer_apply_inv_ovl_inv_s, 2, tsec)
1409 : : call timab(timer_apply_inv_ovl_opernlb, 1, tsec)
1410 : :
1411 : : ! get the corresponding wf
1412 : : ABI_NVTX_START_RANGE(NVTX_INVOVL_NONLOP2)
1413 : : cpopt = 2 ! reuse cprj
1414 : : choice = 7 ! get wf from cprj, without the application of S
1415 : : paw_opt = 3
1416 : : call nonlop(choice,cpopt,cwaveprj,enlout,ham,idir,lambda_block,mpi_enreg,ndat,nnlout,paw_opt,&
1417 : : signs,sm1cwavef,tim_nonlop,cwavef,gvnlxc,vectproj=sm1proj)
1418 : : ABI_NVTX_END_RANGE()
1419 : :
1420 : : call timab(timer_apply_inv_ovl_opernlb, 2, tsec)
1421 : : if (ham%istwf_k==2) mpi_enreg%me_g0=old_me_g0
1422 : :
1423 : : if(size(cwaveprj) > 1) then
1424 : : ABI_MALLOC(cwaveprj_in, (ham%natom,nspinor*ndat))
1425 : : call pawcprj_alloc(cwaveprj_in,0,ham%dimcprj)
1426 : : !$OMP TARGET UPDATE FROM(PtPsm1proj,proj)
1427 : : ! copy PtPsm1proj to cwaveprj(:,:)
1428 : : do idat=1, ndat*nspinor
1429 : : shift = 0
1430 : : do iatom = 1, ham%natom
1431 : : nlmn = cwaveprj(iatom, idat)%nlmn
1432 : : cwaveprj(iatom, idat)%cp(1:cplx, 1:nlmn) = PtPsm1proj(1:cplx, shift+1:shift+nlmn, idat)
1433 : : shift = shift + nlmn
1434 : : end do
1435 : : end do
1436 : : do idat=1, ndat*nspinor
1437 : : shift = 0
1438 : : do iatom = 1, ham%natom
1439 : : nlmn = cwaveprj_in(iatom, idat)%nlmn
1440 : : cwaveprj_in(iatom, idat)%cp(1:cplx, 1:nlmn) = proj(1:cplx, shift+1:shift+nlmn, idat)
1441 : : shift = shift + nlmn
1442 : : end do
1443 : : end do
1444 : : call pawcprj_axpby(one, one, cwaveprj_in, cwaveprj)
1445 : : call pawcprj_free(cwaveprj_in)
1446 : : ABI_FREE(cwaveprj_in)
1447 : : end if
1448 : :
1449 : : call abi_xaxpy(2*npw*nspinor*ndat, cone, cwavef, 1, sm1cwavef, 1, gpu_option=ABI_GPU_OPENMP)
1450 : :
1451 : : if(transfer_omp_args) then
1452 : : !$OMP TARGET UPDATE FROM(sm1cwavef,cwavef)
1453 : : !$OMP TARGET EXIT DATA MAP(delete:sm1cwavef,cwavef)
1454 : : end if
1455 : :
1456 : : !$OMP TARGET EXIT DATA MAP(delete:gvnlxc)
1457 : : !$OMP TARGET EXIT DATA MAP(delete:proj,sm1proj,PtPsm1proj)
1458 : :
1459 : : end subroutine apply_invovl_ompgpu
1460 : : !!***
1461 : :
1462 : : !!****f* m_invovl/solve_inner_ompgpu
1463 : : !! NAME
1464 : : !! solve_inner_ompgpu
1465 : : !!
1466 : : !! FUNCTION
1467 : : !! Helper function: iteratively solves the inner system (OpenMP GPU offload implementation)
1468 : : !!
1469 : : !! INPUTS
1470 : : !!
1471 : : !! SOURCE
1472 : : subroutine solve_inner_ompgpu(invovl, ham, cplx, mpi_enreg, proj, ndat, sm1proj, PtPsm1proj, block_sliced)
1473 : :
1474 : : use m_abi_linalg
1475 : :
1476 : : integer,intent(in) :: ndat,cplx
1477 : : type(invovl_kpt_type), intent(in), target :: invovl
1478 : : real(dp), intent(inout) :: proj(cplx, invovl%nprojs,ndat)
1479 : : real(dp), intent(inout), target :: sm1proj(cplx, invovl%nprojs, ndat)
1480 : : real(dp), intent(inout), target :: PtPsm1proj(cplx, invovl%nprojs, ndat)
1481 : : type(mpi_type), intent(in) :: mpi_enreg
1482 : : type(gs_hamiltonian_type),intent(in) :: ham
1483 : : integer, intent(in) :: block_sliced
1484 : :
1485 : : integer :: array_nlmntot_pp(mpi_enreg%nproc_fft)
1486 : : integer :: nlmntot_this_proc, ibeg, iend, ierr, i, nprojs
1487 : : real(dp) :: resid(cplx, invovl%nprojs,ndat), precondresid(cplx, invovl%nprojs,ndat)
1488 : : real(dp) :: normprojs(ndat), errs(ndat), maxerr, previous_maxerr
1489 : : character(len=500) :: message
1490 : :
1491 : : real(dp), parameter :: precision = 1e-16 ! maximum relative error. TODO: use tolwfr ?
1492 : : real(dp) :: convergence_rate,sum_tmp
1493 : : integer :: additional_steps_to_take,idat,iproj,icplx
1494 : : integer :: Ptsize(3)
1495 : :
1496 : : ! *************************************************************************
1497 : :
1498 : : Ptsize(1) = cplx
1499 : : Ptsize(2) = invovl%nprojs
1500 : : Ptsize(3) = ndat
1501 : : nprojs = invovl%nprojs
1502 : :
1503 : : !$OMP TARGET ENTER DATA MAP(alloc:errs,precondresid,resid,normprojs)
1504 : :
1505 : : !FIXME LLVM has trouble with performing team reduction (AOMP 15.0.2)
1506 : : #ifdef FC_LLVM
1507 : : !$OMP TARGET UPDATE FROM(proj)
1508 : : #else
1509 : : !$OMP TARGET TEAMS DISTRIBUTE MAP(to:normprojs,proj) PRIVATE(idat,sum_tmp)
1510 : : #endif
1511 : : do idat = 1,ndat
1512 : : sum_tmp=0
1513 : : #ifndef FC_LLVM
1514 : : !$OMP PARALLEL DO COLLAPSE(2) REDUCTION(+:sum_tmp) PRIVATE(iproj,icplx)
1515 : : #endif
1516 : : do iproj = 1,nprojs
1517 : : do icplx = 1,cplx
1518 : : sum_tmp = sum_tmp + proj(icplx,iproj,idat)**2
1519 : : end do
1520 : : end do
1521 : : normprojs(idat)=sum_tmp
1522 : : end do
1523 : : #ifndef FC_LLVM
1524 : : !$OMP TARGET UPDATE FROM(normprojs)
1525 : : #endif
1526 : :
1527 : : ibeg = 1
1528 : : iend = nprojs
1529 : : nlmntot_this_proc = nprojs
1530 : :
1531 : : ! first guess for sm1proj
1532 : : call apply_block_ompgpu(ham, cplx, invovl%inv_s_approx, nprojs, ndat, proj, sm1proj, block_sliced)
1533 : :
1534 : : ! Iterative refinement
1535 : : ! TODO use a more efficient iterative algorithm than iterative refinement, use locking
1536 : : additional_steps_to_take = -1
1537 : : do i=1, 30
1538 : : #ifdef FC_NVHPC
1539 : : ! Silly fix for NVHPC 25.1
1540 : : if(ndat == -42) write(100,*) ndat
1541 : : #endif
1542 : : ! compute resid = proj - (D^-1 + PtP)sm1proj
1543 : : call apply_block_ompgpu(ham, cplx, invovl%inv_sij, nprojs, ndat, sm1proj, resid, block_sliced)
1544 : :
1545 : : ! compute matrix multiplication : PtPsm1proj(:,:,1) = invovl%gram * sm1proj(:,:,1)
1546 : : ABI_NVTX_START_RANGE(NVTX_INVOVL_INNER_GEMM)
1547 : : call abi_xgemm('N', 'N', nprojs, ndat, nlmntot_this_proc, cone, &
1548 : : & current_gram_projs, nprojs,&
1549 : : & sm1proj, nlmntot_this_proc, czero, &
1550 : : & PtPsm1proj, nprojs, &
1551 : : & x_cplx=cplx, gpu_option=ABI_GPU_OPENMP)
1552 : :
1553 : : !$OMP TARGET TEAMS DISTRIBUTE &
1554 : : !$OMP& PRIVATE(idat) MAP(to:proj,resid,PtPsm1proj)
1555 : : do idat =1, ndat
1556 : : !$OMP PARALLEL DO COLLAPSE(2) PRIVATE(iproj,icplx)
1557 : : do iproj =1, nprojs
1558 : : do icplx = 1,cplx
1559 : : resid(icplx, iproj, idat) = proj(icplx, iproj, idat) - resid(icplx, iproj, idat) - PtPsm1proj(icplx, iproj, idat)
1560 : : end do
1561 : : end do
1562 : : end do
1563 : :
1564 : : ! exit check
1565 : : #ifdef FC_LLVM
1566 : : !FIXME LLVM has trouble with performing team reduction (v16.0.0 from AMD ROCm 5.6.0)
1567 : : !$OMP TARGET UPDATE FROM(resid)
1568 : : errs = SUM(SUM(resid**2, 1),1)
1569 : : #else
1570 : : !$OMP TARGET TEAMS DISTRIBUTE MAP(to:errs,resid) PRIVATE(idat,sum_tmp)
1571 : : do idat = 1,ndat
1572 : : sum_tmp=0
1573 : : !$OMP PARALLEL DO COLLAPSE(2) REDUCTION(+:sum_tmp) PRIVATE(iproj,icplx)
1574 : : do iproj = 1,nprojs
1575 : : do icplx = 1,cplx
1576 : : sum_tmp = sum_tmp + resid(icplx,iproj,idat)**2
1577 : : end do
1578 : : end do
1579 : : errs(idat)=sum_tmp
1580 : : end do
1581 : : !$OMP TARGET UPDATE FROM(errs)
1582 : : #endif
1583 : :
1584 : : ABI_NVTX_END_RANGE()
1585 : :
1586 : : maxerr = sqrt(MAXVAL(errs/normprojs))
1587 : : if(maxerr < precision .or. additional_steps_to_take == 1) then
1588 : : exit
1589 : : ! We might stall and never get to the specified precision because of machine errors.
1590 : : ! If we got to 1e-10, extrapolate convergence rate and determine the number of additional
1591 : : ! steps to take to reach precision
1592 : : else if(maxerr < 1e-10 .and. additional_steps_to_take == -1) then
1593 : : convergence_rate = -LOG(1e-10) / i
1594 : : additional_steps_to_take = CEILING(-LOG(precision/1e-10)/convergence_rate) + 1
1595 : : else if(additional_steps_to_take > 0) then
1596 : : if(previous_maxerr<maxerr)exit
1597 : : additional_steps_to_take = additional_steps_to_take - 1
1598 : : end if
1599 : : previous_maxerr=maxerr
1600 : :
1601 : : ! add preconditionned residual
1602 : : call apply_block_ompgpu(ham, cplx, invovl%inv_s_approx, nprojs, ndat, resid, precondresid, block_sliced)
1603 : :
1604 : : !$OMP TARGET TEAMS DISTRIBUTE &
1605 : : !$OMP& PRIVATE(idat) MAP(to:sm1proj,precondresid)
1606 : : do idat =1, ndat
1607 : : !$OMP PARALLEL DO PRIVATE(iproj,icplx) COLLAPSE(2)
1608 : : do iproj =1, nprojs
1609 : : do icplx = 1,cplx
1610 : : sm1proj(icplx, iproj, idat) = sm1proj(icplx, iproj, idat) + precondresid(icplx, iproj, idat)
1611 : : end do
1612 : : end do
1613 : : end do
1614 : : end do
1615 : : !$OMP TARGET EXIT DATA MAP(delete:errs,resid,precondresid,normprojs)
1616 : :
1617 : : if(maxerr >= precision .and. maxerr >= 1e-10) then
1618 : : write(message, *) 'In invovl, max error was', maxerr, ' after 30 iterations'
1619 : : ABI_WARNING(message)
1620 : : else
1621 : : ! write(message,'(a,i2,a,es13.5)') 'Iterative solver in invovl finished in ', i, ' iterations, error', maxerr
1622 : : ! call wrtout(std_out,message,'COLL')
1623 : : end if
1624 : :
1625 : : end subroutine solve_inner_ompgpu
1626 : : !!***
1627 : :
1628 : : !!****f* m_invovl/apply_block_ompgpu
1629 : : !! NAME
1630 : : !! apply_block_ompgpu
1631 : : !!
1632 : : !! FUNCTION
1633 : : !! Helper function: applies a block-diagonal matrix mat(lmnmax, lmnmax, ntypat)
1634 : : !!
1635 : : !! INPUTS
1636 : : !!
1637 : : !! SOURCE
1638 : : subroutine apply_block_ompgpu(ham, cplx, mat, nprojs, ndat, x, y, block_sliced)
1639 : :
1640 : : use m_abi_linalg
1641 : :
1642 : : integer,intent(in) :: ndat, nprojs, cplx
1643 : : real(dp), intent(inout), target :: x(cplx, nprojs, ndat), y(cplx, nprojs, ndat)
1644 : : type(gs_hamiltonian_type),intent(in) :: ham
1645 : : real(dp), intent(in), target :: mat(cplx, ham%lmnmax, ham%lmnmax, ham%ntypat)
1646 : : integer, intent(in) :: block_sliced
1647 : :
1648 : : integer :: nlmn, shift, itypat, idat
1649 : : real(dp), ABI_CONTIGUOUS pointer :: x_ptr(:, :, :), y_ptr(:, :, :), mat_ptr(:,:,:)
1650 : :
1651 : : ! *************************************************************************
1652 : :
1653 : : if (block_sliced == 1) then
1654 : :
1655 : : do idat = 1, ndat
1656 : : shift = 1
1657 : : do itypat=1, ham%ntypat
1658 : : nlmn = count(ham%indlmn(3,:,itypat)>0)
1659 : : !! apply mat to all atoms at once
1660 : : ! perform natom multiplications of size nlmn
1661 : : ! compute y = mat*x
1662 : : if(cplx == 2) then
1663 : : !$OMP TARGET DATA USE_DEVICE_ADDR(mat,x,y)
1664 : : call abi_gpu_zhemm('L','U', nlmn, ham%nattyp(itypat), cone, &
1665 : : c_loc(mat(:, :, :, itypat)), ham%lmnmax, &
1666 : : c_loc(x(:, shift:shift+nlmn*ham%nattyp(itypat)-1, idat)), nlmn, czero, &
1667 : : c_loc(y(:, shift:shift+nlmn*ham%nattyp(itypat)-1, idat)), nlmn)
1668 : : !$OMP END TARGET DATA
1669 : : else
1670 : : !$OMP TARGET DATA USE_DEVICE_ADDR(mat,x,y)
1671 : : call abi_gpu_xsymm(cplx, 'L','U', nlmn, ham%nattyp(itypat), cone, &
1672 : : c_loc(mat(:, :, :, itypat)), ham%lmnmax, &
1673 : : c_loc(x(:, shift:shift+nlmn*ham%nattyp(itypat)-1, idat)), nlmn, czero, &
1674 : : c_loc(y(:, shift:shift+nlmn*ham%nattyp(itypat)-1, idat)), nlmn)
1675 : : !$OMP END TARGET DATA
1676 : : end if
1677 : : shift = shift + nlmn*ham%nattyp(itypat)
1678 : : end do
1679 : : end do
1680 : :
1681 : : else ! block_sliced = 0
1682 : :
1683 : : shift = 1
1684 : : do itypat=1, ham%ntypat
1685 : : nlmn = count(ham%indlmn(3,:,itypat)>0)
1686 : : x_ptr => x(:, shift:shift+nlmn*ham%nattyp(itypat)-1, :)
1687 : : y_ptr => y(:, shift:shift+nlmn*ham%nattyp(itypat)-1, :)
1688 : : mat_ptr => mat(:, :, :, itypat)
1689 : : !! apply mat to all atoms at once, all idat at once
1690 : : ! perform natom multiplications of size nlmn
1691 : : ! be careful here matrix extracted from x and y are not memory contiguous
1692 : : ! ==> so in the GPU version we will need to adapt leading dimension
1693 : : !$OMP TARGET DATA USE_DEVICE_ADDR(mat_ptr,x_ptr,y_ptr)
1694 : : call abi_gpu_xgemm_strided(cplx, 'N','N', &
1695 : : nlmn, ham%nattyp(itypat), nlmn, cone, &
1696 : : c_loc(mat_ptr), ham%lmnmax, 0, &
1697 : : c_loc(x_ptr), nlmn, nprojs, &
1698 : : czero, &
1699 : : c_loc(y_ptr), nlmn, nprojs, ndat)
1700 : : !$OMP END TARGET DATA
1701 : : shift = shift + nlmn*ham%nattyp(itypat)
1702 : : end do
1703 : :
1704 : : end if
1705 : :
1706 : : end subroutine apply_block_ompgpu
1707 : : !!***
1708 : : #endif
1709 : :
1710 [ # # # # : 0 : end MODULE m_invovl
# # # # #
# # # # #
# # # # ]
1711 : : !!***
|