Multidip 1.0
Multi-photon matrix elements
Loading...
Searching...
No Matches
multidip_io.F90
Go to the documentation of this file.
1! Copyright 2020
2!
3! For a comprehensive list of the developers that contributed to these codes
4! see the UK-AMOR website.
5!
6! This file is part of UKRmol-out (UKRmol+ suite).
7!
8! UKRmol-out is free software: you can redistribute it and/or modify
9! it under the terms of the GNU General Public License as published by
10! the Free Software Foundation, either version 3 of the License, or
11! (at your option) any later version.
12!
13! UKRmol-out is distributed in the hope that it will be useful,
14! but WITHOUT ANY WARRANTY; without even the implied warranty of
15! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16! GNU General Public License for more details.
17!
18! You should have received a copy of the GNU General Public License
19! along with UKRmol-out (in source/COPYING). Alternatively, you can also visit
20! <https://www.gnu.org/licenses/>.
21!
22!> \brief I/O routines used by MULTIDIP
23!> \author J Benda
24!> \date 2020 - 2022
25!>
26!> This module contains routines that read the necessary input files (K-matrices, scattering coefficients, molecular_data files)
27!> and return the needed subset of data.
28!>
30
31 use, intrinsic :: iso_c_binding, only: c_double, c_int, c_f_pointer
32 use, intrinsic :: iso_fortran_env, only: input_unit, int32, int64, real64, output_unit, iostat_end
33
34 ! GBTOlib
35 use blas_lapack_gbl, only: blasint
36#ifdef WITH_MMAP
37 use file_mapping_gbl, only: file_mapping
38#endif
39 use phys_const_gbl, only: pi, imu, to_ev
40 use precisn_gbl, only: wp, wp_bytes
41
42 implicit none
43
44#ifdef WITH_SCALAPACK
45 integer(blasint), external :: numroc
46#endif
47
48 integer :: myproc = 1
49 integer :: nprocs = 1
50
51 !> \brief K-matrix file
52 !> \author J Benda
53 !> \date 2020 - 2021
54 !>
55 !> This data structure contains data read from a K-matrix file produced by RSOLVE.
56 !> K-matrices are used to obtain the final stationary photoionization wave function.
57 !>
58 type kmatrix
59 integer :: mgvn, stot, nescat, nchan, nopen, ndopen, nchsq, lukmt, nkset, maxne
60 real(wp), allocatable :: escat(:)
61 contains
62 procedure :: get_kmatrix
63 procedure :: read_kmatrix_file
65 end type kmatrix
66
67
68 !> \brief Photoionization wave function coefficients
69 !> \author J Benda
70 !> \date 2020
71 !>
72 !> Photoionization Ak coefficients calculated and written by RSOLVE. This can be used
73 !> as an alternative to calculating the Ak coefficients on the fly.
74 !>
75 type scatakcoeffs
76 real(wp), allocatable :: rea(:, :, :), ima(:, :, :), evchl(:)
77 integer, allocatable :: ichl(:), lvchl(:), mvchl(:)
78 integer :: mgvn, nesc, nchan, nstat, lusct
79 contains
80 procedure :: get_wfncoeffs
83 end type scatakcoeffs
84
85
86 !> \brief Auxiliary data structure for matrix (potentially memory-mapped, or distributed)
87 !> \author J Benda
88 !> \date 2021 - 2022
89 !>
90 !> Matrix class that either contains allocated data, or pointer to a mapped memory.
91 !> Used in MolecularData for inner dipole matrices and for boundary amplitude matrices.
92 !> If the logical flag "distributed" is set to true before reading data, the matrix
93 !> will be read using MPI-IO into a ScaLAPACK-compatible block-cyclic distributed matrix.
94 !>
95 type mappedmatrix
96 real(real64), pointer :: mat(:, :) => null() !< pointer to mapped disk data
97#ifdef WITH_MMAP
98 type(file_mapping) :: mapping !< helper structure for file memory mapping
99#endif
100 logical :: distributed = .false. !< whether this is just a local portion of a distributed ScaLAPACK matrix
101 integer(blasint) :: desc(9) = 0 !< BLACS descriptors (only used when distributed = .true.)
102 integer(blasint) :: row_context = 0 !< auxiliary linear BLACS grid context
103 integer(blasint) :: blk_context = 0 !< main rectangular BLACS grid context
104 integer(blasint) :: block_size = 0 !< ScaLAPACK block size
105 contains
106 procedure :: load => load_mapped_matrix
108 end type mappedmatrix
109
110
111 !> \brief RMT molecular data file
112 !> \author J Benda
113 !> \date 2020 - 2023
114 !>
115 !> This data structure contains data read from the molecular_data file produced by RMT_INTERFACE.
116 !> Only a subset of values needed by this program is stored in memory. This in particular
117 !> includes the inner and outer region transition dipole elements, boundary amplitudes and
118 !> angular integrals of the real spherical harmonics.
119 !>
120 !> When the code is compiled with the WITH_MMAP=1 option, the dipole matrices will not be read
121 !> into memory, but only mapped to the virtual memory.
122 !>
123 !> When the code is compiled with ScaLAPACK support, the large inner dipole matrices will be
124 !> distributed in the standard block-cyclic fashion.
125 !>
126 type moleculardata
127
128 ! inner transition dipoles storage
129 type(mappedmatrix), allocatable :: dipx(:), dipy(:), dipz(:)
130 integer(int32), allocatable :: iidipx(:), iidipy(:), iidipz(:)
131 integer(int32), allocatable :: ifdipx(:), ifdipy(:), ifdipz(:)
132
133 ! boundary amplitudes
134 type(mappedmatrix), allocatable :: wamp(:)
135 type(mappedmatrix), allocatable :: wmat2(:, :)
136
137 ! number of electrons, number of protons
138 integer(int32) :: nelc, nz
139
140 ! other data
141 real(real64) :: rmatr
142 real(real64), allocatable :: crlv(:,:,:), eig(:,:), rg(:), etarg(:), gaunt(:, :, :), r_points(:)
143 integer(int32), allocatable :: mgvns(:), stot(:), mnp1(:), nchan(:), l2p(:,:), m2p(:,:), ichl(:,:), lm_rg(:,:)
144 integer(int32), allocatable :: ltarg(:), starg(:)
145
146 end type moleculardata
147
148contains
149
150 !> \brief Read or map a matrix from file
151 !> \author J Benda
152 !> \date 2021 - 2024
153 !>
154 !> Depending on the compilation settings, either map the given section of the file to memory,
155 !> or simply allocate and read the chunk. Optionally, read and distribute the matrix in the
156 !> block-cyclic way.
157 !>
158 subroutine load_mapped_matrix (this, filename, u, offset, rows, cols)
159
160#ifdef WITH_SCALAPACK
161 use mpi
162 use mpi_gbl, only: mpi_xermsg
163
164 integer, parameter :: mpiint = kind(mpi_comm_world)
165 integer, parameter :: mpiofs = mpi_offset_kind
166
167 character(len=200) :: msg
168
169 integer(mpiint) :: ndims, gsizes(2), distrb(2), dargs(2), psizes(2), locsiz, fh, ierr, datype
170 integer(mpiint) :: comm_size, comm_rank, stat(MPI_STATUS_SIZE), mtwo = 2, dims(2)
171 integer(mpiofs) :: offs
172#endif
173
174 class(mappedmatrix), intent(inout) :: this
175 character(len=*), intent(in) :: filename
176 integer(int32), intent(in) :: u, rows, cols
177 integer(int64), intent(in) :: offset
178
179#ifdef WITH_MMAP
180 integer(int64) :: length
181#endif
182 integer(blasint) :: zero = 0, one = 1, nprow, npcol, lrows, lcols, min_block_size, max_block_size, ld, info, myprow, mypcol
183 integer(blasint) :: brows, bcols
184
185#ifdef WITH_SCALAPACK
186 if (this % distributed) then
187 call mpi_comm_size(mpi_comm_world, comm_size, ierr)
188 call assert_status('Failed to obtain world communicator size: ', int(ierr))
189 call mpi_comm_rank(mpi_comm_world, comm_rank, ierr)
190 call assert_status('Failed to obtain world communicator rank: ', int(ierr))
191
192 dims = 0
193 call mpi_dims_create(comm_size, mtwo, dims, ierr)
194 call assert_status('Failed to reshape world communicator to rectangular grid: ', int(ierr))
195 nprow = maxval(dims)
196 npcol = minval(dims)
197
198 if (npcol * nprow /= nprocs) then
199 write (msg, '(a,3(i0,a))') 'BLACS grid ', nprow, 'x', npcol, ' does not use all ', nprocs, ' processes. Fix it.'
200 call mpi_xermsg('multidip_io', 'load_mapped_matrix', trim(msg), 1, 1);
201 end if
202
203 call blacs_get(zero, zero, this % row_context)
204 call blacs_get(zero, zero, this % blk_context)
205 call blacs_gridinit(this % row_context, 'R', one, nprow * npcol)
206 call blacs_gridinit(this % blk_context, 'R', nprow, npcol)
207 call blacs_gridinfo(this % blk_context, nprow, npcol, myprow, mypcol)
208
209 min_block_size = 1
210 max_block_size = 512 ! rather arbitrary, but this particular choice coincides with a common page size (4 kiB)
211
212 this % block_size = min(rows / nprow, cols / npcol)
213 this % block_size = min(this % block_size, max_block_size)
214 this % block_size = max(this % block_size, min_block_size)
215
216 brows = rows
217 bcols = cols
218 lrows = numroc(brows, this % block_size, myprow, zero, nprow)
219 lcols = numroc(bcols, this % block_size, mypcol, zero, npcol)
220 ld = max(one, lrows)
221
222 if (int(lrows, int64)*int(lcols, int64) > huge(1_int32)) then
223 print '(a,i0,a,i0,a)', 'WARNING: Local portion of the dipole matrix has size ', lrows, ' x ', lcols, '.'
224 print '(9x,a)', 'This means that it cannot be indexed with 4-byte integers.'
225 print '(9x,a,/)', 'If reading fails, try distributing the calculation more.'
226 end if
227
228 call descinit(this % desc, brows, bcols, this % block_size, this % block_size, zero, zero, this % blk_context, ld, info)
229
230 allocate (this % mat(ld, lcols))
231
232 offs = offset - 1
233 ndims = 2
234 gsizes = [ rows, cols ]
235 distrb = [ mpi_distribute_cyclic, mpi_distribute_cyclic ]
236 dargs = [ this % block_size, this % block_size ]
237 psizes = [ nprow, npcol ]
238 locsiz = lrows * lcols
239
240 call mpi_file_open(mpi_comm_world, filename, mpi_mode_rdonly, mpi_info_null, fh, ierr)
241 call assert_status('Failed to open file for MPI-IO: ', int(ierr))
242 call mpi_type_create_darray(comm_size, comm_rank, ndims, gsizes, distrb, dargs, &
243 psizes, mpi_order_fortran, mpi_real8, datype, ierr)
244 call assert_status('Failed to define distributed array data type: ', int(ierr))
245 call mpi_type_commit(datype, ierr)
246 call assert_status('Failed to commit distributed array data type: ', int(ierr))
247 call mpi_file_set_view(fh, offs, mpi_real8, datype, 'native', mpi_info_null, ierr)
248 call assert_status('Failed to define file view: ', int(ierr))
249 call mpi_file_read_all(fh, this % mat, locsiz, mpi_real8, stat, ierr)
250 call assert_status('Failed to read from file: ', int(ierr))
251 call mpi_type_free(datype, ierr)
252 call assert_status('Failed to release distributed array data type: ', int(ierr))
253 call mpi_file_close(fh, ierr)
254 call assert_status('Failed to close file: ', int(ierr))
255 else
256#endif
257#ifdef WITH_MMAP
258 length = int(rows, int64) * int(cols, int64) * wp_bytes
259 call this % mapping % init(filename, offset - 1, length)
260 call c_f_pointer(this % mapping % ptr, this % mat, [rows, cols])
261#else
262 allocate (this % mat(rows, cols))
263 read (u) this % mat
264#endif
265#ifdef WITH_SCALAPACK
266 end if
267#endif
268
269 end subroutine load_mapped_matrix
270
271
272 !> \brief Finalize a (potentially mapped) matrix object
273 !> \author J Benda
274 !> \date 2021 - 2022
275 !>
276 !> Safely deallocates the matrix. If it has been mapped, the pointer
277 !> is only nullified; unmapping happens automatically in the desctructor
278 !> of the file_mapping type.
279 !>
280 subroutine destruct_mapped_matrix (this)
281
282 type(mappedmatrix), intent(inout) :: this
283
284#ifdef WITH_SCALAPACK
285 if (this % distributed) then
286 deallocate (this % mat)
287 else
288#endif
289#ifndef WITH_MMAP
290 if (associated(this % mat)) then
291 deallocate (this % mat)
292 end if
293#endif
294#ifdef WITH_SCALAPACK
295 end if
296#endif
297 nullify (this % mat)
298
299 end subroutine destruct_mapped_matrix
300
301
302 !> \brief Read K-matrix files
303 !> \author J Benda
304 !> \date 2020 - 2024
305 !>
306 !> Read all needed K-matrix files. These are needed to calculate photionization coefficients (Ak)
307 !> during the calculation. Also perform consistency check between the energy samples in
308 !> individual files.
309 !>
310 subroutine read_kmatrices (km, lukmt, nkset)
311
312 use mpi_gbl, only: mpi_xermsg
313
314 type(kmatrix), allocatable, intent(inout) :: km(:)
315 integer, intent(in) :: lukmt(:), nkset(:)
316
317 character(len=200) :: msg
318 integer :: i
319
320 do i = 1, size(km)
321
322 km(i) % lukmt = lukmt(i)
323 km(i) % nkset = nkset(i)
324
325 call km(i) % read_kmatrix_file
326
327 if (km(i) % nescat /= km(1) % nescat) then
328 write (msg, '(a,2(i0,a))') 'ERROR: K-matrix units ', lukmt(1), ' and ', lukmt(i), &
329 ' hold different number of scattering energies!'
330 call mpi_xermsg('multidip_io', 'read_kmatrices', trim(msg), 1, 1)
331 end if
332
333 if (any(km(i) % escat(1 : km(i) % nescat) /= km(1) % escat(1 : km(1) % nescat))) then
334 write (msg, '(a,2(i0,a))') 'ERROR: K-matrix units ', lukmt(1), ' and ', lukmt(i), &
335 ' hold different scattering energies!'
336 call mpi_xermsg('multidip_io', 'read_kmatrices', trim(msg), 1, 1)
337 end if
338
339 end do
340
341 end subroutine read_kmatrices
342
343
344 !> \brief Read K-matrix file
345 !> \author J Benda
346 !> \date 2021 - 2024
347 !>
348 !> Read metadata from a K-matrix file, count K-matrices and store the unit and dataset for
349 !> later use in retrieval of the K-matrices themselves. The K-matrices are *not* being read
350 !> into memory here to avoid exhausting RAM (particularly in parallel mode) if the K-matrices
351 !> are large.
352 !>
353 subroutine read_kmatrix_file (km)
354
355 use mpi_gbl, only: mpi_xermsg
356
357 class(kmatrix), intent(inout) :: km
358
359 integer :: k, n, ifail, key, nset, nrec, ninfo, ndata, ntarg, nvib, ndis, ie, gutot, ion
360 integer :: nopen, ndopen, nchsq, mye, nene, ierr
361 logical :: iwarn
362 real(wp) :: dE1, dE2, r, rmass, en
363 real(wp), allocatable :: kmat(:)
364 character(len=80) :: title, msg
365
366 print '(/,A,I0,A,I0,/)', 'Reading K-matrices from unit ', km % lukmt, ', set ', km % nkset
367
368 call getset(km % lukmt, km % nkset, 11, 'UNFORMATTED', ifail)
369
370 read (km % lukmt) key, nset, nrec, ninfo, ndata
371 read (km % lukmt) title
372 read (km % lukmt) km % mgvn, km % stot, gutot, ion, r, rmass
373 read (km % lukmt) ntarg, nvib, ndis, km % nchan, km % maxne
374
375 print '(2x,A,I0)', 'mgvn = ', km % mgvn
376 print '(2x,A,I0)', 'stot = ', km % stot
377 print '(2x,A,I0)', 'ntarg = ', ntarg
378 print '(2x,A,I0)', 'nvib = ', nvib
379 print '(2x,A,I0)', 'ndis = ', ndis
380 print '(2x,A,I0)', 'nchan = ', km % nchan
381 print '(2x,A,I0)', 'maxne = ', km % maxne
382
383 print '(/,2x,A,/)', 'Energy ranges:'
384
385 km % nescat = 0
386 do ie = 1, km % maxne
387 read (km % lukmt) k, n, de1, de2
388 print '(4x,I4,I8,F8.3,F8.3)', k, n, de1, de2
389 km % nescat = km % nescat + n
390 end do
391
392 nene = (km % nescat + nprocs - 1) / nprocs
393 allocate (km % escat(km % nescat), kmat(km % nchan * (km % nchan + 1)))
394
395 iwarn = .true.
396 mye = 0
397 do ie = 1, km % nescat
398 read (km % lukmt, iostat = ierr) nopen, ndopen, nchsq, en!, kmat(1:nchsq)
399 if (ierr == iostat_end) then
400 print '(/,2x,A,I0,A,I0,A)', 'WARNING: Unit ', km % lukmt, ' contains only ', ie - 1, ' K-matrices.'
401 print '(11x,A)', 'This means that CFASYM failed during RSOLVE for some energies.'
402 print '(11x,A)', 'The scattering energies MUST be consistent across symmetries!'
403 km % nescat = ie - 1
404 exit
405 else if (ierr /= 0) then
406 write (msg, '(2x,a,i0,a)') 'ERROR ', ierr, ' while reading K-matrix file.'
407 call mpi_xermsg('multidip_io', 'read_kmatrix_file', trim(msg), 1, 1)
408 end if
409 km % escat(ie) = en/2 ! Ry -> a.u.
410 if (iwarn .and. nopen < km % nchan) then
411 print '(/,2x,A,I0,A)', 'WARNING: Unit ', km % lukmt, ' contains only open-open subset of the full K-matrix.'
412 print '(11x,A)', 'Use IKTYPE = 1 in RSOLVE to save full K-matrices.'
413 print '(11x,A)', 'You may get spurious below-threshold noise.'
414 iwarn = .false.
415 end if
416 end do
417
418 deallocate (kmat)
419
420 end subroutine read_kmatrix_file
421
422
423 !> \brief Reset I/O pointer to start of K-matrices
424 !> \author J Benda
425 !> \date 2021
426 !>
427 !> Rewind the unit to the start of the associated dataset and read through to the very beginning of the actual
428 !> K-matrix data. Also skip the given number of leading K-matrices. This prepares the file for the subsequent
429 !> calls to `get_kmatrix`.
430 !>
431 subroutine reset_kmatrix_position (km, skip)
432
433 class(kmatrix), intent(in) :: km
434 integer, optional, intent(in) :: skip
435
436 integer :: i
437
438 ! rewind to the beginning of the K-matrix unit
439 call getset(km % lukmt, km % nkset, 11, 'UNFORMATTED', i)
440
441 read (km % lukmt)
442 read (km % lukmt)
443 read (km % lukmt)
444 read (km % lukmt)
445
446 do i = 1, km % maxne
447 read (km % lukmt)
448 end do
449
450 if (present(skip)) then
451 do i = 1, skip
452 read (km % lukmt)
453 end do
454 end if
455
456 end subroutine reset_kmatrix_position
457
458
459 !> \brief Read single K-matrix from the K-matrix file
460 !> \author J Benda
461 !> \date 2021 - 2024
462 !>
463 !> Assuming that the K-matrix file associated with this object is correctly positioned, read the next K-matrix record.
464 !> Symmetrize the K-matrix and store it into the allocatable two-dimensional array `kmat` (re/allocate as necessary).
465 !>
466 subroutine get_kmatrix (km, kmat, skip)
467
468 use mpi_gbl, only: mpi_xermsg
469
470 class(kmatrix), intent(in) :: km
471 real(wp), allocatable, intent(inout) :: kmat(:, :)
472 integer, optional, intent(in) :: skip
473
474 real(wp), allocatable :: buffer(:)
475 character(len=200) :: msg
476
477 real(wp) :: en
478 integer :: a, b, c, i, ierr, nopen, ndopen, nchsq
479
480 allocate (buffer(km % nchan * (km % nchan + 1)))
481
482 read (km % lukmt, iostat = ierr) nopen, ndopen, nchsq, en, buffer(1:nchsq)
483
484 if (ierr == iostat_end) then
485 write (msg, '(a,i0,a)') 'ERROR: Unit ', km % lukmt, ' ended while reading K-matrix!'
486 call mpi_xermsg('multidip_io', 'get_kmatrix', trim(msg), 1, 1)
487 else if (ierr /= 0) then
488 write (msg, '(2x,a,2(i0,a))') 'ERROR ', ierr, ' while reading K-matrix file unit ', km % lukmt, '!'
489 call mpi_xermsg('multidip_io', 'get_kmatrix', trim(msg), 1, 1)
490 end if
491
492 if (allocated(kmat)) then
493 if (size(kmat, 1) /= km % nchan .or. size(kmat, 2) /= km % nchan) then
494 deallocate (kmat)
495 end if
496 end if
497
498 if (.not. allocated(kmat)) then
499 allocate (kmat(km % nchan, km % nchan))
500 end if
501
502 c = 0
503 do a = 1, nopen
504 do b = 1, a
505 c = c + 1
506 kmat(a, b) = buffer(c)
507 kmat(b, a) = buffer(c)
508 end do
509 end do
510
511 if (present(skip)) then
512 do i = 1, skip
513 read (km % lukmt, iostat = ierr) ! ignore EOF
514 end do
515 end if
516
517 end subroutine get_kmatrix
518
519
520 !> \brief Read wave function coefficients from files
521 !> \author J Benda
522 !> \date 2020
523 !>
524 !> Read wave function (Ak) coefficients from an unformatted file written by RSOLVE. This is an optional
525 !> functionality meant for dabugging. It is recommended that the Ak coefficients are calculated by the
526 !> present program instead to avoid storing large datasets on disk or in memory.
527 !>
528 subroutine read_wfncoeffs (ak, lusct)
529
530 type(scatakcoeffs), allocatable, intent(inout) :: ak(:)
531 integer, intent(in) :: lusct(:)
532 integer :: i
533
534 do i = 1, size(ak)
535 ak(i) % lusct = lusct(i)
536 call ak(i) % read_wfncoeffs_file
537 end do
538
539 end subroutine read_wfncoeffs
540
541
542 !> \brief Read inner bound wave function coefficients
543 !> \authors J Benda
544 !> \date 2023
545 !>
546 !> Read the inner region expansion coefficients of the bound state as calculated by BOUND.
547 !>
548 !> \param[out] bnd Vector of inner region expansion coefficients to fill.
549 !> \param[out] Ei Bound state energy as calculated by BOUND.
550 !> \param[in] lubnd File unit number with the (unformatted) BOUND output.
551 !> \param[in] mgvn0 Expected MGVN (to check).
552 !> \param[in] stot0 Expected total spin (to check).
553 !>
554 subroutine read_bndcoeffs (bnd, Ei, lubnd, mgvn0, stot0)
555
556 real(wp), intent(out) :: bnd(:), Ei
557 integer, intent(in) :: lubnd, mgvn0, stot0
558
559 real(wp), allocatable :: xvec(:), etot(:), vtemp(:)
560 real(wp) :: rr
561 integer :: nbset, nchan, mgvn, stot, gutot, nstat, nbound, iprint, iwrite, ifail
562 character(len=11) :: bform
563
564 bform = 'unformatted'
565 nbset = 1
566 iprint = 0
567 iwrite = output_unit
568
569 open (lubnd, action = 'read', form = bform)
570
571 call readbh(lubnd, nbset, nchan, mgvn, stot, gutot, nstat, nbound, rr, bform, iprint, iwrite, ifail)
572
573 if (mgvn /= mgvn0 .or. stot /= stot0) then
574 print '(*(a,i0))', 'ERROR: Bound coefficients at unit ', lubnd, ' are for MGVN = ', mgvn, ', STOT = ', stot, &
575 ', but I am looking for MGVN = ', mgvn0, ', STOT = ', stot0
576 stop 1
577 end if
578
579 nbound = 1 ! read just the first bound state, ignore the rest
580 allocate (xvec(nbound*nchan), vtemp(nbound), etot(nbound))
581
582 call readbc(nstat, etot, vtemp, bnd, nbound, nchan, xvec)
583
584 ei = etot(1)
585
586 close (lubnd)
587
588 end subroutine read_bndcoeffs
589
590
591 !> \brief Read wave function coefficients for a single symmetry from a file
592 !> \author J Benda
593 !> \date 2021
594 !>
595 subroutine read_wfncoeffs_file (ak)
596
597 class(scatakcoeffs), intent(inout) :: ak
598
599 real(wp) :: rr
600 integer :: j, keysc, nset, nrec, ninfo, ndata, stot, gutot, nscat
601 character(len=80) :: header
602
603 print '(/,A,I0,/)', 'Reading wave function coefficients from unit ', ak % lusct
604
605 open (ak % lusct, status = 'old', action = 'read', form = 'unformatted')
606
607 read (ak % lusct) keysc, nset, nrec, ninfo, ndata
608 read (ak % lusct) header
609 read (ak % lusct) nscat, ak % mgvn, stot, gutot, ak % nstat, ak % nchan, ak % nesc
610
611 print '(2x,A,I0)', 'mgvn = ', ak % mgvn
612 print '(2x,A,I0)', 'stot = ', stot
613 print '(2x,A,I0)', 'nstat = ', ak % nstat
614 print '(2x,A,I0)', 'nchan = ', ak % nchan
615 print '(2x,A,I0)', 'nesc = ', ak % nesc
616
617 read (ak % lusct) rr
618
619 if (allocated(ak % ichl)) deallocate (ak % ichl)
620 if (allocated(ak % lvchl)) deallocate (ak % lvchl)
621 if (allocated(ak % mvchl)) deallocate (ak % mvchl)
622 if (allocated(ak % evchl)) deallocate (ak % evchl)
623
624 allocate (ak % ichl(ak % nchan))
625 allocate (ak % lvchl(ak % nchan))
626 allocate (ak % mvchl(ak % nchan))
627 allocate (ak % evchl(ak % nchan))
628
629 read (ak % lusct) ak % ichl, ak % lvchl, ak % mvchl, ak % evchl
630
631 print '(/,2x,A)', 'channel table'
632 do j = 1, ak % nchan
633 print '(2x,4I6,F15.7)', j, ak % ichl(j), ak % lvchl(j), ak % mvchl(j), ak % evchl(j)
634 end do
635
636 close (ak % lusct)
637
638 end subroutine read_wfncoeffs_file
639
640
641 !> \brief Reset I/O pointer to start of Ak-coeffs
642 !> \author J Benda
643 !> \date 2021
644 !>
645 !> Rewind the unit to the start of the associated dataset and read through to the very beginning of the actual
646 !> Ak-coeffs data. Also skip the given number of leading Ak-coeffs. This prepares the file for the subsequent
647 !> calls to `get_wfncoeffs`.
648 !>
649 subroutine reset_wfncoeffs_position (ak, skip)
650
651 class(scatakcoeffs), intent(in) :: ak
652 integer, optional, intent(in) :: skip
653
654 integer :: i, ich
655
656 ! rewind to the beginning of the K-matrix unit
657 call getset(ak % lusct, 1, 88, 'UNFORMATTED', i)
658
659 read (ak % lusct)
660 read (ak % lusct)
661 read (ak % lusct)
662 read (ak % lusct)
663 read (ak % lusct)
664
665 if (present(skip)) then
666 do i = 1, skip
667 do ich = 1, ak % nchan
668 read (ak % lusct)
669 read (ak % lusct)
670 end do
671 end do
672 end if
673
674 end subroutine reset_wfncoeffs_position
675
676
677 !> \brief Read single Ak-matrix from the Ak-coeffs file
678 !> \author J Benda
679 !> \date 2021 - 2024
680 !>
681 !> Assuming that the Ak-coeffs file associated with this object is correctly positioned, read the next Ak-matrix record.
682 !> Also returns the scattering energy in atomic units.
683 !>
684 subroutine get_wfncoeffs (ak, E, Re_Ak, Im_Ak, skip)
685
686 use mpi_gbl, only: mpi_xermsg
687
688 class(scatakcoeffs), intent(in) :: ak
689 real(wp), allocatable, intent(inout) :: Re_Ak(:, :), Im_Ak(:, :)
690 integer, optional, intent(in) :: skip
691 real(wp), intent(inout) :: E
692
693 character(len=250) :: msg, iomsg
694
695 integer :: i, ich, ierr
696
697 do ich = 1, ak % nchan
698 read (ak % lusct, iostat = ierr, iomsg = iomsg) e, i, re_ak(:, ich)
699 read (ak % lusct, iostat = ierr, iomsg = iomsg) e, i, im_ak(:, ich)
700 if (ierr == iostat_end) then
701 write (msg, '(2x,a,i0,a)') 'ERROR: Unit ', ak % lusct, ' ended while reading Ak-coeffs!'
702 call mpi_xermsg('multidip_io', 'get_wfncoeffs', trim(msg), 1, 1)
703 else if (ierr /= 0) then
704 write (msg, '(2x,a,2(i0,a),a)') 'ERROR ', ierr, ' while reading Ak-coeffs file unit ', ak % lusct, ': ', iomsg
705 call mpi_xermsg('multidip_io', 'get_wfncoeffs', trim(msg), 1, 1)
706 end if
707 end do
708
709 if (present(skip)) then
710 do i = 1, skip
711 do ich = 1, ak % nchan
712 read (ak % lusct, iostat = ierr) ! ignore EOF
713 read (ak % lusct, iostat = ierr) ! ignore EOF
714 end do
715 end do
716 end if
717
718 end subroutine get_wfncoeffs
719
720
721 !> \brief Read RMT molecular_data file
722 !> \author J Benda
723 !> \date 2020 - 2023
724 !>
725 !> Read data from the RMT molecular_data file. This includes in particular both the inner and outer region
726 !> transition dipole elements, as well as for instance Gaunt angular integrals for all partial waves used.
727 !>
728 subroutine read_molecular_data (moldat, filename, mpiio, read_wmat2)
729
731
732 type(moleculardata), intent(inout) :: moldat
733 character(*), intent(in) :: filename
734 logical, intent(in) :: mpiio, read_wmat2
735
736 integer(int32), allocatable :: nconat(:)
737 real(real64), allocatable :: cf(:, :, :)
738
739 integer(int32) :: i, j, u, s, s1, s2, ntarg, nrg, lrang2, lamax, inast, nchmx, nstmx, lmaxp1, nfdm
740 integer(int32) :: lrgl, nspn, npty
741 integer(int64) :: offset, length
742 real(real64) :: bbloch, delta_r
743
744 open (newunit = u, file = filename, access = 'stream', status = 'old', action = 'read')
745
746 print '(/,3A,/)', 'Reading file "', filename, '"'
747
748 read (u) s, s1, s2
749 length = int(s1, int64) * int(s2, int64) * wp_bytes
750 allocate (moldat % iidipy(s), moldat % ifdipy(s), moldat % dipy(s))
751 read (u) moldat % iidipy, moldat % ifdipy
752 inquire (u, pos = offset)
753 do i = 1, s
754 moldat % dipy(i) % distributed = mpiio
755 call moldat % dipy(i) % load(filename, u, offset, s1, s2)
756 offset = offset + length
757 end do
758 print '(2x,A,*(1x,I0))', '[m = -1] iidip =', moldat % iidipy
759 print '(2x,A,*(1x,I0))', ' ifdip =', moldat % ifdipy
760
761 if (check_dipoles) then
762 do i = 1, s
763 if (sum(abs(moldat % dipy(i) % mat)) == 0) then
764 print '(/,2x,A,/)', 'WARNING: Norm of some Y dipoles is zero. Something went wrong in CDENPROP?'
765 end if
766 end do
767 end if
768
769 read (u, pos = offset) s, s1, s2
770 length = int(s1, int64) * int(s2, int64) * wp_bytes
771 allocate (moldat % iidipz(s), moldat % ifdipz(s), moldat % dipz(s))
772 read (u) moldat % iidipz, moldat % ifdipz
773 inquire (u, pos = offset)
774 do i = 1, s
775 moldat % dipz(i) % distributed = mpiio
776 call moldat % dipz(i) % load(filename, u, offset, s1, s2)
777 offset = offset + length
778 end do
779 print '(2x,A,*(1x,I0))', '[m = 0] iidip =', moldat % iidipz
780 print '(2x,A,*(1x,I0))', ' ifdip =', moldat % ifdipz
781
782 if (check_dipoles) then
783 do i = 1, s
784 if (sum(abs(moldat % dipz(i) % mat)) == 0) then
785 print '(/,2x,A,/)', 'WARNING: Norm of some Z dipoles is zero. Something went wrong in CDENPROP?'
786 end if
787 end do
788 end if
789
790 read (u, pos = offset) s, s1, s2
791 length = int(s1, int64) * int(s2, int64) * wp_bytes
792 allocate (moldat % iidipx(s), moldat % ifdipx(s), moldat % dipx(s))
793 read (u) moldat % iidipx, moldat % ifdipx
794 inquire (u, pos = offset)
795 do i = 1, s
796 moldat % dipx(i) % distributed = mpiio
797 call moldat % dipx(i) % load(filename, u, offset, s1, s2)
798 offset = offset + length
799 end do
800 print '(2x,A,*(1x,I0))', '[m = +1] iidip =', moldat % iidipx
801 print '(2x,A,*(1x,I0))', ' ifdip =', moldat % ifdipx
802
803 if (check_dipoles) then
804 do i = 1, s
805 if (sum(abs(moldat % dipx(i) % mat)) == 0) then
806 print '(/,2x,A,/)', 'WARNING: Norm of some X dipoles is zero. Something went wrong in CDENPROP?'
807 end if
808 end do
809 end if
810
811 read (u, pos = offset) ntarg
812 print '(/,2x,A,I0)', 'ntarg = ', ntarg
813 allocate (moldat % crlv(ntarg, ntarg, 3))
814 read (u) moldat % crlv
815
816 read (u) nrg
817 allocate (moldat % rg(nrg), moldat % lm_rg(6, nrg))
818 read (u) moldat % rg, moldat % lm_rg
819
820 read (u) moldat % nelc, moldat % nz, lrang2, lamax, ntarg, inast, nchmx, nstmx, lmaxp1
821 read (u) moldat % rmatr, bbloch
822 allocate (moldat % etarg(ntarg), moldat % ltarg(ntarg), moldat % starg(ntarg))
823 read (u) moldat % etarg, moldat % ltarg, moldat % starg
824 read (u) nfdm, delta_r
825 allocate (moldat % r_points(nfdm + 1))
826 read (u) moldat % r_points
827 print '(2x,A,*(1x,F8.3))', 'sampling radii:', moldat % r_points
828
829 print '(/,2x,A)', 'number of inner eigenstates per symmetry'
830
831 allocate (nconat(ntarg), moldat % l2p(nchmx, inast), moldat % m2p(nchmx, inast), moldat % eig(nstmx, inast), &
832 moldat % wamp(inast), cf(nchmx, nchmx, lamax), moldat % ichl(nchmx, inast), &
833 moldat % mgvns(inast), moldat % stot(inast), moldat % nchan(inast), moldat % mnp1(inast), &
834 moldat % wmat2(nfdm, inast))
835
836 inquire (u, pos = offset)
837
838 length = int(nchmx, int64) * int(nstmx, int64) * wp_bytes
839
840 do i = 1, inast
841
842 read (u, pos = offset) lrgl, nspn, npty, moldat % nchan(i), moldat % mnp1(i)
843 moldat % mgvns(i) = lrgl - 1
844 moldat % stot(i) = nspn
845 print '(4x,I3,I8)', moldat % mgvns(i), moldat % mnp1(i)
846 read (u) nconat, moldat % l2p(:, i), moldat % m2p(:, i)
847 read (u) moldat % eig(:, i)
848 inquire (u, pos = offset)
849 call moldat % wamp(i) % load(filename, u, offset, nchmx, nstmx)
850 offset = offset + length
851 read (u, pos = offset) cf, moldat % ichl(:, i)
852 read (u) s1, s2
853 inquire (u, pos = offset)
854
855 ! read or map wmat2 only when explicitly required; it is large and used only for debugging
856 if (read_wmat2) then
857 do j = 1, nfdm
858 call moldat % wmat2(j, i) % load(filename, u, offset, s1, s2)
859 offset = offset + int(s1, int64) * int(s2, int64) * wp_bytes
860 end do
861 else
862 offset = offset + int(s1, int64) * int(s2, int64) * nfdm * wp_bytes
863 end if
864
865 end do
866
867 do i = 1, inast
868 print '(/,2x,A,I0,A,I0,A)', 'channels in symmetry #', i, ' (IRR ', moldat % mgvns(i), '):'
869 do j = 1, moldat % nchan(i)
870 print '(2x,I4,A,I6,I3,SP,I6)', j, ':', moldat % ichl(j, i), moldat % l2p(j, i), moldat % m2p(j, i)
871 if (moldat % ichl(j, i) > ntarg) then
872 print '(4(A,I0),A)', 'Warning: Channel ', j, ' in symmetry ', moldat % mgvns(i), ' is coupled to state ', &
873 moldat % ichl(j, i), ', which is beyond the number of targets ', ntarg, ' (bug in mpi-scatci!)'
874 moldat % ichl(j, i) = ntarg
875 end if
876 end do
877 end do
878
879 close (u)
880
881 call setup_angular_integrals(moldat)
882
883 end subroutine read_molecular_data
884
885
886 !> \brief Read ion transition dipoles
887 !> \author J Benda
888 !> \date 2021
889 !>
890 !> Read ion target transition dipoles from a DENPROP file.
891 !>
892 subroutine read_target_properties (lutarg, prop, etarg)
893
894 integer, intent(in) :: lutarg
895 real(wp), allocatable, intent(inout) :: etarg(:), prop(:,:,:)
896
897 integer :: i, key, set, nrec, nnuc, ntarg, nmom, itarg, jtarg, imgvn, jmgvn, l, m, isw
898 real(wp) :: x, rmoi(3), dum(7)
899 logical :: is_open
900
901 print '(/,A)', 'Reading target transition dipoles'
902
903 inquire(lutarg, opened = is_open)
904 if (.not. is_open) then
905 open (lutarg, action = 'read')
906 end if
907 call readmh(lutarg, key, set, nrec, nnuc, ntarg, nmom, isw, rmoi)
908 if (key /= 6) then
909 print '(/,A,I0,A)', 'ERROR: Key ', key, ' is not DENPROP property file, aborting.'
910 stop 1
911 end if
912 print '(/,1x,A,I0)', 'Number of records: ', nrec
913 print '(1x,A,I0)', 'Number of nuclei: ', nnuc
914 print '(1x,A,I0)', 'Number of targets: ', ntarg
915 print '(1x,A,I0)', 'Number of properties: ', nmom
916 do i = 1, nnuc
917 read (lutarg, *) key
918 if (key /= 8) then
919 print '(/,A,I0,A)', 'ERROR: Key ', key, ' is not nuclear definition, aborting.'
920 stop 1
921 end if
922 end do
923 allocate (etarg(ntarg))
924 do i = 1, ntarg
925 read (lutarg, *) key, dum, etarg(i)
926 if (key /= 5) then
927 print '(/,A,I0,A)', 'ERROR: Key ', key, ' is not target definition, aborting.'
928 stop 1
929 end if
930 end do
931 allocate (prop(ntarg, ntarg, 3))
932 prop = 0
933 do i = 1, nmom
934 read (lutarg, *) key, itarg, imgvn, jtarg, jmgvn, nnuc, l, m, x
935 if (key /= 1) then
936 print '(/,A,I0,A)', 'ERROR: Key ', key, ' is not property definition, aborting.'
937 stop 1
938 end if
939 if (l == 1) then
940 prop(itarg, jtarg, m + 2) = x
941 prop(jtarg, itarg, m + 2) = x
942 end if
943 end do
944 if (.not. is_open) then
945 close (lutarg)
946 end if
947
948 end subroutine read_target_properties
949
950
951 !> \brief Store the angular integrals in a more convenient shape
952 !> \author J Benda
953 !> \date 2020 - 2024
954 !>
955 !> Convert the Gaunt integral storage from a list to an easily addressable matrix.
956 !>
957 subroutine setup_angular_integrals (moldat)
958
959 use mpi_gbl, only: mpi_xermsg
960
961 type(moleculardata), intent(inout) :: moldat
962
963 integer :: maxl, i, l1, m1, l2, m2, l3, m3
964
965 maxl = max(0_int32, maxval(moldat % lm_rg))
966
967 allocate (moldat % gaunt((maxl + 1)**2, (maxl + 1)**2, -1:1))
968
969 moldat % gaunt = 0
970
971 do i = 1, size(moldat % rg)
972 l1 = moldat % lm_rg(1, i); m1 = moldat % lm_rg(2, i)
973 l2 = moldat % lm_rg(3, i); m2 = moldat % lm_rg(4, i)
974 l3 = moldat % lm_rg(5, i); m3 = moldat % lm_rg(6, i)
975 if (l3 /= 1 .or. abs(m3) > 1) then
976 call mpi_xermsg('multidip_io', 'setup_angular_integrals', &
977 'Unexpected layout of the angular integral storage in molecular_data', 1, 1)
978 end if
979 moldat % gaunt(l1*(l1+1) + m1 + 1, l2*(l2+1) + m2 + 1, m3) = moldat % rg(i)
980 end do
981
982 end subroutine setup_angular_integrals
983
984
985 !> \brief Return dipole transition descriptors
986 !> \author J Benda
987 !> \date 2020
988 !>
989 !> Based on the Cartesian component index I = 1, 2, 3, (re)allocate the arrays 'iidip' and 'ifdip'
990 !> and fill them with initial/final irreducible representations pairs for dipole matrices present
991 !> in the molecular_data file structure 'moldat'.
992 !>
993 subroutine get_diptrans (moldat, I, iidip, ifdip)
994
995 type(moleculardata), intent(in) :: moldat
996 integer, intent(in) :: I
997 integer, allocatable, intent(inout) :: iidip(:), ifdip(:)
998
999 if (allocated(iidip)) deallocate (iidip)
1000 if (allocated(ifdip)) deallocate (ifdip)
1001
1002 select case (i)
1003 case (1)
1004 iidip = moldat % iidipx
1005 ifdip = moldat % ifdipx
1006 case (2)
1007 iidip = moldat % iidipy
1008 ifdip = moldat % ifdipy
1009 case (3)
1010 iidip = moldat % iidipz
1011 ifdip = moldat % ifdipz
1012 end select
1013
1014 end subroutine get_diptrans
1015
1016
1017 !> \brief Multiply by dipole matrix
1018 !> \author J Benda
1019 !> \date 2020 - 2022
1020 !>
1021 !> Multiply matrix 'X' by the dipole matrix corresponding to dipole component 'I' and transition index 's'.
1022 !> Store the result in matrix 'Y'. Use 'nn' rows of 'X', and 'nf' rows of 'Y'.
1023 !>
1024 !> The matrix argument X and Y have the following one-dimensional distribution among processes:
1025 !> \verbatim
1026 !> +---+---+---+---+
1027 !> | | | | |
1028 !> | | | | |
1029 !> | 0 | 1 | 2 | 3 |
1030 !> | | | | |
1031 !> | | | | |
1032 !> +---+---+---+---+
1033 !> \endverbatim
1034 !> However, when ScaLAPACK multiplication is desired, the arrays need to be redistributed into the standard
1035 !> two-dimensional block-cyclic form
1036 !> \verbatim
1037 !> +-+-+-+-+-+-+-+-+
1038 !> |0|1|2|3|0|1|2|3|
1039 !> +-+-+-+-+-+-+-+-+
1040 !> |1|2|3|0|1|2|3|0|
1041 !> +-+-+-+-+-+-+-+-+
1042 !> |2|3|0|1|2|3|0|1|
1043 !> +-+-+-+-+-+-+-+-+
1044 !> |3|0|1|2|3|0|1|2|
1045 !> +-+-+-+-+-+-+-+-+
1046 !> \endverbatim
1047 !> and back. This is accomplished by the subroutine `pdgemr2d`. Note however, that this subroutine is prone
1048 !> to failure when the matrices exceed 4-byte addressable number of elements. Working with such matrices then
1049 !> requires patched version of ScaLAPACK that use long integers internally.
1050 !>
1051 subroutine apply_dipole_matrix (moldat, component, irrpair, transp, nf, nn, X, Y)
1052
1053 use multidip_params, only: rone, rzero
1054 use multidip_special, only: blas_dgemm => dgemm
1055
1056 integer(blasint), parameter :: one = 1, zero = 0
1057
1058 type(moleculardata), intent(in) :: moldat
1059 integer, intent(in) :: component, irrpair, nf, nn
1060 character(len=1), intent(in) :: transp
1061 real(wp), intent(inout) :: X(:, :, :), Y(:, :, :)
1062
1063 integer(blasint) :: lda, ldb, ldc, m, n, k, info, Xdesc(9), Xdesc2D(9), Ydesc(9), Ydesc2D(9), desc(9), gn
1064 integer(blasint) :: Xlocr, Xlocc, Ylocr, Ylocc, ldX, ldY, rctx, bctx, rbksz, cbksz, myprow, mypcol, nprow, npcol
1065 logical :: dist
1066
1067 real(wp), allocatable :: Xloc(:, :), Yloc(:, :)
1068 real(wp), pointer :: dips(:, :)
1069
1070 ldb = size(x, 1)
1071 ldc = size(y, 1)
1072
1073 m = nf
1074 n = size(x, 2) * size(x, 3)
1075 k = nn
1076
1077 ! pick the correct inner dipole block (or its local portion if MPI-IO was used)
1078 select case (component)
1079 case (1)
1080 dist = moldat % dipx(irrpair) % distributed
1081 desc = moldat % dipx(irrpair) % desc
1082 rctx = moldat % dipx(irrpair) % row_context
1083 bctx = moldat % dipx(irrpair) % blk_context
1084 lda = size(moldat % dipx(irrpair) % mat, 1)
1085 dips => moldat % dipx(irrpair) % mat(:, :)
1086 case (2)
1087 dist = moldat % dipy(irrpair) % distributed
1088 desc = moldat % dipy(irrpair) % desc
1089 rctx = moldat % dipy(irrpair) % row_context
1090 bctx = moldat % dipy(irrpair) % blk_context
1091 lda = size(moldat % dipy(irrpair) % mat, 1)
1092 dips => moldat % dipy(irrpair) % mat(:, :)
1093 case (3)
1094 dist = moldat % dipz(irrpair) % distributed
1095 desc = moldat % dipz(irrpair) % desc
1096 rctx = moldat % dipz(irrpair) % row_context
1097 bctx = moldat % dipz(irrpair) % blk_context
1098 lda = size(moldat % dipz(irrpair) % mat, 1)
1099 dips => moldat % dipz(irrpair) % mat(:, :)
1100 end select
1101
1102#ifdef WITH_SCALAPACK
1103 if (dist) then
1104 ! total number of columns in X, Y (summed over all parallel images)
1105 gn = n * nprocs
1106
1107 ! get information about the BLACS process grid
1108 call blacs_gridinfo(bctx, nprow, npcol, myprow, mypcol)
1109
1110 ! set up descriptors for the MULTIDIP distribution of vectors X and Y (round-robin in energies)
1111 call descinit(xdesc, k, gn, ldb, n, zero, zero, rctx, ldb, info)
1112 call assert_status('Failed to set up X BLACS descriptor: ', int(info))
1113 call descinit(ydesc, m, gn, ldc, n, zero, zero, rctx, ldc, info)
1114 call assert_status('Failed to set up Y BLACS descriptor: ', int(info))
1115
1116 ! pick a reasonable block size for good load balancing (aim at one ScaLAPACK block per process at least)
1117 rbksz = clip(min(nf, nn) / int(nprow), 1, 64)
1118 cbksz = clip(int(gn / npcol), 1, 64)
1119
1120 ! find out the number of rows and columns in the local portion of the redistributed X matrix
1121 xlocr = numroc(k, rbksz, myprow, zero, nprow); ldx = max(one, xlocr)
1122 xlocc = numroc(gn, cbksz, mypcol, zero, npcol)
1123
1124 ! find out the number of rows and columns in the local portion of the redistributed Y matrix
1125 ylocr = numroc(m, rbksz, myprow, zero, nprow); ldy = max(one, ylocr)
1126 ylocc = numroc(gn, cbksz, mypcol, zero, npcol)
1127
1128 ! allocate the local portions of the redistributed X and Y matrices
1129 allocate (xloc(ldx, xlocc), yloc(ldy, ylocc))
1130
1131 ! set up descriptors for the ScaLAPACK distribution of vectors X and Y (proper two-dimensional block-cyclic)
1132 call descinit(xdesc2d, k, gn, rbksz, cbksz, zero, zero, bctx, ldx, info)
1133 call assert_status('Failed to set up X2D BLACS descriptor: ', int(info))
1134 call descinit(ydesc2d, m, gn, rbksz, cbksz, zero, zero, bctx, ldy, info)
1135 call assert_status('Failed to set up Y2D BLACS descriptor: ', int(info))
1136
1137 ! redistribute X -> Xloc; multiply; redistribute Yloc -> Y
1138 call pdgemr2d(k, gn, x, one, one, xdesc, xloc, one, one, xdesc2d, bctx)
1139 call pdgemm(transp, 'N', m, gn, k, rone, dips, one, one, desc, xloc, one, one, xdesc2d, rzero, yloc, one, one, ydesc2d)
1140 call pdgemr2d(m, gn, yloc, one, one, ydesc2d, y, one, one, ydesc, bctx)
1141 else
1142#endif
1143 call blas_dgemm(transp, 'N', m, n, k, rone, dips, lda, x, ldb, rzero, y, ldc)
1144#ifdef WITH_SCALAPACK
1145 end if
1146#endif
1147
1148 end subroutine apply_dipole_matrix
1149
1150
1151 !> \brief Check for error code
1152 !> \author J Benda
1153 !> \date 2022 - 2024
1154 !>
1155 !> If the provided error code is non-zero, print the error message and abort.
1156 !>
1157 subroutine assert_status (message, errcode)
1158
1159 use mpi_gbl, only: mpi_xermsg
1160
1161 character(len=*), intent(in) :: message
1162 integer, intent(in) :: errcode
1163
1164 if (errcode /= 0) then
1165 call mpi_xermsg('multidip_io', 'assert_status', trim(message), errcode, 1)
1166 end if
1167
1168 end subroutine assert_status
1169
1170
1171 !> \brief Clamp value in range
1172 !> \author J Benda
1173 !> \date 2022
1174 !>
1175 integer function clip (x, a, b) result (y)
1176
1177 integer :: x, a, b
1178
1179 y = max(a, min(x, b))
1180
1181 end function clip
1182
1183
1184 !> \brief Multiply by boundary amplitudes
1185 !> \author J Benda
1186 !> \date 2020 - 2023
1187 !>
1188 !> Multiply real matrix X by the matrix of boundary amplitudes and store the result into the real
1189 !> matrix Y. When transp is 'T', then the elements of the matrix X are expected to correspond to outer
1190 !> region channels, while the elements of the matrix Y to the inner region states. Otherwise,
1191 !> the opposite is assumed.
1192 !>
1193 subroutine apply_boundary_amplitudes (moldat, irr, transp, X, Y)
1194
1195 use multidip_special, only: blas_dgemm => dgemm
1196
1197 type(moleculardata), intent(in) :: moldat
1198 integer, intent(in) :: irr
1199 character(len=1), intent(in) :: transp
1200 real(wp), intent(inout) :: X(:,:), Y(:,:)
1201
1202 integer(blasint) :: m, n, k, lda, ldb, ldc
1203 real(wp) :: alpha = 1, beta = 0
1204 real(wp), pointer :: wamp(:, :)
1205
1206 if (transp == 'T') then
1207 m = moldat % mnp1(irr)
1208 n = int(size(x, 2), blasint)
1209 k = moldat % nchan(irr)
1210 else
1211 m = moldat % nchan(irr)
1212 n = int(size(x, 2), blasint)
1213 k = moldat % mnp1(irr)
1214 end if
1215
1216 lda = size(moldat % wamp(irr) % mat, 1)
1217 ldb = size(x, 1)
1218 ldc = size(y, 1)
1219
1220 if (lda < merge(m, k, transp == 'N') .or. ldb < k .or. ldc < m) then
1221 print '(a)', 'ERROR: Incompatible matrix dimensions in apply_boundary_amplitudes'
1222 print '(7x,a,6(", ",a,1x,i0))', transp, 'm =', m, 'n =', n, 'k =', k, 'lda =', lda, 'ldb =', ldb, 'ldc =', ldc
1223 stop 1
1224 end if
1225
1226 wamp => moldat % wamp(irr) % mat(:, :)
1227
1228 call blas_dgemm(transp, 'N', m, n, k, alpha, wamp, lda, x, ldb, beta, y, ldc)
1229
1230 end subroutine apply_boundary_amplitudes
1231
1232
1233 !> \brief Scale boundary amplitudes matrix by a diagonal matrix
1234 !> \authors J Benda
1235 !> \date 2020 - 2024
1236 !>
1237 !> Multiply boundary amplitudes for each inner region state by an element of the provided vector 'v'.
1238 !> Writes the result into 'vw'.
1239 !>
1240 subroutine scale_boundary_amplitudes (moldat, irr, v, vw)
1241
1242 type(moleculardata), intent(in) :: moldat
1243 integer, intent(in) :: irr
1244 real(wp), intent(in) :: v(:)
1245 real(wp), intent(inout) :: vw(:, :)
1246
1247 integer :: k, nstat, p, nchan
1248
1249 nstat = moldat % mnp1(irr)
1250 nchan = moldat % nchan(irr)
1251
1252 !$omp parallel default(none) private(p, k) firstprivate(nchan, nstat, irr) shared(vw, v, moldat)
1253 !$omp do collapse(2)
1254 do p = 1, nchan
1255 do k = 1, nstat
1256 vw(k, p) = v(k) * moldat % wamp(irr) % mat(p, k)
1257 end do
1258 end do
1259 !$omp end do
1260 !$omp end parallel
1261
1262 end subroutine scale_boundary_amplitudes
1263
1264
1265 !> \brief Write photoelectron energies to file
1266 !> \author J Benda
1267 !> \date 2023
1268 !>
1269 !> Write the column list of photoelectron scattering energies to file. Atomic units are used.
1270 !>
1271 subroutine write_energy_grid (escat)
1272
1273 real(wp), intent(in) :: escat(:)
1274
1275 integer :: u
1276
1277 open (newunit = u, file = 'pe_energies.txt', action = 'write', form = 'formatted')
1278 write (u, '(E25.15)') escat
1279 close (u)
1280
1281 end subroutine write_energy_grid
1282
1283
1284 !> \brief Write partial wave moments
1285 !> \author J Benda
1286 !> \date 2020 - 2024
1287 !>
1288 !> Produce tables with the multi-photon ionisation matrix elements per partial wave.
1289 !>
1290 subroutine write_partial_wave_moments (moldat, M, nesc, suffix)
1291
1292 use mpi_gbl, only: mpi_reduce_inplace_sum_wp
1293
1294 type(moleculardata), intent(in) :: moldat
1295 character(len=*), intent(in) :: suffix
1296 integer, intent(in) :: nesc
1297 complex(wp), allocatable, intent(in) :: M(:, :, :)
1298
1299 complex(wp), allocatable :: val(:)
1300 real(wp), allocatable :: reval(:), imval(:)
1301
1302 integer :: ie, irr, mgvn, u, ichan, ichlf, lf, mf, mye
1303 character(len=100) :: filename
1304
1305 allocate (val(nesc))
1306
1307 ! there is one file per irreducible representation and channel
1308 do irr = 1, size(moldat % mgvns)
1309 mgvn = moldat % mgvns(irr)
1310 do ichan = 1, size(m, 1)
1311 val = 0
1312 mye = 0
1313 do ie = myproc, nesc, nprocs
1314 mye = mye + 1
1315 val(ie) = m(ichan, mye, mgvn)
1316 end do
1317
1318 ! combine data from all images to master
1319 reval = real(val)
1320 imval = aimag(val)
1321 call mpi_reduce_inplace_sum_wp(reval, nesc)
1322 call mpi_reduce_inplace_sum_wp(imval, nesc)
1323 val = cmplx(reval, imval, wp)
1324
1325 ! the first image performs the writing
1326 if (myproc == 1 .and. any(val /= 0)) then
1327 ichlf = moldat % ichl(ichan, irr)
1328 lf = moldat % l2p(ichan, irr)
1329 mf = moldat % m2p(ichan, irr)
1330 write (filename, '(3(A,I0),A,SP,I0,3A)') 'pwdip-', mgvn, '-(', ichlf, ',', lf, ',', mf, ')', suffix, '.txt'
1331 open (newunit = u, file = filename, action = 'write', form = 'formatted')
1332 write (u, '(2E25.15)') val
1333 close (u)
1334 end if
1335 end do
1336 end do
1337
1338 end subroutine write_partial_wave_moments
1339
1340
1341 !> \brief Write raw transition dipole moments
1342 !> \author J Benda
1343 !> \date 2021 - 2024
1344 !>
1345 !> Produce tables with the multi-photon ionisation matrix elements per partial wave and per ionisation chain.
1346 !>
1347 subroutine write_raw_dipoles (M, chains, nesc, stem)
1348
1349 use mpi_gbl, only: mpi_reduce_inplace_sum_wp
1350
1351 character(len=*), intent(in) :: stem
1352 integer, allocatable, intent(in) :: chains(:, :)
1353 complex(wp), allocatable, intent(in) :: M(:, :, :, :)
1354 integer, intent(in) :: nesc
1355
1356 character(len=100) :: filename, history
1357 character(len=1), parameter :: xyz(-1:1) = [ 'y', 'z', 'x' ]
1358 character(len=1), parameter :: sph(-1:1) = [ 'm', '0', 'p' ]
1359
1360 complex(wp), allocatable :: val(:)
1361 real(wp), allocatable :: reval(:), imval(:)
1362
1363 integer :: u, itarg, ntarg, ichain, nchain, ipw, npw, icomp, lf, mf, ie, mye
1364
1365 npw = size(m, 2)
1366 nchain = size(m, 3)
1367 ntarg = size(m, 4)
1368
1369 allocate (val(nesc))
1370
1371 ! there is one output file per target, absorption chain and partial wave
1372 do itarg = 1, ntarg
1373 do ichain = 1, nchain
1374 do ipw = 1, npw
1375
1376 val = 0
1377 mye = 0
1378 do ie = myproc, nesc, nprocs
1379 mye = mye + 1
1380 val(ie) = m(mye, ipw, ichain, itarg)
1381 end do
1382
1383 ! combine data from all images to master
1384 reval = real(val)
1385 imval = aimag(val)
1386 call mpi_reduce_inplace_sum_wp(reval, nesc)
1387 call mpi_reduce_inplace_sum_wp(imval, nesc)
1388 val = cmplx(reval, imval, wp)
1389
1390 ! the first image performs the writing
1391 if (myproc == 1 .and. any(val /= 0)) then
1392 lf = int(sqrt(real(ipw - 1, wp)))
1393 mf = ipw - lf*lf - lf - 1
1394 history = ''
1395 do icomp = 1, size(chains, 1)
1396 select case (trim(stem))
1397 case ('xyz'); history = xyz(chains(icomp, ichain)) // trim(history)
1398 case ('sph'); history = sph(chains(icomp, ichain)) // trim(history)
1399 end select
1400 end do
1401 write (filename, '(3A,I0,A,I0,A,SP,I0,A)') 'rawdip-', trim(history), '-(', itarg, ',', lf, ',', mf, ').txt'
1402 open (newunit = u, file = filename, action = 'write', form = 'formatted')
1403 write (u, '(2E25.15)') val
1404 close (u)
1405 end if
1406
1407 end do
1408 end do
1409 end do
1410
1411 end subroutine write_raw_dipoles
1412
1413
1414 !> \brief Write transition dipole moments in RSOLVE format
1415 !> \author J Benda, Z Masin
1416 !> \date 2021 - 2024
1417 !>
1418 !> Produce tables with 1-photon ionisation matrix elements in the format of RSOLVE.
1419 !>
1420 subroutine write_rsolve_dipoles (moldat, M, chains, escat, lu_pw_dipoles)
1421
1422 use photo_outerio, only: write_pw_dipoles
1423 use mpi_gbl, only: mpi_reduce_inplace_sum_wp
1424 use multidip_params, only: carti
1425
1426 type(moleculardata), intent(in) :: moldat
1427 integer, allocatable, intent(in) :: chains(:, :)
1428 complex(wp), allocatable, intent(in) :: M(:, :, :, :)
1429 real(wp), intent(in) :: escat(:)
1430 integer, intent(in) :: lu_pw_dipoles
1431
1432 complex(wp), allocatable :: val(:)
1433 real(wp), allocatable :: reval(:), imval(:), re_pw_dipoles(:,:,:,:), im_pw_dipoles(:,:,:,:)
1434
1435 integer :: itarg, ntarg, ichain, nchain, ipw, npw, icomp, lf, mf, ie, mye, irr, mgvn, stot, gutot, nch, ch
1436
1437 character(len=11) :: form_pw_dipoles
1438 character(len=80) :: title
1439
1440 integer :: lmax_property, dip_comp_present(3), lu_pw_dipoles_m, nset_pw_dipoles, nesc, iprnt, iwrite, ifail
1441 integer, allocatable :: ichl(:), lvchl(:), mvchl(:), starg(:), mtarg(:), gtarg(:)
1442 real(wp), allocatable :: evchl(:)
1443 real(wp) :: bound_state_energies(1), target_energy
1444
1445 npw = size(m, 2)
1446 nchain = size(m, 3)
1447 ntarg = size(m, 4)
1448 nesc = size(escat)
1449 ntarg = size(moldat % etarg)
1450
1451 allocate (val(nesc), starg(ntarg), mtarg(ntarg), gtarg(ntarg))
1452
1453 ! data for the header of the RSOLVE dipoles file
1454 title = 'MULTIDIP 1-PHOTON DIPOLES'
1455 starg = moldat % starg
1456 mtarg(:) = moldat % ltarg(:) - 1 ! IRR of the states in the CONGEN format (starting from 0)
1457 gtarg = 0 ! HARD-CODED SINCE MOLDAT DOESN'T STORE THIS VALUE
1458 target_energy = minval(moldat % etarg)
1459 bound_state_energies = moldat % eig(1,1) ! Neutral state in agreement with `setup_initial_state`
1460 nset_pw_dipoles = 1
1461 form_pw_dipoles = 'FORMATTED'
1462 iwrite = output_unit
1463 iprnt = 0
1464 lmax_property = 1
1465 ifail = 0
1466
1467 do irr = 1, size(moldat % mgvns)
1468
1469 mgvn = moldat % mgvns(irr)
1470 stot = moldat % stot(irr)
1471 gutot = 0
1472 nch = moldat % nchan(irr)
1473
1474 allocate(re_pw_dipoles(1,nch,3,nesc))
1475 allocate(im_pw_dipoles(1,nch,3,nesc))
1476
1477 re_pw_dipoles = 0.0_wp
1478 im_pw_dipoles = 0.0_wp
1479
1480 dip_comp_present = 0
1481
1482 ! transfer the dipoles from M to re_pw_dipoles, im_pw_dipoles and conjugate
1483 do ichain = 1, nchain
1484 do ch = 1, nch
1485
1486 itarg = moldat % ichl(ch,irr)
1487 lf = moldat % l2p(ch,irr)
1488 mf = moldat % m2p(ch,irr)
1489
1490 ipw = lf * lf + lf + mf + 1
1491
1492 val = 0
1493 mye = 0
1494 do ie = myproc, nesc, nprocs
1495 mye = mye + 1
1496 val(ie) = m(mye, ipw, ichain, itarg)
1497 end do
1498
1499 ! combine data from all images to master
1500 reval = real(val)
1501 imval = aimag(val)
1502 call mpi_reduce_inplace_sum_wp(reval, nesc)
1503 call mpi_reduce_inplace_sum_wp(imval, nesc)
1504 val = cmplx(reval, imval, wp)
1505
1506 ! the first image saves the dipoles
1507 if (myproc == 1) then
1508
1509 if (any(abs(val(:)) > 0.0_wp)) dip_comp_present(carti(ichain)) = 1
1510
1511 ! Save the dipoles and conjugate them so the scattering wf. is ket.
1512 do ie = 1, nesc
1513 re_pw_dipoles(1,ch,carti(ichain),ie) = real(val(ie),wp)
1514 im_pw_dipoles(1,ch,carti(ichain),ie) = -aimag(val(ie))
1515 enddo
1516
1517 end if
1518
1519 end do
1520 end do
1521
1522 lu_pw_dipoles_m = lu_pw_dipoles + mgvn
1523
1524 allocate(ichl(nch), lvchl(nch), mvchl(nch), evchl(nch))
1525
1526 ichl(1:nch) = moldat % ichl(1:nch,irr)
1527 lvchl(1:nch) = moldat % l2p (1:nch,irr)
1528 mvchl(1:nch) = moldat % m2p (1:nch,irr)
1529
1530 ! Channel energies in Rydbergs
1531 do ch = 1, nch
1532 evchl(ch) = 2 * ( moldat % etarg(ichl(ch)) - moldat % etarg(1) )
1533 enddo
1534
1535 call write_pw_dipoles( lu_pw_dipoles_m, nset_pw_dipoles, form_pw_dipoles, title, mgvn, &
1536 stot, gutot, starg, mtarg, gtarg, ichl, lvchl, mvchl, evchl, escat, lmax_property,&
1537 dip_comp_present, bound_state_energies, target_energy, &
1538 re_pw_dipoles, im_pw_dipoles, iprnt, iwrite, ifail )
1539
1540 deallocate(re_pw_dipoles, im_pw_dipoles, ichl, lvchl, mvchl, evchl)
1541
1542 enddo
1543
1544 end subroutine write_rsolve_dipoles
1545
1546
1547 !> \brief Write cross sections to a file
1548 !> \author J Benda
1549 !> \date 2020 - 2024
1550 !>
1551 !> Write cross sections to a file. The first column of the array is expected to be the photon energy.
1552 !>
1553 subroutine write_cross_section (cs, nesc, erange, filename)
1554
1555 use mpi_gbl, only: mpi_reduce_inplace_sum_wp
1556
1557 character(len=*), intent(in) :: filename
1558 real(wp), intent(in) :: cs(:, :)
1559 integer, intent(in) :: nesc, erange(2)
1560
1561 real(wp), allocatable :: csg(:, :), buffer(:)
1562
1563 integer :: u, ie, mye, nene, ntgt
1564
1565 ntgt = size(cs, 1)
1566 nene = size(cs, 2)
1567
1568 ! a local copy of 'cs' for gathering data from parallel images
1569 allocate (csg(ntgt, nesc))
1570 csg = 0
1571 mye = 0
1572 do ie = myproc, nesc, nprocs
1573 mye = mye + 1
1574 csg(:, ie) = cs(:, mye)
1575 end do
1576
1577 ! combine data from all images to master
1578 buffer = reshape(csg, [size(csg)])
1579 call mpi_reduce_inplace_sum_wp(buffer, size(buffer))
1580 csg = reshape(buffer, shape(csg))
1581
1582 ! the first image now writes (non-zero) cross sections to file
1583 if (myproc == 1 .and. any(csg(2:, :) /= 0)) then
1584 open (newunit = u, file = filename, action = 'write', form = 'formatted')
1585 do ie = max(1, erange(1)), min(nesc, erange(2))
1586 write (u, '(*(E25.15))') csg(:, ie)
1587 end do
1588 close (u)
1589 end if
1590
1591 end subroutine write_cross_section
1592
1593end module multidip_io
I/O routines used by MULTIDIP.
subroutine read_bndcoeffs(bnd, ei, lubnd, mgvn0, stot0)
Read inner bound wave function coefficients.
subroutine read_target_properties(lutarg, prop, etarg)
Read ion transition dipoles.
subroutine get_kmatrix(km, kmat, skip)
Read single K-matrix from the K-matrix file.
subroutine assert_status(message, errcode)
Check for error code.
subroutine read_wfncoeffs(ak, lusct)
Read wave function coefficients from files.
subroutine load_mapped_matrix(this, filename, u, offset, rows, cols)
Read or map a matrix from file.
subroutine apply_boundary_amplitudes(moldat, irr, transp, x, y)
Multiply by boundary amplitudes.
subroutine reset_wfncoeffs_position(ak, skip)
Reset I/O pointer to start of Ak-coeffs.
subroutine read_kmatrix_file(km)
Read K-matrix file.
subroutine read_wfncoeffs_file(ak)
Read wave function coefficients for a single symmetry from a file.
subroutine destruct_mapped_matrix(this)
Finalize a (potentially mapped) matrix object.
subroutine scale_boundary_amplitudes(moldat, irr, v, vw)
Scale boundary amplitudes matrix by a diagonal matrix.
subroutine setup_angular_integrals(moldat)
Store the angular integrals in a more convenient shape.
subroutine write_rsolve_dipoles(moldat, m, chains, escat, lu_pw_dipoles)
Write transition dipole moments in RSOLVE format.
subroutine get_diptrans(moldat, i, iidip, ifdip)
Return dipole transition descriptors.
subroutine write_cross_section(cs, nesc, erange, filename)
Write cross sections to a file.
integer function clip(x, a, b)
Clamp value in range.
subroutine write_raw_dipoles(m, chains, nesc, stem)
Write raw transition dipole moments.
subroutine reset_kmatrix_position(km, skip)
Reset I/O pointer to start of K-matrices.
subroutine apply_dipole_matrix(moldat, component, irrpair, transp, nf, nn, x, y)
Multiply by dipole matrix.
integer nprocs
integer myproc
subroutine get_wfncoeffs(ak, e, re_ak, im_ak, skip)
Read single Ak-matrix from the Ak-coeffs file.
subroutine read_molecular_data(moldat, filename, mpiio, read_wmat2)
Read RMT molecular_data file.
subroutine read_kmatrices(km, lukmt, nkset)
Read K-matrix files.
subroutine write_partial_wave_moments(moldat, m, nesc, suffix)
Write partial wave moments.
subroutine write_energy_grid(escat)
Write photoelectron energies to file.
Hard-coded parameters of MULTIDIP.
integer, dimension(3), parameter carti
Position of a Cartesian coordinate in the real spherical basis (y, z, x).
real(wp), parameter rone
logical check_dipoles
Check that all dipole matrices in molecular_data are nonzero.
real(wp), parameter rzero
Special functions and objects used by MULTIDIP.