Multidip 1.0
Multi-photon matrix elements
Loading...
Searching...
No Matches
multidip_routines.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 Main MULTIDIP routines
23!> \author J Benda
24!> \date 2020 - 2021
25!>
27
28 use, intrinsic :: iso_c_binding, only: c_double, c_int, c_f_pointer
29 use, intrinsic :: iso_fortran_env, only: input_unit, int32, int64, real64, output_unit
30
31#ifdef HAVE_NO_FINDLOC
32 ! supply missing intrinsic functions
33 use algorithms, only: findloc
34#endif
35
36 ! GBTOlib
37 use blas_lapack_gbl, only: blasint
38 use phys_const_gbl, only: pi, imu, to_ev
39 use precisn_gbl, only: wp
40
41 implicit none
42
43 !> \brief Intermediate state
44 !> \author J Benda
45 !> \date 2020
46 !>
47 !> Type holding information about a specific irreducible representation of the intermediate state
48 !> of a given perturbative order. The states are connected into a linked list to avoid the necessity
49 !> of reallocating any large expansion during the calculation. Also, each state is linked to its
50 !> "parent" state, which is another instance of this class, which stands on the right-hand side
51 !> of the equation for the intermediate state.
52 !>
53 type intermediatestate
54
55 integer :: order !< Number of absorbed photons
56 integer :: mgvn !< Irreducible representation of this intermediate state
57 integer :: dcomp !< Dipole operator component responsible for populating this state
58
59 !> Inner region expansion coefficients
60 !> - First index: inner region eigenstate index
61 !> - Second index: 1 = real part, 2 = imag part
62 !> - Third index: scattering energy index
63 real(wp), allocatable :: ck(:, :, :)
64
65 !> Outer region expansion coefficients
66 !> - First index: outer region channel index
67 !> - Second index: 1 = real part, 2 = imag part
68 !> - Third index: scattering energy index
69 real(wp), allocatable :: ap(:, :, :)
70
71 !> Photoionisation transition matrix elements. Only used when the "intermediate" state
72 !> is actually the final state. In such a case, the expansion coefficients 'ck' and 'ap'
73 !> are not allocated.
74 !> - First index: outer region channel index
75 !> - Second index: 1 = real part, 2 = imag part
76 !> - Third index: scattering energy index
77 real(wp), allocatable :: dip(:, :, :)
78
79 !> Pointer to the parent intermediate state (if any)
80 type(intermediatestate), pointer :: parent => null()
81
82 !> Linked list connections
83 type(intermediatestate), pointer :: prev => null()
84 type(intermediatestate), pointer :: next => null()
85
86 end type intermediatestate
87
88
89 !> \brief Multi-photon integral cache
90 !> \authors J Benda
91 !> \date 2024
92 !>
93 !> Recursive data structure holding radial multi-photon integrals. Typically, the first object does not contain any data
94 !> in `vals_xxx` and only defines the final reduced channels. [A reduced channel is a multi-index (i, l) given by the target
95 !> index and partial wave angular momentum.] The nested objects then store integral values, e.g.
96 !> \f[
97 !> [i_3 l_3 | m_{32} | i_2 l_2]
98 !> \f]
99 !> and also contain references to further nested blocks of higher orders,
100 !> \f[
101 !> [i_3 l_3 | m_{32} | m_i_2 l_2 | m_{21} | i_1 l_1]
102 !> \f]
103 !> etc.
104 !>
106
107 integer, allocatable :: rchs_pws(:) !< Reduced channels dipole-coupled in the photoelectron (m = 1).
108 integer, allocatable :: rchs_ion(:) !< Reduced channels dipole-coupled in the residual ion (m = 0).
109
110 complex(wp), allocatable :: vals_pws(:, :) !< Integral values for pws-coupled channels (H+/H-).
111 complex(wp), allocatable :: vals_ion(:, :) !< Integral values for ion-coupled channels (H+/H-).
112
113 type(integral_cache_t), allocatable :: next_pws(:) !< Nested caches for pws-copled channels.
114 type(integral_cache_t), allocatable :: next_ion(:) !< Nested caches for ion-copled channels.
115
116 end type integral_cache_t
117
118
119 ! for debugging purposes
120 logical, parameter :: test_expansion = .false.
121
122contains
123
124 !> \brief MULTIDIP main subroutine
125 !> \authors J Benda
126 !> \date 2020 - 2024
127 !>
128 !> Read the input namelist &mdip from the standard input, call routines for reading the input file, and then call
129 !> the main computational routine. Alternatively, when the "--test" switch is present on the command line, it will
130 !> run a few unit tests.
131 !>
132 subroutine multidip_main
133
134 use, intrinsic :: iso_c_binding, only: c_ptr
135
136 use linalg_cl, only: initialize_cl, finalize_cl
137 use mpi_gbl, only: comm_rank => myrank, comm_size => nprocs, mpi_mod_finalize, mpi_mod_start, mpi_xermsg
138 use multidip_io, only: moleculardata, kmatrix, scatakcoeffs, read_wfncoeffs, read_kmatrices, read_molecular_data, &
141#ifdef WITH_GSL
142 use multidip_special, only: gsl_set_error_handler_off
143#endif
144 use multidip_tests, only: run_tests
145
146 type(moleculardata) :: moldat
147 type(kmatrix), allocatable :: km(:)
148 type(scatakcoeffs), allocatable :: ak(:)
149
150 character(len=256) :: arg, rmt_data, raw, msg
151 type(c_ptr) :: dummy
152
153 integer :: order, nirr, lusct(8), lukmt(8), lubnd, nkset(8), iarg, narg, input, i, erange(2), p(nMaxPhotons)
154 integer :: lu_pw_dipoles
155 logical :: verbose, mpiio, gpu
156 real(wp) :: omega(nMaxPhotons), first_IP, rasym
157 complex(wp) :: polar(3, nMaxPhotons)
158
159 call print_ukrmol_header(output_unit)
160
161 print '(/,A)', 'Program MULTIDIP: Calculation of multi-photon ionization'
162
163 iarg = 1
164 narg = command_argument_count()
165 input = input_unit
166
167#ifdef WITH_GSL
168 dummy = gsl_set_error_handler_off()
169#endif
170
171 do while (iarg <= narg)
172 call get_command_argument(iarg, arg)
173 if (arg == '--test') then
174 call run_tests
175 return
176 else if (arg == '--input') then
177 iarg = iarg + 1
178 call get_command_argument(iarg, arg)
179 open (input, file = trim(arg), action = 'read', form = 'formatted')
180 else if (arg == '--help') then
181 print '(/,A,/)', 'Available options:'
182 print '(A)', ' --help display this help and exit'
183 print '(A)', ' --input FILE use FILE as the input file (instead of standard input)'
184 print '(A)', ' --test run internal sanity checks and exit'
185 print *
186 return
187 else
188 write (msg, '(3a)') 'Unknown command line argument "', trim(arg), '"'
189 call mpi_xermsg('multidip_routines', 'multidip_main', trim(msg), 1, 1)
190 end if
191 iarg = iarg + 1
192 end do
193
194 call read_input_namelist(input, order, lusct, lukmt, lubnd, nkset, polar, omega, verbose, rmt_data, first_ip, rasym, raw, &
195 erange, mpiio, gpu, p, lu_pw_dipoles)
196
197 if (input /= input_unit) close (input)
198
199 call mpi_mod_start
200
201 myproc = comm_rank + 1
202 nprocs = comm_size
203 print '(/,A,I0,A,I0)', 'Parallel mode; image ', myproc, ' of ', nprocs
204
205 print '(/,A,/)', 'Absorbed photons'
206 do i = 1, order
207 print '(2x,A,I0,3x,A,SP,2F6.3,A,2F6.3,A,2F6.3,A,F0.3,A)', &
208 '#', i, 'eX: ', polar(1, i), 'i, eY: ', polar(2, i), 'i, eZ: ', polar(3, i), 'i, energy: ', omega(i) * to_ev, ' eV'
209 end do
210
211 if (first_ip > 0) then
212 print '(/,A,F0.5,A)', 'Override first IP to ', first_ip * to_ev, ' eV'
213 else
214 print '(/,A)', 'Use calculated IP'
215 end if
216
217 nirr = count(lukmt > 0)
218
219 allocate (km(nirr))
220
221 if (any(lusct > 0)) then
222 allocate (ak(nirr))
223 call read_wfncoeffs(ak, lusct)
224 end if
225
226 call read_kmatrices(km, lukmt, nkset)
227 call read_molecular_data(moldat, trim(rmt_data), mpiio, test_expansion)
228
229 if (any(erange /= 0)) then
230 if (erange(1) < 1 .or. erange(1) > erange(2) .or. erange(2) > minval(km % nescat)) then
231 write (msg, '(a,i0)') 'Error: Energy range must satisfy 1 <= erange(1) <= erange(2) <= ', minval(km % nescat)
232 call mpi_xermsg('multidip_routines', 'multidip_main', trim(msg), 1, 1)
233 end if
234 else
235 erange(1) = 1
236 erange(2) = minval(km % nescat)
237 end if
238
239 if (moldat % nz < moldat % nelc) then
240 write (msg, '(a,i0,a,i0,a)') 'Error: Number of protons (nz = ', moldat % nz, ') is smaller than the number of &
241 &electrons in residual ion (nelc = ', moldat % nelc, ').'
242 call mpi_xermsg('multidip_routines', 'multidip_main', trim(msg), 1, 1)
243 end if
244
245 if (rasym > moldat % rmatr) then
246 if (order <= 2) then
247 write (*, '(/,A,F0.1,A,F0.1,A)', advance = 'no') 'Interval ', moldat % rmatr, ' to ', rasym, &
248 ' will be integrated numerically using '
249 select case (num_integ_algo)
250 case (1); print '(a)', 'Romberg quadrature'
251 case (2); print '(a)', 'Levin quadrature'
252 end select
253 else
254 print '(/,a)', 'Warning: Numerical integration (rasym) is currently only implemented for the second order.'
255 rasym = 0
256 end if
257 end if
258
259 if (gpu) call initialize_cl(-1_c_int, int(merge(-1, myproc - 1, nprocs == 1), c_int))
260 call multidip_driver(order, moldat, km, ak, lubnd, omega(1:order), polar(:, 1:order), verbose, first_ip, rasym, raw, &
261 erange, p, lu_pw_dipoles)
262 if (gpu) call finalize_cl
263
264 call mpi_mod_finalize
265
266 end subroutine multidip_main
267
268
269 !> \brief Central computation routine
270 !> \author J Benda
271 !> \date 2020 - 2023
272 !>
273 !> This subroutine drives the calculation. First it obtains all intermediate states, then it calculates
274 !> the final photoionization state, and evaluates the dipole elements and generalized cross sections.
275 !> The core of the work is the evaluation of all matrix elements of the type
276 !> \f[
277 !> M_{fi,k_n,\dots,k_i} = \langle \Psi_{f}^{(-)} | x_{k_n} \dots \hat{G}^{(+)} x_{k_2} \hat{G}^{(+)} x_{k_1}
278 !> | \Psi_i \rangle
279 !> \f]
280 !> where \f$ x_j \f$ is the j-th component of the dipole operator, followed by reduction of this tensor with all
281 !> provided photon field polarisations \f$ \epsilon_j \f$:
282 !> \f[
283 !> M_{fi} = \sum_{k_n,\dots,k_1} \epsilon_{k_n} \dots \epsilon_{k_1} M_{fi,k_n,\dots,k_1}
284 !> \f]
285 !> The sequence \f$ k_1, k_2, \dots \f$ is referred to as component "history" or "chain" in the code.
286 !>
287 !> \param[in] order Order of the process (= total number of absorbed photons).
288 !> \param[in] moldat MolecularData object with data read from the file *molecular_data*.
289 !> \param[in] km KMatrix objects for relevant irreducible representations with data read from RSOLVE K-matrix files.
290 !> \param[in] ak Wave function coeffs (from RSOLVE) for the same set of irrs as km.
291 !> \param[in] lubnd File with bound state wave function coefficients.
292 !> \param[in] omega Fixed photon energies in a.u. or zeros for flexible photons.
293 !> \param[in] polar Photon polarisation vectors or zeros for polarisation averaging.
294 !> \param[in] verbose Debugging output intensity.
295 !> \param[in] first_IP First ionization potential in a.u. as requested by the input namelist.
296 !> \param[in] r0 Radius from which to apply asymptotic integrals.
297 !> \param[in] raw Write raw transition dipoles in spherical (= 'sph') or Cartesian (= 'xyz') basis, or both (= 'both').
298 !> \param[in] erange Range (subset) of energies in K-matrix files to use for calculations.
299 !> \param[in] p Lab-frame polarisation for calculations of orientationally averaged beta parameters.
300 !> \param[in] lu_pw_dipoles Base number for the logical unit for saving the pw dipoles in RSOLVE format (typically 410).
301 !>
302 subroutine multidip_driver (order, moldat, km, ak, lubnd, omega, polar, verbose, first_IP, r0, raw, erange, p, lu_pw_dipoles)
303
304 use multidip_io, only: moleculardata, kmatrix, scatakcoeffs, nprocs, get_diptrans, write_energy_grid
305
306 integer, intent(in) :: order, erange(2), lubnd, p(:), lu_pw_dipoles
307 logical, intent(in) :: verbose
308 type(moleculardata), intent(in) :: moldat
309 type(kmatrix), allocatable, intent(in) :: km(:)
310 type(scatakcoeffs), allocatable, intent(in) :: ak(:)
311 real(wp), intent(in) :: omega(:), first_IP, r0
312 complex(wp), intent(in) :: polar(:, :)
313 character(len=*), intent(in) :: raw
314
315 type(integral_cache_t), allocatable :: integral_cache(:)
316
317 type(intermediatestate), pointer :: states, state
318
319 integer, allocatable :: iidip(:), ifdip(:)
320 real(wp), allocatable :: escat(:)
321
322 integer :: j, s, mgvni, mgvnn, mgvn1, mgvn2, nesc, irri, iki, icomp, nstati, nchani
323 real(wp) :: Ei
324
325 iki = 1 ! which K-matrix file (and Ak file) to use for the initial state
326 mgvni = km(iki) % mgvn ! IRR of the initial state
327 nesc = erange(2) - erange(1) + 1 ! number of scattering energies
328 irri = findloc(moldat % mgvns, mgvni, 1) ! internal index of initial IRR in molecular_data
329 nstati = moldat % mnp1(irri) ! number of inner region states in initial IRR
330 nchani = moldat % nchan(irri) ! number of outer region channels in initial IRR
331
332 allocate (states)
333 allocate (states % ck(nstati, 2, (nesc + nprocs - 1) / nprocs))
334 allocate (states % ap(nchani, 2, (nesc + nprocs - 1) / nprocs))
335
336 ! prepate the initial state
337 call setup_initial_state(states, moldat, irri, lubnd, ei)
338
339 ! for all absorbed photons
340 do j = 1, order
341
342 print '(/,2(A,I0))', 'Photon ', j, ' of ', order
343
344 ! precompute asymptotic integrals
345 call precompute_integral_cache(integral_cache, moldat, km(iki) % escat, j, r0, erange, ei, first_ip, omega, verbose)
346
347 ! for all components of the dipole operator
348 do icomp = 1, 3
349
350 call get_diptrans(moldat, icomp, iidip, ifdip)
351
352 ! for all transitions mediated by this component that are stored in molecular_data
353 do s = 1, size(iidip)
354
355 mgvn1 = iidip(s) - 1
356 mgvn2 = ifdip(s) - 1
357
358 ! apply this transition to all relevant previous intermediate states
359 state => states
360 do while (associated(state))
361
362 ! process states of previous order
363 if (state % order + 1 == j) then
364
365 ! IRR of the previous intermediate state (i.e. last element in MGVN history chain)
366 mgvnn = state % mgvn
367
368 ! skip this transition if it is not applicable to the current IRR or there are no K-matrices for it
369 if ((mgvnn == mgvn1 .or. mgvnn == mgvn2) .and. &
370 any(km(:) % mgvn == mgvn1) .and. &
371 any(km(:) % mgvn == mgvn2)) then
372
373 ! either calculate the next intermediate state or evaluate the dipole elements
374 if (j < order) then
375 call solve_intermediate_state(moldat, order, omega, icomp, s, integral_cache, mgvnn, &
376 mgvn1, mgvn2, km, state, verbose, ei, first_ip, r0, erange)
377 else
378 call extract_dipole_elements(moldat, order, omega, icomp, s, integral_cache, mgvnn, &
379 mgvn1, mgvn2, km, ak, state, verbose, ei, first_ip, r0, erange)
380 end if
381
382 end if
383
384 end if
385
386 ! move on to the next state
387 state => state % next
388
389 end do
390
391 end do
392
393 end do
394
395 if (allocated(integral_cache)) then
396 deallocate (integral_cache)
397 end if
398
399 end do
400
401 ! calculate observables
402 escat = km(iki) % escat(erange(1):erange(2))
403 call write_energy_grid(escat)
404 call calculate_pw_transition_elements(moldat, order, states, escat, ei, first_ip, omega, polar, erange)
405 call calculate_asymmetry_parameters(moldat, order, states, escat, ei, first_ip, omega, raw, erange, p, lu_pw_dipoles)
406
407 ! clean up the linked list
408 state => states
409 do while (associated(state % next))
410 state => state % next
411 deallocate (state % prev)
412 end do
413 deallocate (state)
414
415 end subroutine multidip_driver
416
417
418 !> \brief Construct initial state
419 !> \authors J Benda
420 !> \date 2023
421 !>
422 !> Construct inner and outer expansion coefficients of the initial state. When no input from BOUND is provided, this is
423 !> trivial: The initial state coincides with the first inner region eigenstate in the irreducible representation given
424 !> by the first K-matrix file and there is nothing in the outer region.
425 !>
426 !> However, when bound state coefficients are given, they are adopted here and the wave function is correctly continued
427 !> into the outer region using a single-free-electron expansion. The energy shift is not considered, though.
428 !>
429 !> \todo Take into account modified bound state energy calculated by BOUND.
430 !>
431 subroutine setup_initial_state (states, moldat, irr, lubnd, Ei)
432
433 use multidip_io, only: moleculardata, apply_boundary_amplitudes, read_bndcoeffs
436
437 type(intermediatestate), pointer, intent(inout) :: states
438 type(moleculardata), intent(in) :: moldat
439 integer, intent(in) :: irr, lubnd
440 real(wp), intent(out) :: Ei
441
442 complex(wp), allocatable :: ap(:)
443 real(wp), allocatable :: Ek(:), bnd(:, :), wbnd(:, :), fc(:), gc(:), fcp(:), gcp(:)
444 real(wp) :: r
445 integer :: mye, nmye, nchan, nstat, ichan, stot, mgvn, nbnd
446
447 nmye = size(states % ck, 3)
448 mgvn = moldat % mgvns(irr)
449 stot = moldat % stot(irr)
450 nchan = moldat % nchan(irr)
451 nstat = moldat % mnp1(irr)
452 nbnd = 1
453 r = moldat % rmatr
454 ei = moldat % eig(1, irr)
455
456 ! construct representation of the initial state in the inner region
457 states % order = 0
458 states % mgvn = mgvn
459 states % dcomp = 0
460
461 allocate (bnd(nstat, nbnd))
462
463 if (lubnd <= 0) then
464 bnd(:, 1) = 0 ! all inner region eigenstates are unpopulated...
465 bnd(1, 1) = 1 ! ... except for the first one, which has the coefficient 1.0
466 else
467 call read_bndcoeffs(bnd(:, 1), ei, lubnd, mgvn, stot) ! get calculated coefs from BOUND
468 end if
469
470 do mye = 1, nmye
471 states % ck(:, 1, mye) = bnd(:, 1)
472 states % ck(:, 2, mye) = 0
473 end do
474
475 print '(/,a,/)', 'Bound state information'
476 print '(4x,a,e25.15)', 'First R-matrix pole = ', moldat % eig(1, irr)
477 print '(4x,a,e25.15)', 'Calculated initial energy = ', ei
478
479 ! construct representation of the initial state in the outer region
480 if (extend_istate) then
481
482 allocate (wbnd(nchan, nbnd), fc(nchan), gc(nchan), fcp(nchan), gcp(nchan), ap(nchan))
483
484 call apply_boundary_amplitudes(moldat, irr, 'N', bnd, wbnd)
485
486 ! "photoelectron" "kinetic" energies (actually negative closed channel thresholds)
487 ek = ei - moldat % etarg(moldat % ichl(: , irr))
488
489 call evaluate_fundamental_solutions(moldat, r, irr, nchan, ek, fc, gc, fcp, gcp, sqrtknorm = .false.)
490
491 ! obtain outer coeffs by projecting the inner state on channels at boundary and dividing by decaying Whittaker function
492 ap = wbnd(:, 1) / gc
493
494 do mye = 1, nmye
495 states % ap(:, 1, mye) = real(ap)
496 states % ap(:, 2, mye) = aimag(ap)
497 end do
498
499 print '(4x,a,e25.15)', 'Total inner norm = ', norm2(bnd(:, 1))
500 print '(/,a,/)', 'Bound state outer region channel coefficients and amplitudes'
501 print '(4x,a,4x,a,23x,a,20x,a,20x,a)', 'chan', 'Ek', 'Re ap', 'Im ap', 'f_p'
502 do ichan = 1, nchan
503 print '(i6,a,4e25.15)', ichan, ': ', ek(ichan), ap(ichan), wbnd(ichan, 1)
504 end do
505
506 else
507
508 states % ap(:, :, :) = 0 ! outer region is empty
509
510 end if
511
512 if (test_expansion) then
513 call test_outer_expansion('initial-state.txt', moldat, irr, states % ck(:, :, 1), states % ap(:, :, 1), ei)
514 end if
515
516 end subroutine setup_initial_state
517
518
519 !> \brief Calculate intermediate photoionisation state
520 !> \authors J Benda
521 !> \date 2020 - 2024
522 !>
523 !> Solve the intermediate state equation
524 !> \f[
525 !> (E_i + j \omega - \hat{H}) \Psi_j = \hat{D} \Psi_{j-1}
526 !> \f]
527 !> with the right-hand side based on the state provided as argument 'state'.
528 !>
529 !> \param[in] moldat MolecularData object with data read from the file *molecular_data*.
530 !> \param[in] order Perturbation order of the intermediate state to calculate.
531 !> \param[in] Ephoton Fixed photon energies in a.u. or zeros for flexible photons.
532 !> \param[in] icomp Which Cartesian component of the dipole operator will give rise to the intermediate state.
533 !> \param[in] s Which "transition" in *molecular_data* corresponds to the action of this dipole component on parent.
534 !> \param[in] cache Precomputed integrals.
535 !> \param[in] mgvnn MGVN of the previous intermediate state.
536 !> \param[in] mgvn1 Ket MGVN for the transition "s" as stored in *molecular_data*.
537 !> \param[in] mgvn2 Bra MGVN for the transition "s" as stored in *molecular_data*.
538 !> \param[in] km KMatrix objects for relevant irreducible representations with data read from RSOLVE K-matrix files.
539 !> \param[inout] state Tree of intermediate states, pointing at the previous intermediate state to develop into next state.
540 !> \param[in] verbose Debugging output intensity.
541 !> \param[in] calc_Ei Initial state energy calculated by (MPI-)SCATCI or BOUND.
542 !> \param[in] first_IP Ionization potential according to the input namelist.
543 !> \param[in] r0 Radius from which to apply asymptotic integration.
544 !> \param[in] erange Range (subset) of energies in K-matrix files to use for calculations.
545 !>
546 subroutine solve_intermediate_state (moldat, order, Ephoton, icomp, s, cache, mgvnn, mgvn1, mgvn2, &
547 km, state, verbose, calc_Ei, first_IP, r0, erange)
548
549 use multidip_io, only: moleculardata, kmatrix, scatakcoeffs, myproc, nprocs, apply_boundary_amplitudes, &
554
555 type(moleculardata), intent(in) :: moldat
556 type(kmatrix), allocatable, intent(in) :: km(:)
557 type(integral_cache_t), intent(in) :: cache(:)
558
559 type(intermediatestate), pointer, intent(inout) :: state
560 type(intermediatestate), pointer :: last
561
562 integer, intent(in) :: order, icomp, s, mgvnn, mgvn1, mgvn2, erange(2)
563 logical, intent(in) :: verbose
564 real(wp), intent(in) :: Ephoton(:), calc_Ei, first_IP, r0
565
566 integer :: nstatn, nstatf, nchann, nchanf, nopen, mgvnf, irrn, irrf, ikn, ikf, ipw, nesc, ie, mye, t, dt, i
567 real(wp) :: Ei, Ek, Etotf
568 character(len=1) :: transp
569 character(len=1024) :: filename
570
571 complex(wp), allocatable :: beta(:), app(:), H(:, :), lambda(:), hc(:), hcp(:)
572 real(wp), allocatable :: fc(:), fcp(:), gc(:), gcp(:)
573
574 integer, allocatable :: lf(:)
575 real(wp), allocatable :: Ef(:), P(:), rhs(:, :), omega(:), echl(:)
576 real(wp), allocatable :: Pw(:, :), wPw(:, :), wPD(:, :), IwPD(:, :), wckp(:, :), Dpsi(:, :, :), corr(:, :), ckp(:, :)
577
578 if (mgvnn == mgvn1) then
579 mgvnf = mgvn2
580 transp = 'T'
581 else
582 mgvnf = mgvn1
583 transp = 'N'
584 end if
585
586 irrn = findloc(moldat % mgvns, mgvnn, 1)
587 irrf = findloc(moldat % mgvns, mgvnf, 1)
588
589 ikn = findloc(km(:) % mgvn, mgvnn, 1)
590 ikf = findloc(km(:) % mgvn, mgvnf, 1)
591
592 nstatn = moldat % mnp1(irrn)
593 nstatf = moldat % mnp1(irrf)
594
595 nchann = moldat % nchan(irrn)
596 nchanf = moldat % nchan(irrf)
597
598 nesc = erange(2) - erange(1) + 1
599 mye = 0
600 t = 0
601
602 allocate (pw(nstatf, nchanf), wpd(nchanf, 2), iwpd(nchanf, 2), wpw(nchanf, nchanf), wckp(nchanf, 2), omega(order), &
603 dpsi(nstatf, 2, (nesc + nprocs - 1) / nprocs), fc(nchanf), gc(nchanf), fcp(nchanf), gcp(nchanf), &
604 hc(nchanf), hcp(nchanf), corr(nchanf, 2), echl(nchanf), &
605 ef(nchanf), lf(nchanf), ckp(nstatf, 2), h(nchanf, nchanf), lambda(nchanf), beta(nchanf), &
606 app(nchanf))
607
608 echl(1:nchanf) = moldat % etarg(moldat % ichl(1:nchanf, irrf)) - moldat % etarg(1)
609
610 ! set up a new intermediate state at the end of the storage chain
611 last => state
612 do while (associated(last % next))
613 last => last % next
614 end do
615 allocate (last % next)
616 last % next % prev => last
617 last => last % next
618 last % order = state % order + 1
619 last % mgvn = mgvnf
620 last % dcomp = icomp
621 last % parent => state
622 allocate (last % ck(nstatf, 2, (nesc + nprocs - 1) / nprocs))
623 allocate (last % ap(nchanf, 2, (nesc + nprocs - 1) / nprocs))
624
625 call print_transition_header(last)
626
627 ! apply the inner dipoles matrix on the previous inner region solution expansions
628 call reset_timer(t, dt)
629 call apply_dipole_matrix(moldat, icomp, s, transp, nstatf, nstatn, state % ck, dpsi)
630 call reset_timer(t, dt)
631 print '(4x,A,I0,A)', 'inner dipoles applied in ', dt, ' s'
632
633 ! initialize computation of the R-matrix
634 call calculate_r_matrix(0, moldat, irrf, 0._wp, [ 0._wp ], pw, wpw);
635
636 ! for all scattering energies
637 do ie = erange(1) + myproc - 1, erange(2), nprocs
638
639 mye = mye + 1
640
641 ! calculate photon energies and the initial state energy (take into account user-specified first ionization potential)
642 ei = calc_ei
643 call calculate_photon_energies(first_ip, km(ikf) % escat(ie), moldat % etarg(1), ei, ephoton, omega)
644
645 ! quantum numbers of the final state
646 etotf = ei + sum(omega(1 : last % order))
647 ef = km(ikf) % escat(ie) - echl - sum(omega(last % order + 1 :))
648 lf = moldat % l2p(1:nchanf, irrf)
649 nopen = merge(count(ef + closed_range > 0), count(ef > 0), closed_interm)
650
651 print '(4x,A,*(I0,A))', 'energy ', ie, ' of ', km(ikf) % nescat, &
652 ' (', count(ef > 0), ' open / ', nopen, ' open + weakly closed channels of ', nchanf, ')'
653
654 ! evaluate Coulomb functions at this energy for all partial waves in the final IRR
655 fc = 0; fcp = 0; gc = 0; gcp = 0; hc = 0; hcp = 0; lambda = 0
656 call evaluate_fundamental_solutions(moldat, moldat % rmatr, irrf, nopen, ef, fc, gc, fcp, gcp, sqrtknorm = .false.)
657 do ipw = 1, nopen
658 hc(ipw) = gc(ipw) + imu*fc(ipw)
659 hcp(ipw) = gcp(ipw) + imu*fcp(ipw)
660 lambda(ipw) = hcp(ipw) / hc(ipw)
661 end do
662
663 ! inner region part of the right-hand side of the master equation
664 rhs = dpsi(:, :, mye)
665
666 ! outer region correction to the right-hand side
667 ek = km(ikf) % escat(ie) - sum(omega(last % order + 1 :))
668 call multiint(moldat, r0, ei, ek, omega, mye, last, +1, beta, cache)
669 beta = -2 * beta / sqrt(2*abs(ef))
670 where (ef < 0) beta = beta / imu
671 corr(:, 1) = real(beta * (fcp - lambda * fc), wp)
672 corr(:, 2) = aimag(beta * (fcp - lambda * fc))
673 call apply_boundary_amplitudes(moldat, irrf, 'T', corr, dpsi(:, :, mye))
674 rhs = rhs - 0.5 * dpsi(:, :, mye)
675
676 ! solve the master equation using the Sherman-Morrison-Woodbury formula
677 p = 1 / (etotf - moldat % eig(1:nstatf, irrf))
678 ckp(:, 1) = p * rhs(:, 1)
679 ckp(:, 2) = p * rhs(:, 2)
680
681 if (nopen > 0) then
682
683 ! compose the reduced inverse Hamiltonian
684 h = 0
685 call calculate_r_matrix(1, moldat, irrf, etotf, p, pw, wpw)
686 h(1:nopen, 1:nopen) = wpw(1:nopen, 1:nopen)
687 do i = 1, nopen
688 h(i, i) = h(i, i) + 2/lambda(i)
689 end do
690 do i = nopen + 1, nchanf
691 h(i, i) = 1
692 end do
693 call apply_boundary_amplitudes(moldat, irrf, 'N', ckp, wpd)
694
695 ! solve the reduced system
696 iwpd = 0
697 call solve_complex_system(nopen, h, wpd, iwpd)
698
699 ! expand the reduced solution to the full solution
700 call apply_boundary_amplitudes(moldat, irrf, 'T', iwpd, ckp)
701 ckp(:, 1) = p * (rhs(:, 1) - ckp(:, 1))
702 ckp(:, 2) = p * (rhs(:, 2) - ckp(:, 2))
703
704 end if
705
706 ! extract the outer region channel amplitudes
707 call apply_boundary_amplitudes(moldat, irrf, 'N', ckp, wckp)
708 app = cmplx(wckp(:, 1), wckp(:, 2), wp)
709 where (hc /= 0)
710 app = (app - beta*fc) / hc
711 elsewhere
712 app = 0
713 end where
714
715 ! for debugging purposes
716 if (test_expansion) then
717 wckp(:, 1) = real(app)
718 wckp(:, 2) = aimag(app)
719 write (filename, '(a,i0,a,i0,a)') 'intermediate-state-', irrf, '-', ie, '.txt'
720 call test_outer_expansion(trim(filename), moldat, irrf, ckp, wckp, etotf)
721 end if
722
723 if (verbose) print '(4x,A,*(E25.15))', ' - beta: ', beta(1:nopen)
724 if (verbose) print '(4x,A,*(E25.15))', ' - ap: ', app(1:nopen)
725
726 last % ck(:, 1, mye) = ckp(:, 1)
727 last % ck(:, 2, mye) = ckp(:, 2)
728
729 last % ap(:, 1, mye) = real(app, wp)
730 last % ap(:, 2, mye) = aimag(app)
731
732 end do
733
734 ! finalize computation of the R-matrix
735 call calculate_r_matrix(2, moldat, irrf, 0._wp, [ 0._wp ], pw, wpw);
736
737 call reset_timer(t, dt)
738 print '(4x,A,I0,A)', 'intermediate states solved in ', dt, ' s'
739
740 end subroutine solve_intermediate_state
741
742
743 !> \brief Calculate dipole elements from intermediate and final states
744 !> \author J Benda
745 !> \date 2020 - 2024
746 !>
747 !> Calculates the transition dipole matrix element between the last intermediate state and the final
748 !> stationary photoionization state.
749 !>
750 !> \param[in] moldat MolecularData object with data read from the file *molecular_data*.
751 !> \param[in] order Perturbation order of the intermediate state to calculate.
752 !> \param[in] Ephoton Fixed photon energies in a.u. or zeros for flexible photons.
753 !> \param[in] icomp Which Cartesian component of the dipole operator will give rise to the intermediate state.
754 !> \param[in] s Which "transition" in *molecular_data* corresponds to the action of this dipole component on parent.
755 !> \param[in] cache Precomputed integrals.
756 !> \param[in] mgvnn MGVN of the previous intermediate state.
757 !> \param[in] mgvn1 Ket MGVN for the transition "s" as stored in *molecular_data*.
758 !> \param[in] mgvn2 Bra MGVN for the transition "s" as stored in *molecular_data*.
759 !> \param[in] km KMatrix objects for relevant irreducible representations with data read from RSOLVE K-matrix files.
760 !> \param[in] ak Wave function coeffs (from RSOLVE) for the same set of irrs as km.
761 !> \param[inout] state Tree of intermediate states, pointing at the previous intermediate state to develop into next state.
762 !> \param[in] verbose Debugging output intensity.
763 !> \param[in] calc_Ei Initial state energy calculated by (MPI-)SCATCI or BOUND.
764 !> \param[in] first_IP Ionization potential according to the input namelist.
765 !> \param[in] r0 Radius from which to apply asymptotic integrals.
766 !> \param[in] erange Range (subset) of energies in K-matrix files to use for calculations.
767 !>
768 subroutine extract_dipole_elements (moldat, order, Ephoton, icomp, s, cache, mgvnn, mgvn1, mgvn2, &
769 km, ak, state, verbose, calc_Ei, first_IP, r0, erange)
770
771 use multidip_io, only: moleculardata, kmatrix, scatakcoeffs, myproc, nprocs, apply_dipole_matrix
772 use multidip_outer, only: evaluate_fundamental_solutions, calculate_k_matrix
774 use multidip_special, only: calculate_s_matrix, calculate_t_matrix, blas_zgemv => zgemv
775
776 type(moleculardata), intent(in) :: moldat
777 type(kmatrix), allocatable, intent(in) :: km(:)
778 type(scatakcoeffs), allocatable, intent(in) :: ak(:)
779 type(integral_cache_t), intent(in) :: cache(:)
780
781 type(intermediatestate), pointer, intent(inout) :: state
782 type(intermediatestate), pointer :: last
783
784 integer, intent(in) :: order, icomp, s, mgvnn, mgvn1, mgvn2, erange(2)
785 logical, intent(in) :: verbose
786 real(wp), intent(in) :: Ephoton(:), calc_Ei, first_IP, r0
787
788 character(len=1) :: transp
789 character(len=1024) :: filename
790
791 complex(wp), allocatable :: Af(:, :), dm(:), dp(:), Sm(:, :), tmat(:, :)
792 real(wp), allocatable :: Dpsi(:, :, :), Ef(:), echlf(:), d_inner(:, :), d_outer(:, :), Re_Af(:, :), Im_Af(:, :)
793 real(wp), allocatable :: omega(:), d_total(:, :), kmat(:, :), Sf(:), Cf(:), Sfp(:), Cfp(:)
794 integer, allocatable :: lf(:)
795
796 real(wp) :: Etotf, Ei, Ek
797 integer :: mgvnf, irrn, irrf, ikn, ikf, nstatn, nstatf, nchann, nchanf, nesc, ie, nopen, nochf, mye, nene, maxchan, i
798 integer :: t, dt
799
800 integer(blasint) :: m, n, lds, inc = 1
801
802 if (mgvnn == mgvn1) then
803 mgvnf = mgvn2
804 transp = 'T'
805 else
806 mgvnf = mgvn1
807 transp = 'N'
808 end if
809
810 irrn = findloc(moldat % mgvns, mgvnn, 1)
811 irrf = findloc(moldat % mgvns, mgvnf, 1)
812
813 ikn = findloc(km(:) % mgvn, mgvnn, 1)
814 ikf = findloc(km(:) % mgvn, mgvnf, 1)
815
816 nstatn = moldat % mnp1(irrn)
817 nstatf = moldat % mnp1(irrf)
818
819 nchann = km(ikn) % nchan
820 nchanf = km(ikf) % nchan
821
822 nesc = erange(2) - erange(1) + 1
823 nene = (nesc + nprocs - 1) / nprocs
824 mye = 0
825 t = 0
826
827 maxchan = nchanf
828 if (maxtarg > 0) then
829 maxchan = count(moldat % ichl(1:nchanf, irrf) <= maxtarg)
830 end if
831
832 allocate (dpsi(nstatf, 2, nene), omega(order), ef(nchanf), lf(nchanf), echlf(nchanf), &
833 re_af(nstatf, maxchan), im_af(nstatf, maxchan), af(nstatf, maxchan), sm(nchanf, nchanf), &
834 d_inner(maxchan, 2), d_outer(maxchan, 2), d_total(maxchan, 2), dm(maxchan), dp(nchanf), &
835 sf(nchanf), cf(nchanf), sfp(nchanf), cfp(nchanf), kmat(nchanf, nchanf), tmat(nchanf, nchanf))
836
837 echlf(1:nchanf) = moldat % etarg(moldat % ichl(1:nchanf, irrf)) - moldat % etarg(1)
838
839 ! set up a final state at the end of the storage chain
840 last => state
841 do while (associated(last % next))
842 last => last % next
843 end do
844 allocate (last % next)
845 last % next % prev => last
846 last => last % next
847 last % order = order
848 last % mgvn = mgvnf
849 last % dcomp = icomp
850 last % parent => state
851 allocate (last % dip(maxchan, 2, nene))
852
853 call print_transition_header(last)
854
855 ! apply the inner dipoles matrix on the previous inner region solution expansions
856 call reset_timer(t, dt)
857 call apply_dipole_matrix(moldat, icomp, s, transp, nstatf, nstatn, state % ck, dpsi)
858 call reset_timer(t, dt)
859 print '(4x,A,I0,A)', 'inner dipoles applied in ', dt, ' s'
860
861 ! position K-matrix file pointer at the beginning of (this process') K-matrix data
862 call km(ikf) % reset_kmatrix_position(skip = myproc - 1)
863 if (allocated(ak)) call ak(ikf) % reset_wfncoeffs_position(skip = myproc - 1)
864
865 ! for all scattering energies
866 do ie = myproc, erange(2), nprocs
867
868 ! read the K-matrix and Ak coeffs from the file and then skip the next 'nprocs - 1' records used by other images
869 call km(ikf) % get_kmatrix(kmat, skip = nprocs - 1)
870 if (allocated(ak)) call ak(ikf) % get_wfncoeffs(ek, re_af, im_af, skip = nprocs - 1)
871 if (ie < erange(1)) cycle
872
873 mye = mye + 1
874 last % dip(:, :, mye) = 0
875
876 ! calculate photon energies and the initial state energy (take into account user-specified first ionization potential)
877 ei = calc_ei
878 call calculate_photon_energies(first_ip, km(ikf) % escat(ie), moldat % etarg(1), ei, ephoton, omega)
879
880 ! quantum numbers of the final state
881 etotf = ei + sum(omega)
882 ef = km(ikf) % escat(ie) - echlf ! photoelectron energies in all (open and closed) channels
883 lf = moldat % l2p(1:nchanf, irrf) ! photoelectron angular momentum in all (open and closed) channels
884 nopen = count(ef > 0) ! number of all open channels
885 nochf = min(nopen, maxchan) ! number of open channels for which we want to obtain dipoles
886
887 print '(4x,A,*(I0,A))', 'energy ', ie, ' of ', km(ikf) % nescat, ' (', nopen, ' open channels of ', nchanf, ')'
888
889 ! get values and derivatives of the fundamental solutions of uncoupled (or dipole-coupled) outer region equations
890 call evaluate_fundamental_solutions(moldat, moldat % rmatr, irrf, nchanf, ef, sf, cf, sfp, cfp, sqrtknorm = .true.)
891
892 ! if desired, recalculate the K-matrix here
893 if (custom_kmatrices) then
894 call calculate_k_matrix(moldat, nopen, irrf, etotf, sf, cf, sfp, cfp, kmat)
895 end if
896 call calculate_t_matrix(kmat, tmat, nopen)
897
898 ! contract D*psi with complex-conjugated wave function coefficients Ak to get inner region contribution
899 if (.not. allocated(ak)) then
900 call apply_ak_coefficients_multidip(dpsi(:, :, mye), d_inner, moldat, nopen, irrf, etotf, &
901 sfp, cfp, kmat, tmat, .true.)
902 else
903 call apply_ak_coefficiens_compak(dpsi(:, :, mye), d_inner, re_af(:, 1:nochf), im_af(:, 1:nochf))
904 end if
905
906 ! outer region correction
907 dm = 0
908 dp = 0
909 call multiint(moldat, r0, ei, km(ikf) % escat(ie), omega, mye, last, -1, dm(1:nochf), cache)
910 call multiint(moldat, r0, ei, km(ikf) % escat(ie), omega, mye, last, +1, dp(1:nopen), cache)
911 dm(1:nochf) = dm(1:nochf) * imu / sqrt(2*pi*sqrt(2*ef(1:nochf)))
912 dp(1:nopen) = dp(1:nopen) * imu / sqrt(2*pi*sqrt(2*ef(1:nopen)))
913 if (verbose) print '(5x,A,1x,*(1x,E25.15))', 'dm = ', dm(1:nochf)
914 if (verbose) print '(5x,A,1x,*(1x,E25.15))', 'dp = ', dp(1:nopen)
915 if (any(dp /= 0) .or. test_expansion) then
916 m = int(nopen, blasint)
917 n = int(nochf, blasint)
918 lds = int(nchanf, blasint)
919 call calculate_s_matrix(tmat, sm, nopen)
920 call blas_zgemv('T', m, n, -cone, sm, lds, dp, inc, cone, dm, inc) ! dm = dm - S^T dp
921 end if
922 d_outer(:, 1) = real(dm)
923 d_outer(:, 2) = aimag(dm)
924
925 ! for debugging purposes
926 if (test_expansion) then
927 write (filename, '(a,i0,a,i0,a)') 'final-state-', irrf, '-', ie, '.txt'
928 call test_final_expansion(trim(filename), moldat, irrf, nopen, etotf, ef, sfp, cfp, kmat, tmat)
929 end if
930
931 ! combine and store the partial wave dipoles
932 d_total = d_inner + d_outer
933 last % dip(:, :, mye) = d_total
934
935 if (verbose) print '(5x,A,1x,*(1x,E25.15))', 'd_inner = ', (d_inner(i, 1), d_inner(i, 2), i = 1, nochf)
936 if (verbose) print '(5x,A,1x,*(1x,E25.15))', 'd_outer = ', (d_outer(i, 1), d_outer(i, 2), i = 1, nochf)
937 if (verbose) print '(5x,A,1x,*(1x,E25.15))', 'd_total = ', (d_total(i, 1), d_total(i, 2), i = 1, nochf)
938
939 end do
940
941 call reset_timer(t, dt)
942 print '(4x,A,I0,A)', 'matrix elements calculated in ', dt, ' s'
943
944 end subroutine extract_dipole_elements
945
946
947 !> \brief Prints a one-line summary of the transition
948 !> \authors J Benda
949 !> \date 2023
950 !>
951 !> A sample output can look like this:
952 !> \verbatim
953 !> Transtion 0 -[x]-> 1 -[y]-> 3
954 !> \endverbatim
955 !>
956 subroutine print_transition_header (state)
957
958 use multidip_params, only: compt
959
960 type(intermediatestate), pointer, intent(in) :: state
961 type(intermediatestate), pointer :: ptr
962
963 integer, allocatable :: irreds(:), compts(:)
964 integer :: order, i
965
966 order = state % order
967
968 allocate (irreds(order + 1), compts(order))
969
970 ptr => state
971 irreds(order + 1) = ptr % mgvn
972 do while (associated(ptr % parent))
973 compts(order) = ptr % dcomp
974 ptr => ptr % parent
975 irreds(order) = ptr % mgvn
976 order = order - 1
977 end do
978
979 write (*, '(2x,a)', advance = 'no') 'Transition'
980 do i = 1, state % order
981 write (*, '(1x,i0,1x,3a)', advance = 'no') irreds(i), '-[', compt(compts(i)), ']->'
982 end do
983 write (*, '(1x,i0)') irreds(state % order + 1)
984
985 end subroutine print_transition_header
986
987
988 !> \brief Multiply vector by the (complex-conjugated) wave function coefficients
989 !> \authors J Benda
990 !> \date 2023
991 !>
992 !> Contracts the provided wave function coefficients with the given inner region expansion.
993 !>
994 !> \param[in] psi Inner region wave function expansion coefficients as two columns: real and imaginary part.
995 !> \param[in] Apsi The result, in the same form as psi.
996 !> \param[in] ReAk Real part of the wave function coefficients of shape (nstat, nopen).
997 !> \param[in] ImAk Imag part of the wave function coefficients of shape (nstat, nopen).
998 !>
999 subroutine apply_ak_coefficiens_compak (psi, Apsi, ReAk, ImAk)
1000
1001 use multidip_params, only: rone, rzero
1002 use multidip_special, only: blas_dgemv => dgemv
1003
1004 real(wp), intent(in) :: psi(:, :), ReAk(:, :), ImAk(:, :)
1005 real(wp), intent(inout) :: Apsi(:, :)
1006
1007 integer(blasint) :: m, n, inc = 1
1008
1009 m = int(size(reak, 1), blasint)
1010 n = int(size(reak, 2), blasint)
1011
1012 call blas_dgemv('T', m, n, rone, reak, m, psi(:, 1), inc, rzero, apsi(:, 1), inc)
1013 call blas_dgemv('T', m, n, +rone, imak, m, psi(:, 2), inc, +rone, apsi(:, 1), inc)
1014 call blas_dgemv('T', m, n, rone, reak, m, psi(:, 2), inc, rzero, apsi(:, 2), inc)
1015 call blas_dgemv('T', m, n, -rone, imak, m, psi(:, 1), inc, +rone, apsi(:, 2), inc)
1016
1017 end subroutine apply_ak_coefficiens_compak
1018
1019
1020 !> \brief Multiply vector by the (complex-conjugated) wave function coefficients
1021 !> \authors J Benda
1022 !> \date 2023 - 2024
1023 !>
1024 !> Contracts the provided wave function coefficients with the inner region expansion, which will be computed on the fly.
1025 !> The following formula is used:
1026 !> \f[
1027 !> A_{rj}^{(-)}(E) = \frac{1}{2} \frac{1}{E_j - E} \sum_{pq} w_{pj} F_{pq}^{(-)\prime} \,,
1028 !> \f]
1029 !> where
1030 !> \f[
1031 !> F_{pq}^{(-)\prime} = \sum_{s} \sqrt{\frac{2}{\pi k_p}} (S_p' \delta_{ps} + C_p' K_{ps})
1032 !> [(1 + i K_{oo})^{-1}]_{sq}\,.
1033 !> \f]
1034 !> The last factor uses only the open-open subset of the K-matrix. In this subroutine it is evaluated by means
1035 !> of the T-matrix (defined as S - 1) as
1036 !> \f[
1037 !> (1 + iK)^{-1} = 1 + \frac{1}{2}T^* \,.
1038 !> \f]
1039 !>
1040 !> Altogether, application of this subroutine does the following:
1041 !> \f[
1042 !> \chi = \psi \mathsf{A}^{(-)*} = \psi \frac{1}{\mathsf{E} - E} \mathsf{w}^\top \sqrt{ \frac{2}{\pi \mathsf{k}} }
1043 !> (\mathsf{T}\mathsf{S}' + \mathsf{T} \mathsf{C}' \mathsf{K})^* (1 + i\mathsf{K})^{-1*} \,.
1044 !> \f]
1045 !>
1046 !> \param[in] psi Inner region wave function expansion coefficients as two columns: real and imaginary part.
1047 !> \param[in] Apsi The result, in the same form as psi.
1048 !> \param[in] moldat Molecular data class.
1049 !> \param[in] nopen Number of open channels.
1050 !> \param[in] irr Irreducible representation index.
1051 !> \param[in] Etot Total energy of the system.
1052 !> \param[in] Sp Derivative of the regular asymptotic solution at boundary per partial wave channel.
1053 !> \param[in] Cp Derivative of the irregular asymptotic solution at boundary per partial wave channel.
1054 !> \param[in] kmat K-matrix
1055 !> \param[in] tmat T-matrix defined as T = S - 1
1056 !> \param[in] conj Apply a conjugated wave-function coefficient (for photodipoles)
1057 !>
1058 subroutine apply_ak_coefficients_multidip (psi, Apsi, moldat, nopen, irr, Etot, Sp, Cp, kmat, tmat, conj)
1059
1060 use multidip_io, only: moleculardata, apply_boundary_amplitudes
1061 use multidip_params, only: czero, rzero
1062
1063 type(moleculardata), intent(in) :: moldat
1064 real(wp), intent(in) :: psi(:, :), Etot
1065 real(wp), intent(inout) :: Apsi(:, :)
1066 integer, intent(in) :: nopen, irr
1067 real(wp), intent(in) :: Sp(:), Cp(:), kmat(:, :)
1068 complex(wp), intent(in) :: tmat(:, :)
1069 logical, intent(in) :: conj
1070
1071 real(wp), allocatable :: tmps(:, :), tmpc(:, :)
1072 complex(wp), allocatable :: tmpo(:)
1073
1074 integer :: nchan, nstat, i, j
1075 real(wp) :: Fij
1076 complex(wp) :: z, T
1077
1078 nstat = moldat % mnp1(irr)
1079 nchan = moldat % nchan(irr)
1080
1081 allocate (tmps(nstat, 2), tmpc(nchan, 2), tmpo(nopen))
1082
1083 ! multiply solution by R-matrix poles
1084 tmps(:, 1) = psi(:, 1) / (moldat % eig(1:nstat, irr) - etot)
1085 tmps(:, 2) = psi(:, 2) / (moldat % eig(1:nstat, irr) - etot)
1086
1087 ! contract with the boundary amplitudes
1088 call apply_boundary_amplitudes(moldat, irr, 'N', tmps, tmpc)
1089
1090 ! multiply by complex-conjugated matrix F' = (TS' + TC'K) / √2π
1091 do j = 1, nopen
1092 tmpo(j) = 0
1093 do i = 1, nchan
1094 fij = (merge(sp(i), rzero, i == j) + cp(i)*kmat(i, j)) / sqrt(2*pi)
1095 tmpo(j) = tmpo(j) + cmplx(tmpc(i, 1), tmpc(i, 2), wp)*fij
1096 end do
1097 end do
1098
1099 ! multiply by complex-conjugated T (nopen-by-nopen)
1100 do j = 1, min(nopen, size(apsi, 1))
1101 apsi(j, 1) = 0
1102 apsi(j, 2) = 0
1103 do i = 1, nopen
1104 t = merge(1, 0, i == j) + conjg(tmat(i, j))/2 ! = (1 + iK)⁻¹
1105 if (conj) t = conjg(t)
1106 z = tmpo(i) * t
1107 apsi(j, 1) = apsi(j, 1) + real(z)
1108 apsi(j, 2) = apsi(j, 2) + aimag(z)
1109 end do
1110 end do
1111
1112 ! erase the closed-channel amplitudes
1113 do j = nopen + 1, min(nchan, size(apsi, 1))
1114 apsi(j, 1) = 0
1115 apsi(j, 2) = 0
1116 end do
1117
1118 end subroutine apply_ak_coefficients_multidip
1119
1120
1121 !> \brief Write radially sampled final wave-function to file
1122 !> \authors J Benda
1123 !> \date 2023 - 2024
1124 !>
1125 !> Sample the final wave function in all channels at several radii and print results to the provided file. The sampling
1126 !> inside the inner region is fully given by the sampling points defined in the molecular_data file. In the outer region,
1127 !> the values are obtained at equidistant points stretching to twice the R-matrix sphere radius.
1128 !>
1129 subroutine test_final_expansion (filename, moldat, irr, nopen, Etot, Ek, Sp, Cp, kmat, tmat)
1130
1131 use multidip_io, only: moleculardata
1133
1134 character(len=*), intent(in) :: filename
1135 type(moleculardata), intent(in) :: moldat
1136 integer, intent(in) :: irr, nopen
1137 real(wp), intent(in) :: Etot, Ek(:), Sp(:), Cp(:), kmat(:, :)
1138 complex(wp), intent(in) :: tmat(:, :)
1139
1140 real(wp), allocatable :: psi(:, :), Apsi(:, :), S(:), C(:), dSdr(:), dCdr(:)
1141 complex(wp), allocatable :: Fqp(:, :), Hp(:), Hm(:)
1142
1143 real(wp) :: dr, r
1144 integer :: i, nfdm, u, nchan, p, q, nstat
1145
1146 nfdm = size(moldat % r_points) - 1
1147 nchan = moldat % nchan(irr)
1148 nstat = moldat % mnp1(irr)
1149 dr = 0.1
1150
1151 allocate (fqp(nchan, nopen), psi(nstat, 2), apsi(nchan, 2), s(nchan), c(nchan), hp(nchan), hm(nchan), dsdr(nchan), &
1152 dcdr(nchan))
1153
1154 open (newunit = u, file = filename, action = 'write', form = 'formatted')
1155
1156 ! inner region
1157 do i = 1, nfdm
1158 write (u, '(e25.15)', advance = 'no') moldat % r_points(i)
1159 do q = 1, nchan
1160 if (.not. associated(moldat % wmat2(i, irr) % mat)) then
1161 print '(a)', 'Error: wmat2 has not been read from molecular_data'
1162 stop 1
1163 else if (moldat % wmat2(i, irr) % distributed) then
1164 print '(a)', 'Error: test_outer_expansion not implemented in MPI-IO mode'
1165 stop 1
1166 end if
1167 ! the "wave-function" is a row of a boundary amplitudes matrix to reduce the Ak coefficient with
1168 psi(:, 1) = moldat % wmat2(i, irr) % mat(q, :)
1169 psi(:, 2) = 0
1170 ! perform the reduction w*A
1171 call apply_ak_coefficients_multidip(psi, apsi, moldat, nopen, irr, etot, sp, cp, kmat, tmat, .false.)
1172 ! combine components to a single complex number
1173 fqp(q, 1:nopen) = cmplx(apsi(1:nopen, 1), apsi(1:nopen, 2), wp)
1174 end do
1175 write (u, '(*(e25.15))') fqp(1:nchan, 1:nopen)
1176 end do
1177
1178 ! outer region
1179 do i = 0, nint(moldat % rmatr/dr)
1180 r = moldat % rmatr + i*dr
1181 write (u, '(e25.15)', advance = 'no') r
1182 call evaluate_fundamental_solutions(moldat, r, irr, nchan, ek, s, c, dsdr, dcdr)
1183 hp = (c + imu*s) * (-imu) / sqrt(2*pi)
1184 hm = (c - imu*s) * (-imu) / sqrt(2*pi)
1185 do p = 1, nopen
1186 do q = 1, nchan
1187 if (q == p) fqp(q, p) = hp(q)
1188 if (q /= p) fqp(q, p) = 0
1189 fqp(q, p) = fqp(q, p) - hm(q) * conjg(1 + tmat(q, p))
1190 write (u, '(*(e25.15))', advance = 'no') fqp(q, p)
1191 end do
1192 end do
1193 write (u, '()')
1194 end do
1195
1196 close (u)
1197
1198 end subroutine test_final_expansion
1199
1200
1201 !> \brief Get current time stamp
1202 !> \author J Benda
1203 !> \date 2022
1204 !>
1205 !> Update the parameter "t" with the current system time stamp. Store into "dt" the elapsed time in seconds.
1206 !> If the elapsed time is longer than an integer multiple of the system clock period, only the remainder will
1207 !> be reported.
1208 !>
1209 !> In parallel mode individual processes work in sync, so we have to wait for all before reading out the time.
1210 !>
1211 subroutine reset_timer (t, dt)
1212
1213 use mpi_gbl, only: mpi_mod_barrier
1214
1215 integer, intent(inout) :: t
1216 integer, intent(out) :: dt
1217 integer :: clk_now, clk_rate, clk_max, ierr
1218
1219 call mpi_mod_barrier(ierr)
1220
1221 call system_clock(clk_now, clk_rate, clk_max)
1222
1223 if (t < clk_now) then
1224 dt = (clk_now - t) / clk_rate
1225 else
1226 ! Cater for possible system clock wrap-around. Also pay attention to integer overflow; clk_max
1227 ! is typically the upper limit of the given integer type.
1228 dt = ((clk_max - t) + clk_now) / clk_rate
1229 end if
1230
1231 t = clk_now
1232
1233 end subroutine reset_timer
1234
1235
1236 !> \brief Adjust ionization potential and calculate energy of each photon
1237 !> \author J Benda
1238 !> \date 2021 - 2024
1239 !>
1240 !> Calculate energies of all photons. The positive elements of the input array `Ephoton` specify fixed
1241 !> energies of selected photons. The remaining energy needed to reach the ionisation potential is divided
1242 !> among the remaining photons.
1243 !>
1244 !> The user-specified parameter `first_IP` will be used to redefine the energy of the initial state before ionization.
1245 !>
1246 !> \param[in] first_IP Total energy needed to ionize the target to the first ionic state.
1247 !> \param[in] escat Photoelectron kinetic energy after absorption of all photons (only needed for "floating IP").
1248 !> \param[in] etarg Energy of the first ionic state.
1249 !> \param[inout] Ei Energy of the initial state before ionization; will be shifted if `first_IP` is non-zero.
1250 !> \param[in] Ephoton User-specified fixed energies of photons or zeros for flexible photons.
1251 !> \param[inout] omega Calculated energies of all photons.
1252 !>
1253 subroutine calculate_photon_energies (first_IP, escat, etarg, Ei, Ephoton, omega)
1254
1255 real(wp), intent(in) :: first_IP, escat, etarg, Ephoton(:)
1256 real(wp), intent(inout) :: Ei, omega(:)
1257 real(wp) :: IP, Ef
1258
1259 ! calculate total final energy
1260 ef = etarg + escat
1261
1262 ! obtain the calculated ionization potential (including non-zero photoelectron energy)
1263 ip = ef - ei
1264
1265 ! set the requested ionization potential by shifting the initial state energy
1266 if (first_ip > 0) then
1267 ip = first_ip + escat
1268 ei = ef - ip
1269 end if
1270
1271 ! calculate photon energies
1272 where (ephoton /= 0)
1273 omega = ephoton
1274 elsewhere
1275 omega = (ip - sum(ephoton)) / (size(omega) - count(ephoton /= 0))
1276 end where
1277
1278 end subroutine calculate_photon_energies
1279
1280
1281 !> \brief Precompute outer radial integrals (driver)
1282 !> \authors J Benda
1283 !> \date 2024
1284 !>
1285 !> If `cache_integrals` is enabled, precompute all needed radial outer region multiphoton integrals.
1286 !>
1287 !> \param[inout] integral_cache Precomputed integrals per this task's energy.
1288 !> \param[in] moldat Molecular data.
1289 !> \param[in] esc Scattering energies.
1290 !> \param[in] nphot Number of absorbed photons.
1291 !> \param[in] r0 Numerical integration distance.
1292 !> \param[in] erange Index of the first and last scatternig energy to consider.
1293 !> \param[in] calc_Ei The calculated energy of the initial state.
1294 !> \param[in] first_IP First ionization potential needed to obtain photon energies from known kinetic energies.
1295 !> \param[in] Ephoton Absorbed photon energies.
1296 !> \param[in] verbose Whether to print the cache to standard output.
1297 !>
1298 subroutine precompute_integral_cache (integral_cache, moldat, esc, nphot, r0, erange, calc_Ei, first_IP, Ephoton, verbose)
1299
1301 use multidip_io, only: moleculardata, myproc, nprocs
1303
1304 type(integral_cache_t), allocatable, intent(inout) :: integral_cache(:)
1305 type(moleculardata), intent(in) :: moldat
1306
1307 real(wp), intent(in) :: esc(:), Ephoton(:), r0, calc_Ei, first_IP
1308 integer, intent(in) :: nphot, erange(2)
1309 logical, intent(in) :: verbose
1310
1311 complex(wp), allocatable :: ks(:)
1312 integer, allocatable :: rchs(:), ms(:), ls(:), pws_coupled(:, :), ion_coupled(:, :)
1313
1314 real(wp) :: ebase, etarg, escat, Ei, Ek, omega(size(Ephoton))
1315 integer :: nesc, nene, mye, rchi, rchj, nredch, ntarg, itarg, jtarg, li, lj, order, ie, lmax, nrchs, t, dt
1316
1317 order = merge(nphot, nphot - 1, extend_istate) ! outer integral dimension
1318
1319 if (order <= 0 .or. .not. cache_integrals) return
1320
1321 print '(2x,a)', 'Precomputing outer integrals'
1322
1323 nesc = size(esc) ! total number of scattering energies
1324 nene = (nesc + nprocs - 1) / nprocs ! number of scattering energies assigned to this process
1325 ntarg = size(moldat % etarg) ! total number of target states in outer expansion
1326 lmax = maxval(moldat % l2p) ! highest partial wave angular momentum
1327 nredch = ntarg * (lmax + 1) ! total number of reduced channels
1328 ebase = moldat % etarg(1) ! energy of the first residual ion state
1329
1330 call reset_timer(t, dt)
1331
1332 allocate (integral_cache(nene), rchs(order + 1), ms(order), ks(order + 1), ls(order + 1), &
1333 pws_coupled(nredch + 1, nredch), ion_coupled(nredch + 1, nredch))
1334
1335 ! find out which reduced channels are dipole-coupled in partial waves
1336 pws_coupled = 0
1337 do li = 0, lmax
1338 do lj = max(0, li - 1), min(lmax, li + 1)
1339 do itarg = 1, ntarg
1340 rchi = li*ntarg + itarg
1341 rchj = lj*ntarg + itarg
1342 pws_coupled(rchi + 1, rchj) = rchi
1343 pws_coupled(rchj + 1, rchi) = rchj
1344 end do
1345 end do
1346 end do
1347
1348 ! find out which reduced channels are dipole-coupled in residual ion
1349 ion_coupled = 0
1350 do itarg = 1, ntarg
1351 do jtarg = 1, ntarg
1352 if (any(moldat % crlv(itarg, jtarg, :) /= 0)) then
1353 do li = 0, lmax
1354 rchi = li*ntarg + itarg
1355 rchj = li*ntarg + jtarg
1356 ion_coupled(rchi + 1, rchj) = rchi
1357 ion_coupled(rchj + 1, rchi) = rchj
1358 end do
1359 end if
1360 end do
1361 end do
1362
1363 ! for each reduced channel assemble a list of dipole-coupled reduced channels
1364 do rchi = 1, nredch
1365 ! coupled partial waves
1366 nrchs = count(pws_coupled(2:, rchi) /= 0, 1)
1367 pws_coupled(1, rchi) = nrchs
1368 pws_coupled(2:nrchs + 1, rchi) = pack(pws_coupled(2:, rchi), pws_coupled(2:, rchi) /= 0)
1369 ! coupled residual ions
1370 nrchs = count(ion_coupled(2:, rchi) /= 0, 1)
1371 ion_coupled(1, rchi) = nrchs
1372 ion_coupled(2:nrchs + 1, rchi) = pack(ion_coupled(2:, rchi), ion_coupled(2:, rchi) /= 0)
1373 end do
1374
1375 ! allocate integral storage
1376 do mye = 1, nene
1377 allocate (integral_cache(mye) % next_pws(nredch))
1378 end do
1379
1380 ! precompute the integrals in parallel
1381 !$omp parallel do schedule(dynamic, 1) default(none) &
1382 !$omp& private(mye, rchi, itarg, ie, Ei, Ek, etarg, escat) &
1383 !$omp& shared(nene, nredch, integral_cache, moldat, pws_coupled, ion_coupled, nprocs, myproc, esc) &
1384 !$omp& firstprivate(rchs, ms, ks, ls, order, ntarg, nphot, ebase, r0, omega, erange, Ephoton, calc_Ei, first_IP)
1385 do mye = 1, nene
1386 ! calculate global energy index
1387 ie = (mye - 1)*nprocs + myproc
1388 if (ie < erange(1) .or. ie > erange(2)) cycle
1389 ! calculate unspecified photon energies
1390 ei = calc_ei
1391 call calculate_photon_energies(first_ip, esc(ie), ebase, ei, ephoton, omega)
1392 ! process all final reduced channels
1393 do rchi = 1, nredch
1394 rchs(order + 1) = rchi
1395 itarg = mod(rchi - 1, ntarg) + 1
1396 etarg = moldat % etarg(itarg)
1397 escat = esc(ie) - sum(omega(nphot + 1:))
1398 ek = escat + ebase - etarg
1399 if (ek >= 0) then
1400 ks(order + 1) = sqrt(2*ek)
1401 else
1402 ks(order + 1) = cmplx(0._wp, sqrt(-2*ek), wp)
1403 cycle ! FIXME: closed channels seem to be used in 3+ photon ionization?
1404 end if
1405 ls(order + 1) = (rchi - 1) / ntarg
1406 call precompute_integral_cache_block(integral_cache(mye) % next_pws(rchi), moldat, pws_coupled, ion_coupled, &
1407 order, omega(1:nphot), escat, r0, rchs, ms, ks, ls)
1408 end do
1409 end do
1410
1411 if (verbose) then
1412 call print_integral_cache(integral_cache, erange, ntarg)
1413 end if
1414
1415 call reset_timer(t, dt)
1416
1417 print '(4x,a,i0,a)', 'radial integrals precomputed in ', dt, ' s'
1418
1419 end subroutine precompute_integral_cache
1420
1421
1422 !> \brief Precompute outer radial integrals (implementation)
1423 !> \authors J Benda
1424 !> \date 2024
1425 !>
1426 !> Follow on the provided chain of dipole-coupled reduced channels, precomputing all outer-region multi-photon integrals along
1427 !> the way.
1428 !>
1429 !> \param[inout] integral_cache A block of precomputed integrals.
1430 !> \param[in] moldat Molecular data.
1431 !> \param[in] pws_coupled One column for each reduced chan.: number of pws-coupled reduced chans. followed by their list.
1432 !> \param[in] ion_coupled One column for each reduced chan.: number of ion-coupled reduced chans. followed by their list.
1433 !> \param[in] order Maximal dimension of integrals to recompute.
1434 !> \param[in] omega Absorbed photon energies.
1435 !> \param[in] escat Energy of the photoelectron in the first channel after absorption of all photons.
1436 !> \param[in] r0 Numerical integration distance.
1437 !> \param[inout] rchs Reduced channels chain: (i,l), (i',l'), (i'',l''), ...
1438 !> \param[inout] ms Coordinate powers per photon absorption.
1439 !> \param[inout] ks Final and intermediate momenta (possibly complex) of the photoelectron in this chain.
1440 !> \param[inout] ls Angular momenta of the photoelectron in this chain.
1441 !>
1442 recursive subroutine precompute_integral_cache_block (integral_cache, moldat, pws_coupled, ion_coupled, order, omega, &
1443 escat, r0, rchs, ms, ks, ls)
1444
1446 use multidip_io, only: moleculardata
1448
1449 type(integral_cache_t), intent(inout) :: integral_cache
1450 type(moleculardata), intent(in) :: moldat
1451
1452 real(wp), intent(in) :: omega(:), escat, r0
1453 integer, intent(in) :: order, pws_coupled(:, :), ion_coupled(:, :)
1454 integer, intent(inout) :: rchs(:), ms(:), ls(:)
1455 complex(wp), intent(inout) :: ks(:)
1456
1457 integer :: rchi, rchj, n, ntarg, ncoup, icoup, jtarg
1458 real(wp) :: ebase, etarg, a, z, c, ek, ekj
1459
1460 rchi = rchs(order + 1) ! current reduced channel
1461 n = size(rchs) - order ! dimension of outer integrals that end at this absorption level
1462 ebase = moldat % etarg(1) ! energy of the first residual ion
1463 ntarg = size(moldat % etarg) ! number of residual ion states
1464 a = moldat % rmatr ! R-matrix radius
1465 z = moldat % nz - moldat % nelc ! residual ion charge
1466 c = 0 ! dipole damping factor
1467
1468 ! kinetic energy of photoelectron in the first channel before absorbing the current photon
1469 ek = escat - sum(omega(size(omega) - order + 1:))
1470
1471 ! process all reduced channels that are pws-coupled to the current reduced channel chain
1472 ncoup = pws_coupled(1, rchi)
1473 allocate (integral_cache % rchs_pws(ncoup), integral_cache % vals_pws(2, ncoup), integral_cache % next_pws(ncoup))
1474 do icoup = 1, ncoup
1475 rchj = pws_coupled(1 + icoup, rchi) ! index of reduced channel pws-coupled to rchi
1476 jtarg = mod(rchj - 1, ntarg) + 1 ! corresponding target state
1477 etarg = moldat % etarg(jtarg) ! energy of the target state
1478 ekj = ek + ebase - etarg ! photoelectron kinetic energy in this channel
1479 if (ekj + closed_range <= 0) cycle ! ignore strongly closed channels
1480 if (ekj < 0 .and. .not. closed_interm) cycle ! optionally ignore also weakly closed channels
1481 rchs(order) = rchj ! store the reduced channel before absorption
1482 if (ekj >= 0) then
1483 ks(order) = sqrt(2*ekj) ! photoelectron momentum before absorption
1484 else
1485 ks(order) = cmplx(0._wp, sqrt(-2*ekj), wp) ! use imaginary momentum for closed channels
1486 end if
1487 ls(order) = (rchj - 1)/ntarg ! photoelectron angular momentum before absorption
1488 ms(order) = 1 ! coordinate power (dipole operator)
1489 integral_cache % rchs_pws(icoup) = rchs(order)
1490 integral_cache % vals_pws(1, icoup) = nested_cgreen_integ(z, a, r0, c, n, +1, +1, ms(order:), ls(order:), ks(order:))
1491 integral_cache % vals_pws(2, icoup) = nested_cgreen_integ(z, a, r0, c, n, +1, -1, ms(order:), ls(order:), ks(order:))
1492 if (order > 1) then
1493 call precompute_integral_cache_block(integral_cache % next_pws(icoup), moldat, pws_coupled, ion_coupled, &
1494 order - 1, omega, escat, r0, rchs, ms, ks, ls)
1495 end if
1496 end do
1497
1498 ! process all reduced channels that are ion-coupled to the current reduced channel chain
1499 ncoup = ion_coupled(1, rchi)
1500 allocate (integral_cache % rchs_ion(ncoup), integral_cache % vals_ion(2, ncoup), integral_cache % next_ion(ncoup))
1501 do icoup = 1, ncoup
1502 rchj = ion_coupled(1 + icoup, rchi) ! index of reduced channel pws-coupled to rchi
1503 jtarg = mod(rchj - 1, ntarg) + 1 ! corresponding target state
1504 etarg = moldat % etarg(jtarg) ! energy of the target state
1505 ekj = ek + ebase - etarg ! photoelectron kinetic energy in this channel
1506 if (ekj + closed_range <= 0) cycle ! ignore strongly closed channels
1507 if (ekj < 0 .and. .not. closed_interm) cycle ! optionally ignore also weakly closed channels
1508 rchs(order) = rchj ! store the reduced channel before absorption
1509 if (ekj >= 0) then
1510 ks(order) = sqrt(2*ekj) ! photoelectron momentum before absorption
1511 else
1512 ks(order) = cmplx(0._wp, sqrt(-2*ekj), wp) ! use imaginary momentum for closed channels
1513 end if
1514 ls(order) = (rchj - 1)/ntarg ! photoelectron angular momentum before absorption
1515 ms(order) = 0 ! coordinate power (overlap operator)
1516 integral_cache % rchs_ion(icoup) = rchs(order)
1517 integral_cache % vals_ion(1, icoup) = nested_cgreen_integ(z, a, r0, c, n, +1, +1, ms(order:), ls(order:), ks(order:))
1518 integral_cache % vals_ion(2, icoup) = nested_cgreen_integ(z, a, r0, c, n, +1, -1, ms(order:), ls(order:), ks(order:))
1519 if (order > 1) then
1520 call precompute_integral_cache_block(integral_cache % next_ion(icoup), moldat, pws_coupled, ion_coupled, &
1521 order - 1, omega, escat, r0, rchs, ms, ks, ls)
1522 end if
1523 end do
1524
1525 end subroutine precompute_integral_cache_block
1526
1527
1528 !> \brief Print precomputed integrals
1529 !> \authors J Benda
1530 !> \date 2024
1531 !>
1532 subroutine print_integral_cache (cache, erange, ntarg)
1533
1534 use multidip_io, only: myproc, nprocs
1535
1536 type(integral_cache_t), intent(in) :: cache(:)
1537 integer, intent(in) :: erange(2), ntarg
1538
1539 integer :: i, j
1540
1541 do i = myproc, erange(2), nprocs
1542 if (i < erange(1)) cycle
1543 print '(4x,a,i0)', 'cache for energy #', i
1544 do j = 1, size(cache(i) % next_pws)
1545 call print_integral_cache_block(cache(i) % next_pws(j), ntarg, 1, [j])
1546 end do
1547 end do
1548
1549 end subroutine print_integral_cache
1550
1551
1552 !> \brief Print precomputed integrals
1553 !> \authors J Benda
1554 !> \date 2024
1555 !>
1556 recursive subroutine print_integral_cache_block (cache, ntarg, level, chain)
1557
1558 type(integral_cache_t), intent(in) :: cache
1559 integer, intent(in) :: ntarg, level, chain(level)
1560
1561 integer :: i, j, l, rch, ichl
1562 complex(wp) :: val(2)
1563
1564 if (allocated(cache % rchs_pws)) then
1565 do i = 1, size(cache % rchs_pws)
1566 rch = chain(1)
1567 ichl = mod(rch - 1, ntarg) + 1
1568 l = (rch - 1)/ntarg
1569 write (*, '(6x,a,i0,a,i0,1x,i0)', advance = 'no') '( [', rch, '] ', ichl, l
1570 do j = 2, level
1571 rch = chain(j)
1572 ichl = mod(rch - 1, ntarg) + 1
1573 l = (rch - 1)/ntarg
1574 write (*, '(a,i0,a,i0,1x,i0)', advance = 'no') ' | [', rch, '] ', ichl, l
1575 end do
1576 rch = cache % rchs_pws(i)
1577 val = cache % vals_pws(:, i)
1578 ichl = mod(rch - 1, ntarg) + 1
1579 l = (rch - 1)/ntarg
1580 write (*, '(a,i0,a,i0,1x,i0,a,4e25.15)') ' | [', rch, '] ', ichl, l, ' ):', val
1581 call print_integral_cache_block(cache % next_pws(i), level + 1, ntarg, [chain, rch])
1582 end do
1583 end if
1584
1585 if (allocated(cache % rchs_ion)) then
1586 do i = 1, size(cache % rchs_ion)
1587 rch = chain(1)
1588 ichl = mod(rch - 1, ntarg) + 1
1589 l = (rch - 1)/ntarg
1590 write (*, '(6x,a,i0,a,i0,1x,i0)', advance = 'no') '( [', rch, '] ', ichl, l
1591 do j = 2, level
1592 rch = chain(j)
1593 ichl = mod(rch - 1, ntarg) + 1
1594 l = (rch - 1)/ntarg
1595 write (*, '(a,i0,a,i0,1x,i0)', advance = 'no') ' | [', rch, '] ', ichl, l
1596 end do
1597 rch = cache % rchs_ion(i)
1598 val = cache % vals_ion(:, i)
1599 ichl = mod(rch - 1, ntarg) + 1
1600 l = (rch - 1)/ntarg
1601 write (*, '(a,i0,a,i0,1x,i0,a,4e25.15)') ' | [', rch, '] ', ichl, l, ' ):', val
1602 call print_integral_cache_block(cache % next_ion(i), level + 1, ntarg, [chain, rch])
1603 end do
1604 end if
1605
1606 end subroutine print_integral_cache_block
1607
1608
1609 !> \brief Calculate R-matrix from boundary amplitudes
1610 !> \authors J Benda
1611 !> \date 2024
1612 !>
1613 !> Evaluate the R-matrix for given irreducible representation from the formula
1614 !> \f[
1615 !> R_{pq}(E) = \sum_j w_{jp} [E - e_j]^{-1} w_{jq}
1616 !> \f]
1617 !>
1618 !> \param[in] stage Stage of the calculation (0 = initialize OpenCL buffers, 1 = calculate, 2 = release OpenCL buffers).
1619 !> \param[in] moldat MolecularData object.
1620 !> \param[in] irr Irreducible representation index.
1621 !> \param[in] etot Total energy of the system.
1622 !> \param[in] P Poles (1/(E - etot)).
1623 !> \param[inout] Pw Ampae workspace (poles times boundary amplitudes).
1624 !> \param[inout] wPw The resulting R-matrix.
1625 !>
1626 subroutine calculate_r_matrix (stage, moldat, irr, etot, P, Pw, wPw)
1627
1628 use linalg_cl, only: is_initialized_cl, residr_cl
1630
1631 type(moleculardata), intent(in) :: moldat
1632
1633 integer, intent(in) :: stage, irr
1634 real(wp), intent(in) :: etot, P(:)
1635 real(wp), intent(inout) :: Pw(:, :), wPw(:, :)
1636
1637 integer(c_int) :: cstge, nchan, nstat, ldwam
1638 real(wp), pointer :: wamp(:, :)
1639
1640 ! gpu code path
1641 if (is_initialized_cl() /= 0) then
1642
1643 ! convert parameters to C integers
1644 cstge = int(stage, c_int)
1645 nchan = int(moldat % nchan(irr), c_int)
1646 nstat = int(moldat % mnp1(irr), c_int)
1647 ldwam = int(size(moldat % wamp(irr) % mat, 1), c_int)
1648
1649 ! set up a pointer to the (possibly dummy) boundary amplitudes matrix
1650 if (stage == 0) then
1651 wamp => moldat % wamp(irr) % mat
1652 else
1653 allocate (wamp(0, 0))
1654 end if
1655
1656 ! perform the calculation and go
1657 call residr_cl(cstge, nchan, nstat, 0_c_int, -1._wp, moldat % eig(:, irr), etot, wamp, ldwam, wpw)
1658
1659 ! clean up the allocation
1660 if (stage /= 0) then
1661 deallocate (wamp)
1662 end if
1663
1664 return
1665
1666 end if
1667
1668 ! non-gpu code path
1669 if (stage == 1) then
1670 call scale_boundary_amplitudes(moldat, irr, p, pw)
1671 call apply_boundary_amplitudes(moldat, irr, 'N', pw, wpw)
1672 end if
1673
1674 end subroutine calculate_r_matrix
1675
1676
1677 !> \brief Evaluate the correction dipole integral for all orders
1678 !> \author J Benda
1679 !> \date 2020 - 2024
1680 !>
1681 !> If the parent intermediate state has a non-vanishing outer region part, integrate the final wave function with it
1682 !> and multiply by the associated expansion coefficients 'ap'. Then, iteratively, for all parent states of the parent
1683 !> state, add their contribution by means of the Coulomb-Green's function (multiplied by their expansion coefficients).
1684 !> The deeper in the absorption chain we get, the more dimensions the resulting Coulomb-Green's integral has.
1685 !>
1686 !> \param[in] moldat multidip_io::MolecularData object with data read from *molecular_data*.
1687 !> \param[in] r0 Radius from which to apply asymptotic integrals.
1688 !> \param[in] Ei Total energy of the initial state (no photons absorbed).
1689 !> \param[in] esc Photoelectron energy in the first channel after absorption of photons at this order.
1690 !> \param[in] omega Energy of all photons.
1691 !> \param[in] ie Local energy index.
1692 !> \param[in] state Tree of parent intermediate states pointed to the last intermediate state.
1693 !> \param[in] sb Kind (sign) of the outer-most Coulomb-Hankel function.
1694 !> \param[out] dip Evaluated multi-photon matrix element.
1695 !> \param[in] cache Precomputed integrals.
1696 !>
1697 subroutine multiint (moldat, r0, Ei, esc, omega, ie, state, sb, dip, cache)
1698
1699 use mpi_gbl, only: mpi_xermsg
1700 use multidip_io, only: moleculardata
1702
1703 type(moleculardata), intent(in) :: moldat
1704 type(intermediatestate), pointer, intent(in) :: state
1705 type(integral_cache_t), intent(in) :: cache(:)
1706
1707 complex(wp), intent(inout) :: dip(:)
1708 real(wp), allocatable, intent(inout) :: omega(:)
1709 real(wp), intent(in) :: Ei, esc, r0
1710 integer, intent(in) :: ie, sb
1711
1712 complex(wp), allocatable :: k(:)
1713 integer, allocatable :: l(:), m(:)
1714
1715 real(wp) :: c, a, Ekf, ebase, echl, etarg
1716 integer :: nphot, mgvn, ichan, irr, ichl, irch, ntarg
1717
1718 dip = 0
1719
1720 if (.not. associated(state % parent)) then ! state with no parent should not be a final state in the first place
1721 call mpi_xermsg('multidip_routines', 'multiint', 'Runtime error: unexpected parent state', 1, 1)
1722 end if
1723
1724 a = moldat % rmatr ! R-matrix radius
1725 c = 0 ! dipole damping factor
1726 nphot = state % order ! number of photons absorbed by the final state
1727 mgvn = state % mgvn ! MGVN of the final state
1728 irr = findloc(moldat % mgvns, mgvn, 1) ! index of this spin-symmetry in molecular_data
1729 ebase = moldat % etarg(1) ! energy of the first ion state
1730 ntarg = size(moldat % etarg) ! number of residual ion states
1731
1732 if (.not. extend_istate .and. nphot <= 1) return
1733
1734 !$omp parallel default(none) &
1735 !$omp& shared(moldat, state, omega, dip, closed_range, closed_interm, cache_integrals, cache) &
1736 !$omp& private(k, l, m, ichan, ichl, irch, etarg, echl, Ekf) &
1737 !$omp& firstprivate(r0, c, esc, sb, ie, Ei, ebase, irr, nphot, ntarg)
1738
1739 allocate (k(nphot + 1), l(nphot + 1), m(nphot))
1740
1741 !$omp do schedule(dynamic)
1742
1743 do ichan = 1, size(dip)
1744
1745 ! set up quantum numbers for this channel
1746 ichl = moldat % ichl(ichan, irr) ! index of ion state this partial wave is coupled to
1747 etarg = moldat % etarg(ichl) ! energy of that ion state
1748 echl = etarg - ebase ! excitation threshold of this ion state w.r.t. ion ground
1749 ekf = esc - echl ! always positive for final states (but not for intermediate)
1750 if (ekf + closed_range <= 0) cycle ! ignore strongly closed channels
1751 if (ekf < 0 .and. .not. closed_interm) cycle ! optionally ignore also weakly closed channels
1752 k(1) = sqrt(2*abs(ekf)); if (ekf < 0) k(1) = k(1) * imu
1753 l(1) = moldat % l2p(ichan, irr)
1754
1755 ! evaluate the correction dipole integral for this final channel
1756 if (cache_integrals) then
1757 irch = l(1)*ntarg + ichl
1758 dip(ichan) = multiint_chain(moldat, r0, ei, esc - omega(nphot), omega, ie, c, 1, state, ichan, sb, k, l, m, &
1759 cache(ie) % next_pws(irch))
1760 else
1761 dip(ichan) = multiint_chain(moldat, r0, ei, esc - omega(nphot), omega, ie, c, 1, state, ichan, sb, k, l, m)
1762 end if
1763
1764 end do
1765
1766 !$omp end do
1767 !$omp end parallel
1768
1769 end subroutine multiint
1770
1771
1772 !> \brief Calculate dipole correction integrals at given absorption depth
1773 !> \author J Benda
1774 !> \date 2020 - 2024
1775 !>
1776 !> Recursively evaluates the outer region contributions multi-photon transition element contributions from all combinations
1777 !> of channels at all absorption levels that share the initial sequence given by k, l and m.
1778 !>
1779 !> \param[in] moldat multidip_io::MolecularData object with data read from *molecular_data*.
1780 !> \param[in] r0 Radius from which to apply asymptotic integrals.
1781 !> \param[in] Ei Total energy of the initial state.
1782 !> \param[in] esc Photoelectron energy in the first channel after absorption of photons at this order.
1783 !> \param[in] omega Energy of all photons.
1784 !> \param[in] ie Local energy index.
1785 !> \param[in] c Dipole exponential damping coefficient.
1786 !> \param[in] N Index of the dipole operator to consider on this recursion level.
1787 !> \param[in] state Tree of parent intermediate states pointed to the last intermediate state.
1788 !> \param[in] ichanf Final channel after action of the dipole operator at this recursion level.
1789 !> \param[in] sb Kind (sign) of the outer-most Coulomb-Hankel function.
1790 !> \param[inout] k Linear momenta of the final and initial dipole transition channels for at previous recursion levels.
1791 !> \param[inout] l Angular momenta of the final and initial dipole transition channels for at previous recursion levels.
1792 !> \param[inout] m Angular projections of the final and initial dipole transition channels for at previous recursion levels.
1793 !> \param[in] cache Precomputed integrals.
1794 !>
1795 !> \return integ Evaluated multi-photon matrix element.
1796 !>
1797 recursive complex(wp) function multiint_chain (moldat, r0, Ei, esc, omega, ie, c, N, &
1798 state, ichanf, sb, k, l, m, cache) result (integ)
1799
1801 use multidip_io, only: moleculardata
1803
1804 type(moleculardata), intent(in) :: moldat
1805 type(intermediatestate), pointer, intent(in) :: state
1806 type(integral_cache_t), optional, intent(in) :: cache
1807 type(intermediatestate), pointer :: parent
1808
1809 complex(wp), intent(inout) :: k(:)
1810 real(wp), intent(inout) :: omega(:)
1811 integer, intent(inout) :: l(:), m(:)
1812 real(wp), intent(in) :: r0, ei, esc
1813 integer, intent(in) :: ie, sb, n, ichanf
1814
1815 complex(wp) :: ap, kr(n + 1)
1816 real(wp) :: z, c, a, ek, qion, qpws, ebase, etarg, echl
1817 integer :: mgvni, mgvnf, nchani, irri, irrf, ichani, ichl, irch, nphot, dcomp, lr(n + 1), mr(n), ntarg
1818
1819 integ = 0
1820
1821 ! no need to recurse further if the current state is the initial state
1822 if (.not. associated(state % parent)) return
1823
1824 parent => state % parent ! previous intermediate state
1825 nphot = parent % order ! number of photons absorbed by the previous intermediate state
1826 ntarg = size(moldat % etarg) ! number of residual ion states
1827 ebase = moldat % etarg(1) ! energy of the ground ion state
1828 a = moldat % rmatr ! R-matrix radius
1829 z = moldat % nz - moldat % nelc ! Residual ion charge
1830 dcomp = state % dcomp ! last polarisation component absorbed by the final state
1831 mgvnf = state % mgvn ! irreducible representation of the current state
1832 mgvni = parent % mgvn ! irreducible representation of the previos state
1833 irrf = findloc(moldat % mgvns, mgvnf, 1) ! index of the current spin-symmetry in molecular_data
1834 irri = findloc(moldat % mgvns, mgvni, 1) ! index of the previous spin-symmetry in molecular_data
1835 nchani = moldat % nchan(irri) ! number of channels in the previous state irreducible representation
1836
1837 ! loop over all channels of the intermediate state that are dipole-coupled to ichanf
1838 do ichani = 1, nchani
1839
1840 ! set up quantum numbers for this channel
1841 ichl = moldat % ichl(ichani, irri) ! index of ion state this pw is coupled to
1842 etarg = moldat % etarg(ichl) ! energy of that state
1843 echl = etarg - ebase ! excitation threshold of this ion state w.r.t. ground ion
1844 ek = esc - echl ! can become negative starting from some channel
1845 if (ek + closed_range <= 0) exit ! ignore strongly closed channels
1846 if (ek < 0 .and. .not. closed_interm) exit ! optionally ignore also weakly closed channels
1847 l(n + 1) = moldat % l2p(ichani, irri)
1848
1849 if (.not. cache_integrals) then
1850 k(n + 1) = sqrt(2*abs(ek))
1851 if (ek < 0) k(n + 1) = k(n + 1) * imu
1852 kr(1 : n + 1) = k(n + 1 : 1 : -1)
1853 lr(1 : n + 1) = l(n + 1 : 1 : -1)
1854 end if
1855
1856 ! dipole angular integrals
1857 qion = channel_coupling_ion(moldat, dcomp, irrf, irri, ichanf, ichani)
1858 qpws = channel_coupling_pws(moldat, dcomp, irrf, irri, ichanf, ichani)
1859
1860 if (qion == 0 .and. qpws == 0) cycle
1861
1862 ! outer region expansion coefficient for this partial wave and energy
1863 ap = cmplx(parent % ap(ichani, 1, ie), parent % ap(ichani, 2, ie), wp)
1864
1865 ! evaluate Coulomb-Green's integrals for dipole-coupled ions
1866 if (qion /= 0) then
1867 if (cache_integrals) then
1868 irch = 0
1869 if (allocated(cache % rchs_ion)) then
1870 irch = findloc(cache % rchs_ion, l(n + 1)*ntarg + ichl, 1)
1871 end if
1872 if (ap /= 0) then
1873 if (irch == 0) then
1874 print '(/,a,4(i0,a))', 'Error: Missing integral in cache for channel pair ', ichanf, ' (irr ', irrf, &
1875 ') - ', ichani, ' (irr ', irri, ')!'
1876 print '(7x,a,*(1x,i0))', 'Available reduced channels in cache:', cache % rchs_ion
1877 print '(7x,3(a,i0),a)', 'Needed reduced channel: ', l(n + 1)*ntarg + ichl, &
1878 ' (ichl = ', ichl, ', l = ', l(n + 1), ')'
1879 error stop
1880 end if
1881 integ = integ + qion * ap * cache % vals_ion((3 - sb)/2, irch)
1882 end if
1883 if (nphot > 0) then
1884 if (irch == 0) then
1885 print '(/,a,4(i0,a))', 'Error: Missing integral chain for channel pair ', ichanf, ' (irr ', irrf, &
1886 ') - ', ichani, ' (irr ', irri, ')!'
1887 print '(7x,a,*(1x,i0))', 'Available reduced channels in cache:', cache % rchs_ion
1888 print '(7x,3(a,i0),a)', 'Needed reduced channel: ', l(n + 1)*ntarg + ichl, &
1889 ' (ichl = ', ichl, ', l = ', l(n + 1), ')'
1890 error stop
1891 end if
1892 integ = integ + qion * multiint_chain(moldat, r0, ei, esc - omega(nphot), omega, &
1893 ie, c, n + 1, parent, ichani, sb, k, l, m, &
1894 cache % next_ion(irch))
1895 end if
1896 else
1897 m(n) = 0
1898 mr(1:n) = m(n:1:-1)
1899 if (ap /= 0) integ = integ + qion * ap * nested_cgreen_integ(z, a, r0, c, n, +1, sb, mr, lr, kr)
1900 if (nphot > 0) integ = integ + qion * multiint_chain(moldat, r0, ei, esc - omega(nphot), omega, &
1901 ie, c, n + 1, parent, ichani, sb, k, l, m)
1902 end if
1903 end if
1904
1905 ! evaluate Coulomb-Green's integrals for dipole-coupled partial waves
1906 if (qpws /= 0) then
1907 if (cache_integrals) then
1908 irch = 0
1909 if (allocated(cache % rchs_pws)) then
1910 irch = findloc(cache % rchs_pws, l(n + 1)*ntarg + ichl, 1)
1911 end if
1912 if (ap /= 0) then
1913 if (irch == 0) then
1914 print '(/,a,4(i0,a))', 'Error: Missing integral in cache for channel pair ', ichanf, ' (irr ', irrf, &
1915 ') - ', ichani, ' (irr ', irri, ')!'
1916 print '(7x,a,*(1x,i0))', 'Available reduced channels in cache:', cache % rchs_pws
1917 print '(7x,3(a,i0),a)', 'Needed reduced channel: ', l(n + 1)*ntarg + ichl, &
1918 ' (ichl = ', ichl, ', l = ', l(n + 1), ')'
1919 error stop
1920 end if
1921 integ = integ + qpws * ap * cache % vals_pws((3 - sb)/2, irch)
1922 end if
1923 if (nphot > 0) then
1924 if (irch == 0) then
1925 print '(/,a,4(i0,a))', 'Error: Missing integral chain for channel pair ', ichanf, ' (irr ', irrf, &
1926 ') - ', ichani, ' (irr ', irri, ')!'
1927 print '(7x,a,*(1x,i0))', 'Available reduced channels in cache:', cache % rchs_pws
1928 print '(7x,3(a,i0),a)', 'Needed reduced channel: ', l(n + 1)*ntarg + ichl, &
1929 ' (ichl = ', ichl, ', l = ', l(n + 1), ')'
1930 error stop
1931 end if
1932 integ = integ + qpws * multiint_chain(moldat, r0, ei, esc - omega(nphot), omega, &
1933 ie, c, n + 1, parent, ichani, sb, k, l, m, &
1934 cache % next_pws(irch))
1935 end if
1936 else
1937 m(n) = 1
1938 mr(1:n) = m(n:1:-1)
1939 if (ap /= 0) integ = integ + qpws * ap * nested_cgreen_integ(z, a, r0, c, n, +1, sb, mr, lr, kr)
1940 if (nphot > 0) integ = integ + qpws * multiint_chain(moldat, r0, ei, esc - omega(nphot), omega, &
1941 ie, c, n + 1, parent, ichani, sb, k, l, m)
1942 end if
1943 end if
1944
1945 end do
1946
1947 end function multiint_chain
1948
1949
1950 !> \brief Ion channel dipole coupling
1951 !> \author J Benda
1952 !> \date 2020 - 2024
1953 !>
1954 !> Returns the ion transition dipole element between the channels. This is diagonal in
1955 !> quantum numbers of the partial waves and simply equal to the corresponding N-electron
1956 !> propery integral.
1957 !>
1958 !> \param[in] moldat multidip_io::MolecularData object with data read from *molecular_data*.
1959 !> \param[in] dcomp Index of the Cartesian component of the dipole operator.
1960 !> \param[in] irrf Irreducible representation of the final state channel.
1961 !> \param[in] irri Irreducible representation of the initial state channel.
1962 !> \param[in] ichanf Index of the final state channel.
1963 !> \param[in] ichani Index of the initial state channel.
1964 !>
1965 real(wp) function channel_coupling_ion (moldat, dcomp, irrf, irri, ichanf, ichani) result (qion)
1966
1967 use multidip_io, only: moleculardata
1968 use multidip_params, only: carti
1969
1970 type(moleculardata), intent(in) :: moldat
1971 integer, intent(in) :: dcomp, irrf, irri, ichanf, ichani
1972
1973 integer :: itargi, itargf, li, lf, mi, mf
1974
1975 lf = moldat % l2p(ichanf, irrf); mf = moldat % m2p(ichanf, irrf); itargf = moldat % ichl(ichanf, irrf)
1976 li = moldat % l2p(ichani, irri); mi = moldat % m2p(ichani, irri); itargi = moldat % ichl(ichani, irri)
1977
1978 if (lf /= li .or. mf /= mi) then
1979 qion = 0
1980 else
1981 qion = moldat % crlv(itargf, itargi, carti(dcomp))
1982 end if
1983
1984 end function channel_coupling_ion
1985
1986
1987 !> \brief Partial wave channel dipole coupling
1988 !> \author J Benda
1989 !> \date 2020 - 2024
1990 !>
1991 !> Returns the partial wave transition dipole element between the channels. This is
1992 !> diagonal in the ion states and proportional to the Gaunt coefficient.
1993 !>
1994 !> \param[in] moldat multidip_io::MolecularData object with data read from *molecular_data*.
1995 !> \param[in] dcomp Index of the Cartesian component of the dipole operator.
1996 !> \param[in] irrf Irreducible representation of the final state channel.
1997 !> \param[in] irri Irreducible representation of the initial state channel.
1998 !> \param[in] ichanf Index of the final state channel.
1999 !> \param[in] ichani Index of the initial state channel.
2000 !>
2001 real(wp) function channel_coupling_pws (moldat, dcomp, irrf, irri, ichanf, ichani) result (qpws)
2002
2003 use multidip_io, only: moleculardata
2004 use multidip_params, only: cartm
2005
2006 type(moleculardata), intent(in) :: moldat
2007 integer, intent(in) :: dcomp, irrf, irri, ichanf, ichani
2008
2009 integer :: itargi, itargf, li, lf, mi, mf, ipwi, ipwf
2010
2011 itargf = moldat % ichl(ichanf, irrf)
2012 itargi = moldat % ichl(ichani, irri)
2013
2014 if (itargf /= itargi) then
2015 qpws = 0
2016 else
2017 lf = moldat % l2p(ichanf, irrf); mf = moldat % m2p(ichanf, irrf); ipwf = lf*(lf + 1) + mf + 1
2018 li = moldat % l2p(ichani, irri); mi = moldat % m2p(ichani, irri); ipwi = li*(li + 1) + mi + 1
2019 qpws = sqrt(4*pi/3) * moldat % gaunt(ipwf, ipwi, cartm(dcomp))
2020 end if
2021
2022 end function channel_coupling_pws
2023
2024
2025 !> \brief Calculate partial wave dipoles, oriented dipoles and cross sections
2026 !> \author J Benda
2027 !> \date 2020 - 2023
2028 !>
2029 !> Given the uncontracted partial wave dipoles
2030 !> \f[
2031 !> M_{i_f, l_f, m_f, j_1, \dots, j_n}^{(n)}
2032 !> \f]
2033 !> calculated in \ref extract_dipole_elements, where \f$ i_f \f$ is the index of the final ion state, \f$ l_f \f$ and
2034 !> \f$ m_f \f$ denote the emission partial wave and \f$ j_1, \dots, j_n \f$ are the indices of components of the polarisation
2035 !> vectors, evaluate the partial wave transition matrix elements
2036 !> \f[
2037 !> M_{i_f, l_f, m_f} = \mathrm{i}^{-l_f} \mathrm{e}^{\mathrm{i} \sigma_f} \sum_{j_1, \dots, j_n}
2038 !> \epsilon_{j_1} \dots \epsilon_{j_n} M_{i_f, l_f, mf_, j_1, \dots, j_n}^{(n)}
2039 !> \f]
2040 !> contracted with the polarisation vectors themselves, and the fixed-orientation generalized cross section
2041 !> \f[
2042 !> \sigma_f^{(n)} = 2\pi (2\pi\alpha\omega)^n \sum_{l_f m_f} \left| M_{i_f, l_f, m_f} \right|^2
2043 !> \f]
2044 !>
2045 !> \param[in] moldat multidip_io::MolecularData object with data read from the file *molecular_data*.
2046 !> \param[in] order Perturbation order matrix elements (= number of absorbed photons).
2047 !> \param[in] state Tree of intermediate and final states.
2048 !> \param[in] escat Scattering energies in a.u., as stored in the K-matrix files.
2049 !> \param[in] calc_Ei Initial state energy calculated by (MPI-)SCATCI or BOUND.
2050 !> \param[in] first_IP Ionization potential according to the input namelist.
2051 !> \param[in] Ephoton Fixed photon energies in a.u. or zeros for flexible photons.
2052 !> \param[in] polar Photon polarisations or zeros for polarisation averaging.
2053 !> \param[in] erange Range (subset) of energies in K-matrix files to use for calculations.
2054 !>
2055 subroutine calculate_pw_transition_elements (moldat, order, state, escat, calc_Ei, first_IP, Ephoton, polar, erange)
2056
2058 use multidip_params, only: alpha, maxtarg
2059 use multidip_special, only: cphase
2060
2061 type(moleculardata), intent(in) :: moldat
2062 type(intermediatestate), pointer, intent(in) :: state
2063 type(intermediatestate), pointer :: ptr, parent
2064
2065 integer, intent(in) :: order, erange(2)
2066 real(wp), intent(in) :: escat(:), calc_Ei, first_IP, Ephoton(:)
2067 complex(wp), intent(in) :: polar(:, :)
2068 integer :: nesc, ntarg, nchan, mxchan, lf, ichan, itarg, ie, irr, mgvn, mye, nirr, nene
2069 real(wp) :: Z, Ef, Ei, kf, sigma, Q
2070 complex(wp) :: d, ej
2071 complex(wp), allocatable :: M(:, :, :)
2072 real(wp), allocatable :: cs(:, :), omega(:)
2073
2074 z = moldat % nz - moldat % nelc ! residual charge
2075 nirr = size(moldat % mgvns) ! number of irreducible representations in molecular_data
2076 nesc = size(escat) ! total number of scattering energies
2077 mxchan = maxval(moldat % nchan) ! maximal number of channels per irreducible representation
2078 ntarg = size(moldat % etarg) ! number of targes (residual ions)
2079 nene = (nesc + nprocs - 1) / nprocs ! number of scattering energies managed by this image
2080
2081 ! if not all final targets are required, reduce some of the above limits
2082 if (maxtarg > 0) then
2083 mxchan = 0
2084 do irr = 1, nirr
2085 nchan = moldat % nchan(irr)
2086 mxchan = max(mxchan, count(moldat % ichl(1:nchan, irr) <= maxtarg))
2087 end do
2088 ntarg = min(ntarg, maxtarg)
2089 end if
2090
2091 ! set up final storage
2092 allocate (omega(order), m(mxchan, nene, 0:7), cs(2 + ntarg, nene))
2093 m = 0
2094 cs = 0
2095
2096 ! the dipole elements stored in molecular_data do not contain the charge, so for each transition we need a factor of -1
2097 q = (-1)**order
2098
2099 ! calculate fixed-in-space transition matrix elements
2100 ptr => state
2101 do while (associated(ptr))
2102 if (ptr % order == order) then
2103
2104 ! get polarisation factor for this absorption chain
2105 ej = polar(ptr % dcomp, ptr % order)
2106 parent => ptr % parent
2107 do while (associated(parent % parent))
2108 ej = ej * polar(parent % dcomp, parent % order)
2109 parent => parent % parent
2110 end do
2111
2112 ! add transition element contribution from this absorption chain
2113 mye = 0
2114 do ie = myproc, nesc, nprocs
2115 mye = mye + 1
2116 do ichan = 1, size(ptr % dip, 1)
2117 d = ej * cmplx(ptr % dip(ichan, 1, mye), ptr % dip(ichan, 2, mye), wp)
2118 m(ichan, mye, ptr % mgvn) = m(ichan, mye, ptr % mgvn) + q * d
2119 end do
2120 end do
2121
2122 end if
2123 ptr => ptr % next
2124 end do
2125
2126 ! write partial wave transition moments without Coulomb phase factor
2127 call write_partial_wave_moments(moldat, m, nesc, '')
2128
2129 ! process transition matrix elements
2130 mye = 0
2131 do ie = myproc, nesc, nprocs
2132
2133 mye = mye + 1
2134 ei = calc_ei
2135 call calculate_photon_energies(first_ip, escat(ie), moldat % etarg(1), ei, ephoton, omega)
2136
2137 ! add phase factors to transition matrix elements
2138 do irr = 1, nirr
2139 mgvn = moldat % mgvns(irr)
2140 nchan = min(int(moldat % nchan(irr)), mxchan)
2141 do ichan = 1, nchan
2142 lf = moldat % l2p(ichan, irr)
2143 ef = escat(ie) - moldat % etarg(moldat % ichl(ichan, irr)) + moldat % etarg(1)
2144 if (ef > 0) then
2145 kf = sqrt(2*ef)
2146 sigma = cphase(z, lf, kf)
2147 m(ichan, mye, mgvn) = m(ichan, mye, mgvn) * imu**(-lf) * (cos(sigma) + imu*sin(sigma))
2148 end if
2149 end do
2150 end do
2151
2152 ! calculate oriented cross section
2153 cs(1, mye) = omega(1) * to_ev
2154 do itarg = 1, ntarg
2155 cs(2 + itarg, mye) = 0
2156 do irr = 1, nirr
2157 mgvn = moldat % mgvns(irr)
2158 nchan = min(int(moldat % nchan(irr)), mxchan)
2159 do ichan = 1, nchan
2160 if (itarg == moldat % ichl(ichan, irr)) then
2161 d = m(ichan, mye, mgvn)
2162 cs(2 + itarg, mye) = cs(2 + itarg, mye) + real(d * conjg(d))
2163 end if
2164 end do
2165 end do
2166 end do
2167 cs(2, mye) = sum(cs(3:, mye))
2168 cs(2:, mye) = cs(2:, mye) * 2*pi * (2*pi*alpha)**order * product(omega)
2169
2170 end do
2171
2172 ! write partial wave transition moments with Coulomb phase factor
2173 call write_partial_wave_moments(moldat, m, nesc, '+cphase')
2174
2175 ! write fixed-in-space cross section
2176 call write_cross_section(cs, nesc, erange, 'gen_photo_xsec.txt')
2177
2179
2180
2181 !> \brief Calculate cross sections and asymmetry parameters
2182 !> \author J Benda
2183 !> \date 2020 - 2023
2184 !>
2185 !> Evaluate coefficients \f$ \beta_L \f$ in the expansion of differential cross section averaged over
2186 !> molecular orientation:
2187 !> \f[
2188 !> \frac{\mathrm{d}\sigma}{\mathrm{d}\Omega} = \frac{\beta_0}{4\pi} \left(1+\sum_{L=1}^{2n} \beta_L P_L(\cos\theta) \right).
2189 !> \f]
2190 !> The quantity \f$ \beta_0 \f$ is equal to the integral cross section.
2191 !>
2192 !> \param[in] moldat multidip_io::MolecularData object with data read from the file *molecular_data*.
2193 !> \param[in] order Perturbation order matrix elements (= number of absorbed photons).
2194 !> \param[in] state Tree of intermediate and final states.
2195 !> \param[in] escat Scattering energies in a.u., as stored in the K-matrix files.
2196 !> \param[in] calc_Ei Initial state energy calculated by (MPI-)SCATCI or BOUND.
2197 !> \param[in] first_IP Ionization potential according to the input namelist.
2198 !> \param[in] Ephoton Fixed photon energies in a.u. or zeros for flexible photons.
2199 !> \param[in] raw Write raw transition dipoles (in spherical or cartesian basis).
2200 !> \param[in] erange Range (subset) of energies in K-matrix files to use for calculations.
2201 !> \param[in] p Laboratory-frame polarisation (i.e. one of: -1,0,1) for each photon absorption.
2202 !> \param[in] lu_pw_dipoles Base number for the logical unit for saving the pw dipoles in RSOLVE format (typically 410).
2203 !>
2204 subroutine calculate_asymmetry_parameters (moldat, order, state, escat, calc_Ei, first_IP, Ephoton, raw, erange, p, &
2205 lu_pw_dipoles)
2206
2208 use multidip_params, only: alpha, maxtarg
2209 use multidip_special, only: cphase
2210
2211 character(len=*), intent(in) :: raw
2212 type(moleculardata), intent(in) :: moldat
2213 type(intermediatestate), pointer, intent(in) :: state
2214 type(intermediatestate), pointer :: ptr, parent
2215
2216 integer, intent(in) :: order, erange(2), p(:), lu_pw_dipoles
2217 real(wp), intent(in) :: escat(:), calc_Ei, first_IP, Ephoton(:)
2218
2219 integer :: nesc, ii, li, mi, nchain, ichain
2220 integer :: ntarg, ichan, ie, mye, irr, L, nene, maxl, pw
2221 integer, allocatable :: chains(:, :)
2222 real(wp), allocatable :: omega(:), cs(:, :)
2223 complex(wp), allocatable :: M_xyz(:, :, :, :), M_xyz_no_phase(:, :, :, :), M_sph(:, :, :, :), beta(:, :)
2224 complex(wp) :: d
2225 real(wp) :: Z, Ek, k, sigma, Ei, Q
2226 character(len=50) :: filename
2227
2228 z = moldat % nz - moldat % nelc
2229 nesc = size(escat) ! total number of scattering energies
2230 nene = (nesc + nprocs - 1) / nprocs ! number of scattering energies managed by this image
2231 maxl = maxval(moldat % l2p) ! highest partial wave angular momentum
2232 ntarg = size(moldat % etarg) ! number of targes (residual ions)
2233
2234 ! if not all final targets are required, reduce some of the above limits
2235 if (maxtarg > 0) then
2236 ntarg = min(ntarg, maxtarg)
2237 maxl = maxval(moldat % l2p, moldat % ichl <= maxtarg)
2238 end if
2239
2240 ! find out how many absorption chains we have
2241 nchain = 0
2242 ptr => state
2243 do while (associated(ptr))
2244 if (ptr % order == order) then
2245 nchain = nchain + 1
2246 end if
2247 ptr => ptr % next
2248 end do
2249
2250 allocate (omega(order), chains(order, nchain), cs(ntarg, nene), beta(2 + ntarg, nesc), &
2251 m_xyz(nene, (maxl + 1)**2, nchain, ntarg), &
2252 m_sph(nene, (maxl + 1)**2, nchain, ntarg))
2253
2254
2255 m_xyz = 0 ! multiphoton transition matrix elements in Cartesian basis
2256 m_sph = 0 ! multiphoton transition matrix elements in spherical basis
2257
2258 if (order == 1) then
2259 allocate(m_xyz_no_phase(nene, (maxl + 1)**2, nchain, ntarg))
2260 m_xyz_no_phase = 0 ! multiphoton transition matrix elements in Cartesian basis without the additional phase factors
2261 endif
2262
2263 ! the dipole elements stored in molecular_data do not contain the charge, so for each transition we need a factor of -1
2264 q = (-1)**order
2265
2266 ! assemble multi-photon molecular transition elements in Cartesian basis
2267 ichain = 0
2268 ptr => state
2269 do while (associated(ptr))
2270 if (ptr % order == order) then
2271
2272 ! find this irreducible representation in molecular_data
2273 irr = findloc(moldat % mgvns, ptr % mgvn, 1)
2274
2275 ! assemble polarisation components for this absorption chain
2276 ichain = ichain + 1
2277 parent => ptr
2278 do while (associated(parent % parent))
2279 chains(parent % order, ichain) = mod(1 + parent % dcomp, 3) - 1 ! y ~ -1, z ~ 0, x ~ +1
2280 parent => parent % parent
2281 end do
2282
2283 ! copy all Cartesian matrix elements for this irreducible representation and absorption history to M_xyz
2284 do ichan = 1, size(ptr % dip, 1)
2285 ii = moldat % ichl(ichan, irr)
2286 li = moldat % l2p(ichan, irr)
2287 mi = moldat % m2p(ichan, irr)
2288 pw = li*li + li + mi + 1
2289 mye = 0
2290 do ie = myproc, nesc, nprocs
2291 mye = mye + 1
2292 ek = escat(ie) - moldat % etarg(ii) + moldat % etarg(1)
2293 if (ek > 0) then
2294 k = sqrt(2*ek)
2295 sigma = cphase(z, li, k)
2296 d = cmplx(ptr % dip(ichan, 1, mye), ptr % dip(ichan, 2, mye), wp)
2297 if (.not. d == d) d = 0 ! clear NaNs
2298 m_xyz(mye, pw, ichain, ii) = imu**(-li) * (cos(sigma) + imu*sin(sigma)) * q * d
2299 if (order == 1) m_xyz_no_phase(mye, pw, ichain, ii) = q * d
2300 end if
2301 end do
2302 end do
2303
2304 end if
2305 ptr => ptr % next
2306 end do
2307
2308 ! for 1-photon case write the dipoles always in the RSOLVE format
2309 if (order == 1) then
2310 call write_rsolve_dipoles(moldat, m_xyz_no_phase, chains, escat, lu_pw_dipoles)
2311 endif
2312
2313 if (raw == 'xyz' .or. raw == 'both') then
2314 call write_raw_dipoles(m_xyz, chains, nesc, 'xyz')
2315 end if
2316
2317 ! transform the multiphoton transition matrix elements from Cartesian basis (M_xyz) to spherical basis (M_sph)
2318 call convert_xyz_to_sph (m_xyz, m_sph, maxl, chains)
2319
2320 if (raw == 'sph' .or. raw == 'both') then
2321 call write_raw_dipoles(m_sph, chains, nesc, 'sph')
2322 end if
2323
2324 ! evaluate and write the asymmetry parameters for all possible orders
2325 do l = 0, 2*order
2326
2327 print '(/,A,I0,A,*(1x,I0))', 'Evaluating asymmetry parameter for L = ', l, ', p =', p(1:order)
2328
2329 ! calculate the absolute asymmetry parameter
2330 call calculate_quadratic_dipole_sph(beta, l, maxl, chains, chains, ntarg, nesc, m_sph, m_sph, p)
2331
2332 ! sum partial cross section, add prefactors
2333 mye = 0
2334 do ie = myproc, nesc, nprocs
2335 mye = mye + 1
2336 ei = calc_ei
2337 call calculate_photon_energies(first_ip, escat(ie), moldat % etarg(1), ei, ephoton, omega)
2338 beta(1, mye) = omega(1) * to_ev
2339 beta(2:, mye) = beta(2:, mye) * 2*pi * (2*pi*alpha)**order * product(abs(omega))
2340 if (l == 0) then
2341 cs(:, mye) = real(beta(3:, mye)) ! partial cross sections per target
2342 beta(2, mye) = sum(cs(:, mye)) ! total cross section
2343 else
2344 where (cs(:, mye) > 0)
2345 beta(3:, mye) = beta(3:, mye) / cs(:, mye) ! scale partial asymmetry parameter by the partial cross section
2346 end where
2347 beta(2, :) = 0 ! there is no "total beta" for L > 0
2348 end if
2349 end do
2350
2351 ! write the asymmetry parameter
2352 write (filename, '(A,I0,A,I0,A)') 'gen_', order, 'photo_beta_', l, '.txt'
2353 call write_cross_section(real(beta, wp), nesc, erange, filename)
2354
2355 end do
2356
2357 end subroutine calculate_asymmetry_parameters
2358
2359
2360 !> \brief Change coordiantes
2361 !> \author J Benda
2362 !> \date 2021
2363 !>
2364 !> Transform the multi-photon matrix elements from the Cartesian basis to the spherical basis.
2365 !> It is expected that both `M_xyz` and `M_sph` arrays are allocated and have the same size.
2366 !>
2367 subroutine convert_xyz_to_sph (M_xyz, M_sph, maxl, chains)
2368
2369 use dipelm_special_functions, only: sph_basis_transform_elm
2370
2371 complex(wp), allocatable, intent(in) :: M_xyz(:, :, :, :)
2372 complex(wp), allocatable, intent(inout) :: M_sph(:, :, :, :)
2373 integer, intent(in) :: maxl, chains(:, :)
2374
2375 integer :: order, ntarg, nchain, i, ii, l, mi, mj, pwi, pwj, ichain, jchain
2376 complex(wp) :: cpl
2377
2378 order = size(chains, 1)
2379 nchain = size(chains, 2)
2380 ntarg = size(m_xyz, 4)
2381
2382 m_sph = 0
2383
2384 do ii = 1, ntarg
2385 do ichain = 1, nchain
2386 do jchain = 1, nchain
2387 do l = 0, maxl
2388 do mi = -l, l
2389 do mj = -l, l
2390 cpl = conjg(sph_basis_transform_elm(l, mj, mi, 'Slm'))
2391 do i = 1, order
2392 cpl = cpl * sph_basis_transform_elm(1, chains(i, jchain), chains(i, ichain), 'Slm')
2393 end do
2394 pwi = l*l + l + mi + 1
2395 pwj = l*l + l + mj + 1
2396 m_sph(:, pwj, jchain, ii) = m_sph(:, pwj, jchain, ii) + cpl * m_xyz(:, pwi, ichain, ii)
2397 end do
2398 end do
2399 end do
2400 end do
2401 end do
2402 end do
2403
2404 end subroutine convert_xyz_to_sph
2405
2406
2407 !> \brief Evaluate asymmetry parameter for given total L in the spherical basis
2408 !> \author J Benda
2409 !> \date 2021 - 2024
2410 !>
2411 !> Evaluate quadratic form in partial wave dipoles (e.g. asymmetry parameter beta_L) for all final states.
2412 !> For cross sections, the (spherical) matrix elements M1 and M2 correspond to the same energies. However, for RABITT, these
2413 !> are evaluated at different energies.
2414 !>
2415 subroutine calculate_quadratic_dipole_sph (beta, L, maxl, chains1, chains2, ntarg, nesc, M1, M2, p)
2416
2417 use mpi_gbl, only: mpi_reduceall_inplace_sum_wp
2418 use multidip_io, only: myproc, nprocs
2420
2421 complex(wp), allocatable, intent(inout) :: beta(:, :)
2422 complex(wp), allocatable, intent(in) :: M1(:, :, :, :), M2(:, :, :, :)
2423 integer, intent(in) :: L, maxl, chains1(:, :), chains2(:, :), ntarg, nesc, p(:)
2424
2425 complex(wp) :: MM
2426 real(wp), allocatable :: T(:, :, :, :), buffer(:)
2427 integer :: ie, mye, itarg, idx, li, mi, lj, mj, pwi, pwj, ichain, jchain, nchain1, nchain2, order1, order2
2428 integer :: qi(size(chains1, 1)), qj(size(chains2, 1))
2429
2430 if (any(abs(p) > 1)) then
2431 print '(/,A,I0)','calculate_quadratic_dipole_sph: lab-frame polarisation p out of range', p
2432 stop 1
2433 endif
2434
2435 beta = 0
2436 order1 = size(chains1, 1); nchain1 = size(chains1, 2)
2437 order2 = size(chains2, 1); nchain2 = size(chains2, 2)
2438
2439 if (order1 /= order2) then
2440 print '(A)', 'WARNING: calculate_quadratic_dipole_sph is implemented only equal absorption orders'
2441 return
2442 end if
2443
2444 allocate (t((maxl + 1)**2, (maxl + 1)**2, nchain1, nchain2))
2445
2446 ! erase the angular integrals storage
2447 !$omp parallel do default(none) private(pwi, pwj, ichain, jchain) shared(maxl, nchain1, nchain2, T) collapse(4)
2448 do jchain = 1, nchain2
2449 do ichain = 1, nchain1
2450 do pwj = 1, (maxl + 1)**2
2451 do pwi = 1, (maxl + 1)**2
2452 t(pwi, pwj, ichain, jchain) = 0
2453 end do
2454 end do
2455 end do
2456 end do
2457
2458 ! calculate angular integrals (distribute over images and threads)
2459 !$omp parallel do default(none) schedule(dynamic) private(idx, pwi, pwj, li, lj, mi, mj, ichain, jchain, qi, qj) &
2460 !$omp& shared(nchain1, nchain2, maxl, chains1, chains2, nprocs, myproc, L, p, T, order1)
2461 do idx = myproc, (maxl + 1)**4 * nchain1 * nchain2, nprocs
2462
2463 ! unpack idx = ((pwi*(maxl + 1)**2 + pwj)*nchain1 + ichain - 1)*nchain2 + jchain
2464 pwi = 1 + (idx - 1) / (nchain2 * nchain1 * (maxl + 1)**2) ! = 1, ..., (maxl + 1)^2
2465 pwj = 1 + mod((idx - 1) / (nchain2 * nchain1), (maxl + 1)**2) ! = 1, ..., (maxl + 1)^2
2466 ichain = 1 + mod((idx - 1) / nchain2, nchain1) ! = 1, ..., nchain1
2467 jchain = 1 + mod( idx - 1, nchain2) ! = 1, ..., nchain2
2468
2469 ! unpack pwi = li*li + li + mi + 1
2470 li = floor(sqrt(pwi - 1._wp))
2471 mi = pwi - li*li - li - 1
2472 qi = chains1(:, ichain)
2473
2474 ! unpack pwj = lj*lj + lj + mj + 1
2475 lj = floor(sqrt(pwj - 1._wp))
2476 mj = pwj - lj*lj - lj - 1
2477 qj = chains2(:, jchain)
2478
2479 t(pwi, pwj, ichain, jchain) = beta_contraction_tensor(l, order1, p, li, mi, qi, lj, mj, qj)
2480
2481 end do
2482
2483 ! allreduce the contraction tensor
2484 buffer = reshape(t, [size(t)])
2485 call mpi_reduceall_inplace_sum_wp(buffer, size(buffer))
2486 t = reshape(buffer, shape(t))
2487
2488 ! calculate the asymmetry parameter for this image's energies (distribute over threads)
2489 !$omp parallel do default(none) private(idx, itarg, pwi, pwj, ichain, jchain, li, mi, lj, mj, mye, MM) reduction(+:beta) &
2490 !$omp& shared(ntarg, maxl, nchain1, nchain2, myproc, nesc, nprocs, M1, M2, T)
2491 do idx = 1, ntarg * (maxl + 1)**4 * nchain1 * nchain2
2492
2493 ! unpack idx = (((itarg - 1)*(maxl + 1)**2 + pwi)*(maxl + 1)**2 + pwj)*nchain1 + ichain - 1)*nchain2 + jchain
2494 itarg = 1 + (idx - 1) / (nchain2 * nchain1 * (maxl + 1)**4) ! = 1, ..., ntarg
2495 pwi = 1 + mod((idx - 1) / (nchain2 * nchain1 * (maxl + 1)**2), (maxl + 1)**2) ! = 1, ..., (maxl + 1)^2
2496 pwj = 1 + mod((idx - 1) / (nchain2 * nchain1), (maxl + 1)**2) ! = 1, ..., (maxl + 1)^2
2497 ichain = 1 + mod((idx - 1) / nchain2, nchain1) ! = 1, ..., nchain1
2498 jchain = 1 + mod( idx - 1, nchain2) ! = 1, ..., nchain2
2499
2500 ! unpack pwi = li*li + li + mi
2501 li = floor(sqrt(pwi - 1._wp))
2502 mi = pwi - li*li - li - 1
2503
2504 ! unpack pwj = lj*lj + lj + mj
2505 lj = floor(sqrt(pwj - 1._wp))
2506 mj = pwj - lj*lj - lj - 1
2507
2508 mye = 0
2509 do ie = myproc, nesc, nprocs
2510 mye = mye + 1
2511 mm = conjg(m1(mye, pwi, ichain, itarg)) * m2(mye, pwj, jchain, itarg)
2512 beta(2 + itarg, mye) = beta(2 + itarg, mye) + t(pwi, pwj, ichain, jchain) * mm
2513 end do
2514
2515 end do
2516
2517 end subroutine calculate_quadratic_dipole_sph
2518
2519
2520 !> \brief Evaluate asymmetry parameter for given total L in the Cartesian basis
2521 !> \author J Benda
2522 !> \date 2021 - 2023
2523 !>
2524 !> Evaluate quadratic form in partial wave dipoles (e.g. asymmetry parameter beta_L) for all final states.
2525 !> For cross sections, the (Cartesian) matrix elements M1 and M2 correspond to the same energies. However, for RABITT, these
2526 !> are evaluated at different energies.
2527 !>
2528 !> \note This subroutine is implemented only for linear polarisation and only for L = 0.
2529 !>
2530 subroutine calculate_quadratic_dipole_xyz (beta, L, maxl, chains1, chains2, ntarg, nesc, M1, M2)
2531
2532 use multidip_io, only: myproc, nprocs
2534
2535 complex(wp), allocatable, intent(inout) :: beta(:, :)
2536 complex(wp), allocatable, intent(in) :: M1(:, :, :, :), M2(:, :, :, :)
2537 integer, intent(in) :: L, maxl, chains1(:, :), chains2(:, :), ntarg, nesc
2538
2539 complex(wp) :: MM
2540 real(wp) :: T
2541 integer :: ie, mye, itarg, pwi, qi(size(chains1, 1)), qj(size(chains2, 1)), ichain, jchain
2542
2543 beta = 0
2544
2545 if (l /= 0) then
2546 print '(A)', 'WARNING: calculate_quadratic_dipole_xyz is implemented only for L = 0'
2547 return
2548 end if
2549
2550 ! calculate the asymmetry parameter
2551 !$omp parallel do collapse(2) default(none) private(ichain, jchain, qi, qj, T, pwi, mye, ie, MM) reduction(+:beta) &
2552 !$omp& shared(chains1, chains2, maxl, myproc, nesc, nprocs, ntarg, M1, M2)
2553 do ichain = 1, size(chains1, 2)
2554 do jchain = 1, size(chains2, 2)
2555 qi = chains1(:, ichain)
2556 qj = chains2(:, jchain)
2558 do pwi = 1, (maxl + 1)**2
2559 mye = 0
2560 do ie = myproc, nesc, nprocs
2561 mye = mye + 1
2562 do itarg = 1, ntarg
2563 mm = conjg(m1(mye, pwi, ichain, itarg)) * m2(mye, pwi, jchain, itarg)
2564 beta(2 + itarg, mye) = beta(2 + itarg, mye) + t * mm
2565 end do
2566 end do
2567 end do
2568 end do
2569 end do
2570
2571 end subroutine calculate_quadratic_dipole_xyz
2572
2573end module multidip_routines
Special integrals needed by MULTIDIP.
complex(wp) function nested_cgreen_integ(z, a, r0, c, n, sa, sb, m, l, k)
Multi-dimensional integral of Coulomb-Hankel and Coulomb-Green functions and powers.
I/O routines used by MULTIDIP.
subroutine read_bndcoeffs(bnd, ei, lubnd, mgvn0, stot0)
Read inner bound wave function coefficients.
subroutine read_wfncoeffs(ak, lusct)
Read wave function coefficients from files.
subroutine apply_boundary_amplitudes(moldat, irr, transp, x, y)
Multiply by boundary amplitudes.
subroutine scale_boundary_amplitudes(moldat, irr, v, vw)
Scale boundary amplitudes matrix by a diagonal matrix.
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.
subroutine write_raw_dipoles(m, chains, nesc, stem)
Write raw transition dipole moments.
subroutine apply_dipole_matrix(moldat, component, irrpair, transp, nf, nn, x, y)
Multiply by dipole matrix.
integer nprocs
integer myproc
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.
Routines related to outer region asymptotic channels.
subroutine test_outer_expansion(filename, moldat, irr, ck, ap, etot)
Evaluate wfn in channels for inside and outside of the R-matrix sphere.
subroutine evaluate_fundamental_solutions(moldat, r, irr, nopen, ek, s, c, sp, cp, sqrtknorm)
Evaluate asymptotic solutions at boundary.
Hard-coded parameters of MULTIDIP.
logical extend_istate
Continue initial state from the known inner region expansion outwards.
real(wp), parameter pi
subroutine read_input_namelist(input, order, lusct, lukmt, lubnd, nkset, polar, omega, verbose, rmt_data, first_ip, rasym, raw, erange, mpiio, gpu, lab_polar, lu_pw_dipoles)
logical closed_interm
Consider weakly closed channel in intermediate states (unfinished!)
logical custom_kmatrices
Ignore RSOLVE K-matrices and calculate them from scratch.
integer num_integ_algo
Numerical integration mode (1 = Romberg, 2 = Levin)
integer, dimension(3), parameter carti
Position of a Cartesian coordinate in the real spherical basis (y, z, x).
character(len=1), dimension(3), parameter compt
logical cache_integrals
Cache Coulomb-Green integrals in memory (but disables some threading)
complex(wp), parameter imu
real(wp), parameter rone
complex(wp), parameter cone
real(wp) closed_range
Energy interval (a.u.) before threshold for inclusion of closed channels.
real(wp), parameter rzero
integer maxtarg
Maximal number of target states to calculate dipoles for (0 = all)
real(wp), parameter alpha
Fine structure constant.
integer, dimension(3), parameter cartm
Real spherical harmonic m-value corresponding to given a Cartesian coord.
complex(wp), parameter czero
integer, parameter nmaxphotons
The limit on number of photons is nested_cgreen_integ
Main MULTIDIP routines.
real(wp) function channel_coupling_ion(moldat, dcomp, irrf, irri, ichanf, ichani)
Ion channel dipole coupling.
subroutine solve_intermediate_state(moldat, order, ephoton, icomp, s, cache, mgvnn, mgvn1, mgvn2, km, state, verbose, calc_ei, first_ip, r0, erange)
Calculate intermediate photoionisation state.
subroutine test_final_expansion(filename, moldat, irr, nopen, etot, ek, sp, cp, kmat, tmat)
Write radially sampled final wave-function to file.
subroutine calculate_photon_energies(first_ip, escat, etarg, ei, ephoton, omega)
Adjust ionization potential and calculate energy of each photon.
subroutine calculate_quadratic_dipole_sph(beta, l, maxl, chains1, chains2, ntarg, nesc, m1, m2, p)
Evaluate asymmetry parameter for given total L in the spherical basis.
recursive subroutine precompute_integral_cache_block(integral_cache, moldat, pws_coupled, ion_coupled, order, omega, escat, r0, rchs, ms, ks, ls)
Precompute outer radial integrals (implementation)
recursive subroutine print_integral_cache_block(cache, ntarg, level, chain)
Print precomputed integrals.
subroutine reset_timer(t, dt)
Get current time stamp.
logical, parameter test_expansion
subroutine calculate_quadratic_dipole_xyz(beta, l, maxl, chains1, chains2, ntarg, nesc, m1, m2)
Evaluate asymmetry parameter for given total L in the Cartesian basis.
subroutine extract_dipole_elements(moldat, order, ephoton, icomp, s, cache, mgvnn, mgvn1, mgvn2, km, ak, state, verbose, calc_ei, first_ip, r0, erange)
Calculate dipole elements from intermediate and final states.
subroutine multiint(moldat, r0, ei, esc, omega, ie, state, sb, dip, cache)
Evaluate the correction dipole integral for all orders.
subroutine print_integral_cache(cache, erange, ntarg)
Print precomputed integrals.
recursive complex(wp) function multiint_chain(moldat, r0, ei, esc, omega, ie, c, n, state, ichanf, sb, k, l, m, cache)
Calculate dipole correction integrals at given absorption depth.
real(wp) function channel_coupling_pws(moldat, dcomp, irrf, irri, ichanf, ichani)
Partial wave channel dipole coupling.
subroutine print_transition_header(state)
Prints a one-line summary of the transition.
subroutine setup_initial_state(states, moldat, irr, lubnd, ei)
Construct initial state.
subroutine multidip_driver(order, moldat, km, ak, lubnd, omega, polar, verbose, first_ip, r0, raw, erange, p, lu_pw_dipoles)
Central computation routine.
subroutine convert_xyz_to_sph(m_xyz, m_sph, maxl, chains)
Change coordiantes.
subroutine precompute_integral_cache(integral_cache, moldat, esc, nphot, r0, erange, calc_ei, first_ip, ephoton, verbose)
Precompute outer radial integrals (driver)
subroutine calculate_asymmetry_parameters(moldat, order, state, escat, calc_ei, first_ip, ephoton, raw, erange, p, lu_pw_dipoles)
Calculate cross sections and asymmetry parameters.
subroutine calculate_pw_transition_elements(moldat, order, state, escat, calc_ei, first_ip, ephoton, polar, erange)
Calculate partial wave dipoles, oriented dipoles and cross sections.
subroutine multidip_main
MULTIDIP main subroutine.
Special functions and objects used by MULTIDIP.
real(wp) function beta_contraction_tensor(j, n, p, li, mi, qi, lj, mj, qj)
Angular part of the beta parameters.
subroutine solve_complex_system(n, a, x, y)
Solve system of equations with complex matrix.
real(wp) recursive function cartesian_vector_component_product_average(q)
Return Cartesian invariant.
real(wp) function cphase(z, l, k)
Coulomb phase.
subroutine coul(z, l, ek, r, f, fp, g, gp)
Coulomb functions.
MULTIDIP unit tests.
subroutine run_tests
Numerical unit tests.