Multidip  1.0
Multi-photon matrix elements
multidip_integ.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 !
30 
31  use iso_fortran_env, only: error_unit
33  use phys_const_gbl, only: pi, imu
34  use precisn_gbl, only: wp
35 
36  implicit none
37 
45 
46  integer, allocatable :: l(:, :) ! known sets of angular momentum
47  integer, allocatable :: m(:, :) ! known sets of angular momentum projection
48  complex(wp), allocatable :: k(:, :) ! per stored integral: sequence of momenta
49  integer, allocatable :: idx(:, :) ! per stored integral: 1. 'sa', 2. 'sb', 3. 'm' idx, 4. 'l' idx, 5. 'k' idx
50  complex(wp), allocatable :: values(:, :) ! per stored integral: its value (first dimension is always 1)
51 
52  end type
53 
61 
62  real(wp) :: a ! R-matrix radius
63  real(wp) :: r0 ! asymptotic radius
64  real(wp) :: c ! dipole damping coefficient
65 
66  integer :: ie ! which energy to use
67 
68  type(nested_cgreen_integ_cache_internal_t), allocatable :: data(:, :) ! one data structure per photon order and energy
69 
70  contains
71 
72  procedure :: init => init_nested_cgreen_integ_cache_t
73  procedure :: get => get_nested_cgreen_integ_cache_t
74  procedure :: set => set_nested_cgreen_integ_cache_t
75 
76  end type
77 
79 
80 contains
81 
88  subroutine init_nested_cgreen_integ_cache_t (this, N, ne, a, r0, c)
89 
90  class(nested_cgreen_integ_cache_t), intent(inout) :: this
91  integer, intent(in) :: N, ne
92  real(wp), intent(in) :: a, r0, c
93 
94  if (allocated(this % data)) deallocate(this % data)
95 
96  allocate (this % data(n, ne))
97 
98  this % a = a
99  this % r0 = r0
100  this % c = c
101 
102  end subroutine init_nested_cgreen_integ_cache_t
103 
104 
112  logical function get_nested_cgreen_integ_cache_t (this, a, r0, c, N, sa, sb, m, l, k, val) result (status)
113 
114  class(nested_cgreen_integ_cache_t), intent(in) :: this
115 
116  real(wp), intent(in) :: a, r0, c
117  integer, intent(in) :: n, sa, sb, m(:), l(:)
118  complex(wp), intent(in) :: k(:)
119  complex(wp), intent(inout) :: val
120 
121  integer :: i, im, il, ik
122 
123  status = .false.
124  if (allocated(this % data)) then
125  if (this % a == a .and. this % r0 == r0 .and. this % c == c .and. size(this % data) >= n) then
126  if (allocated(this % data(n, this % ie) % values)) then
127  im = find_column(this % data(n, this % ie) % m, m); if (im == 0) return
128  il = find_column(this % data(n, this % ie) % l, l); if (il == 0) return
129  ik = find_column(this % data(n, this % ie) % k, k); if (ik == 0) return
130  do i = 1, size(this % data(n, this % ie) % idx, 2)
131  if (all(this % data(n, this % ie) % idx(:, i) == [sa, sb, im, il, ik])) then
132  val = this % data(n, this % ie) % values(1, i)
133  status = .true.
134  return
135  end if
136  end do
137  end if
138  end if
139  end if
140 
142 
143 
150  subroutine set_nested_cgreen_integ_cache_t (this, a, r0, c, N, sa, sb, m, l, k, val)
151 
152  class(nested_cgreen_integ_cache_t), intent(inout) :: this
153 
154  real(wp), intent(in) :: a, r0, c
155  integer, intent(in) :: N, sa, sb, m(:), l(:)
156  complex(wp), intent(in) :: k(:)
157  complex(wp), intent(in) :: val
158 
159  integer :: im, il, ik, ii
160  complex(wp) :: orig_val
161 
162  if (this % get(a, r0, c, n, sa, sb, m, l, k, orig_val)) then
163  write (error_unit, '(a)') 'Error: Overwriting existing integral cache entry'
164  stop 1
165  end if
166 
167  if (this % a /= a .or. this % r0 /= r0 .or. this % c /= c) then
168  write(error_unit, '(a)') 'Error: Inserting value into cache for incompatible setup'
169  stop 1
170  end if
171 
172  im = find_column(this % data(n, this % ie) % m, m); if (im == 0) im = append_column(this % data(n, this % ie) % m, m)
173  il = find_column(this % data(n, this % ie) % l, l); if (il == 0) il = append_column(this % data(n, this % ie) % l, l)
174  ik = find_column(this % data(n, this % ie) % k, k); if (ik == 0) ik = append_column(this % data(n, this % ie) % k, k)
175 
176  ii = append_column(this % data(n, this % ie) % idx, [sa, sb, im, il, ik])
177  ii = append_column(this % data(n, this % ie) % values, [val])
178 
179  end subroutine set_nested_cgreen_integ_cache_t
180 
181 
210  complex(wp) function nested_exp_integ (Z, a, c, N, m, s, k) result (val)
211 
212  use multidip_params, only: ntermsppt
213 
214  integer, intent(in) :: n, m(0:n-1), s(0:2*n-1)
215  real(wp), intent(in) :: z, a, c
216  complex(wp), intent(in) :: k(0:2*n-1)
217 
218  integer, parameter :: npp = ntermsppt
219 
220  complex(wp) :: i(0:n*(npp+1)), f(0:n*(npp+1)-1), fg(0:n*(npp+1)-1), g(0:(n+1)*(npp+1)-1), j(0:n*(npp+1)-1)
221  complex(wp) :: t(0:npp, 0:n*(npp + 1)-1)
222 
223  real(wp) :: b(0:n*(npp+1)-1, 0:n*(npp+1))
224 
225  complex(wp) :: u, v
226  integer :: o, p, q, lvl
227 
228  if (n < 1) then
229  val = 0
230  return
231  end if
232 
233  ! For a given expansion depth of the integral (Npp) we need that many
234  ! derivatives of the base function (here of the coordinate power r^m).
235  ! With N levels of nesting, this combines to N * Npp derivatives in total
236  ! for the first base function. Further base functions are not needed
237  ! to such high orders.
238 
239  i = 0
240  f = 0
241  fg = 0
242  g = 0
243  j = 0
244  t = 0
245 
246  ! Construct Pascal triangle of binomial coefficients.
247 
248  b = 0
249  do p = lbound(b, 1), ubound(b, 1)
250  b(p, 0) = 1
251  do q = 1, p - 1
252  b(p, q) = b(p - 1, q - 1) + b(p - 1, q)
253  end do
254  b(p, p) = 1
255  end do
256 
257  ! Constants of the phase function. This accumulates all phases along the nested dimensions.
258 
259  u = 0.
260  v = 0.
261 
262  ! Initialize the "deepest-nested" integral, so that f*I = f.
263 
264  i(0) = 1.
265 
266  ! Recursively evaluate levels.
267 
268  do lvl = 0, n - 1
269 
270  ! 1. Evaluate the base function for this level, including all its derivatives.
271  ! The base function is simply a coordinate power, r^m[lvl], so its
272  ! derivatives can be calculated directly.
273 
274  f(0) = a**m(lvl)
275 
276  do p = 1, (n - lvl)*(npp + 1) - 1
277  f(p) = (m(lvl) + 1 - p) * f(p - 1) / a
278  end do
279 
280  ! 2. Combine the base function with the integral from the previous level,
281  ! resulting in the effective base function fg for this level.
282 
283  do p = 0, (n - lvl)*(npp + 1) - 1
284  fg(p) = 0
285  do q = 0, p
286  fg(p) = fg(p) + b(p,q) * f(p - q) * i(q)
287  end do
288  end do
289 
290  ! 3. Evaluate the phase function at this level. This is required for construction
291  ! of the individual terms of the asymptotic expansion, as well as for finalization
292  ! of the integral after the terms are summer. The highest derivative degree
293  ! needed is Npp plus the number of derivatives of the resulting integral that
294  ! we are calculating at this level.
295 
296  u = u + s(2*lvl+1)*k(2*lvl+1) + s(2*lvl+0)*k(2*lvl+0) + imu*c
297  v = v + z*s(2*lvl+1)/abs(k(2*lvl+1)) + z*s(2*lvl+0)/abs(k(2*lvl+0))
298 
299  g(0) = imu*a / (u*a + v)
300  g(1) = imu*v / ((u*a + v)*(u*a + v))
301 
302  do p = 2, (n - lvl + 1)*(npp + 1) - 1
303  g(p) = g(p - 1) * (-p) * u / (u*a + v)
304  end do
305 
306  ! 4. Evaluate all terms of the asymptotic expansion of the integral, as well as their
307  ! derivatives up to the order required for the integral itself, that is (N - lvl)*(Npp + 1).
308 
309  do p = 0, (n - lvl)*(npp + 1) - 1
310  t(0, p) = fg(p)
311  end do
312 
313  do o = 1, npp
314  do p = 0, (n - lvl)*(npp + 1) - o - 1
315  t(o, p) = 0
316  do q = 0, p + 1
317  t(o, p) = t(o, p) + b(p + 1, q) * g(p + 1 - q) * t(o - 1, q)
318  end do
319  end do
320  end do
321 
322  ! 5. Sum the terms of the asymptotic expansion to get the reduced integral J (and its derivatives).
323 
324  do p = 0, (n - lvl - 1)*(npp + 1)
325  j(p) = 0
326  do o = 0, npp
327  j(p) = j(p) + t(o, p)
328  end do
329  end do
330 
331  ! 5. Evaluate the resulting integral (and derivatives) at this level.
332 
333  do p = 0, (n - lvl - 1)*(npp + 1)
334  i(p) = 0
335  do q = 0, p
336  i(p) = i(p) + b(p, q) * g(p - q) * j(q)
337  end do
338  end do
339 
340  end do
341 
342  val = i(0)
343 
344  end function nested_exp_integ
345 
346 
369  complex(wp) function nested_coul_integ (Z, a, c, N, m, s, l, k) result (val)
370 
371  use multidip_params, only: ntermsasy
372  use multidip_special, only: cphase, kahan_add
373 
374  integer, intent(in) :: n, m(1:n), s(1:2*n), l(1:2*n)
375  real(wp), intent(in) :: z, a, c
376  complex(wp), intent(in) :: k(1:2*n)
377 
378  integer :: nn(2*n), ms(n), p
379  complex(wp) :: an(2*n), bn(2*n), factor(2*n)
380 
381  logical :: at_end
382  real(wp) :: total_phase, total_exponent, kr, ki
383  complex(wp) :: integ, err
384 
385  at_end = .false.
386  val = 0
387  err = 0
388  factor = 1.
389  nn = 0
390 
391  ! initial values of the Pochhammer symbols in the asymptotic series for the Coulomb-Hankel (or real Whittaker) functions
392  do p = 1, 2*n
393  an(p) = 1 + l(p) - z*s(p)*imu/k(p)
394  bn(p) = -l(p) - z*s(p)*imu/k(p)
395  end do
396 
397  ! sum all terms of the Cartesian product of all Coulomb-Hankel (or real Whittaker) functions expansions
398  do while (.not. at_end)
399 
400  ! total power in r^m
401  do p = 1, n
402  ms(p) = m(p) - nn(2*p-1) - nn(2*p-0)
403  end do
404 
405  ! radial integral
406  integ = nested_exp_integ(z, a, c, n, ms, s, k)
407 
408  ! combine all coefficients from the expansions of all Coulomb-Hankel (or real Whittaker) functions
409  do p = 1, 2*n
410  integ = integ * factor(p)
411  end do
412 
413  ! add the integral value using the floating-point-error compensating routine
414  call kahan_add(val, integ, err)
415 
416  ! pick up the next single term from the product of expansions of all Coulomb-Hankel (or real Whittaker) functions
417  do p = 1, 2*n
418  nn(p) = nn(p) + 1
419  if (nn(p) == ntermsasy) then
420  nn(p) = 0
421  factor(p) = 1.
422  at_end = .true.
423  else
424  factor(p) = factor(p) * (an(p) + nn(p) - 1) * (bn(p) + nn(p) - 1) / (2 * imu * s(p) * nn(p) * k(p))
425  at_end = .false.
426  exit
427  end if
428  end do
429 
430  end do
431 
432  ! calculate the overall exponential and phase factor
433  total_phase = 0
434  total_exponent = 0
435  do p = 1, 2*n
436  kr = real(k(p), wp)
437  ki = aimag(k(p))
438  if (ki == 0) then
439  ! positive energy solution -> Coulomb-Hankel function
440  total_phase = total_phase + s(p)*(kr*a + z*log(2*kr*a)/kr - pi*l(p)/2 + cphase(z, l(p), kr))
441  else
442  ! negative energy solution -> Whittaker function
443  total_exponent = total_exponent + s(p)*(-ki*a + z*log(2*ki*a)/ki)
444  end if
445  end do
446 
447  ! multiply the sum of expansion terms with the combined exponential factor
448  val = val * cmplx(cos(total_phase), sin(total_phase), wp) * exp(total_exponent - n*c*a)
449 
450  end function nested_coul_integ
451 
452 
485  complex(wp) function nested_cgreen_integ (Z, a, r0, c, N, sa, sb, m, l, k) result (val)
486 
487  use ieee_arithmetic, only: ieee_value, ieee_quiet_nan
488  use mpi_gbl, only: mpi_xermsg
493 
494  integer, intent(in) :: n, sa, sb, m(:), l(:)
495  real(wp), intent(in) :: z, a, c, r0
496  complex(wp), intent(in) :: k(:)
497 
498  logical :: acc(n)
499  integer :: order(n), mp(n), lp(2*n), sp(2*n), p, signs
500  real(wp) :: b, rs(n)
501  complex(wp) :: kp(2*n), err, integ, ix, ia
502 
503  character(len=100) :: msg
504 
505  val = 0
506  err = 0
507  b = a
508 
509  ! check if we computed this integral before
510  if (nested_cgreen_integ_cache % get(a, r0, c, n, sa, sb, m, l, k, val)) return
511 
512  ! test the accuracy of the integrand at the R-matrix radius "a" (NOTE: numerical correction implemented only for N = 1)
513  if (a < r0 .and. n == 1) then
514  rs = a
515  ix = nested_cgreen_eval(z, c, n, sa, sb, m, l, k, rs, .false.)
516  ia = nested_cgreen_eval(z, c, n, sa, sb, m, l, k, rs, .true.)
517  if (abs(ix) > 1e-10 .and. abs(ia - ix) > coultol * abs(ix)) then
518  ! R-matrix radius too small, prefer the user-given asymptotic radius "r0"
519  b = r0
520  end if
521  end if
522 
523  ! test the accuracy of the integrand at the asymptotic radius "b" (NOTE: nested_cgreen_eval implemented only for N = 1)
524  if (coulomb_check .and. n == 1) then
525  rs = b
526  ix = nested_cgreen_eval(z, c, n, sa, sb, m, l, k, rs, .false.)
527  ia = nested_cgreen_eval(z, c, n, sa, sb, m, l, k, rs, .true.)
528  if (abs(ix) > 1e-10 .and. abs(ia - ix) > abs(ix)) then
529  if (print_warnings) then
530  ! asymptotic radius too small, do not bother to evaluate the integrals at all
531  !$omp critical (print_warning)
532  write (*, '(5x,A,3(I0,1x))', advance = 'no') 'Warning: Giving up Coul-Green integ '
533  write (*, '(3(A,I0,SP))', advance = 'no') 'm = ', m, ', sa = ', sa, ', sb = ', sb
534  write (*, '(A,*(I0,","))', advance = 'no') ', l = ', l
535  write (*, '(A,SP,*(F0.4,","))', advance = 'no') ' E = ', real(k**2/2, wp)
536  write (*, '(SP,2(A,2E10.4),A)') ' Ix = ', ix, 'i, Ia = ', ia, 'i'
537  !$omp end critical (print_warning)
538  end if
539  return
540  end if
541  end if
542 
543  ! initial ordering of coordinates, rN < ... < r2 < r1 (order(1) = index of the largest coordinate)
544  order = -1
545 
546  ! for all orderings of the coordinates { rN, ..., r2, r1 }
547  do while (next_permutation(order))
548 
549  ! reorder quantum numbers according to coordinate order
550  do p = 1, n
551  mp(p) = m(order(p))
552  end do
553 
554  ! for all regular Coulomb function splittings, F = (H⁺ - H⁻)/2i
555  do signs = 0, 2**(n - 1) - 1
556 
557  ! Each bit of 'signs' (starting from the least significant one) corresponds to the sign of the Coulomb-Hankel
558  ! function H arising from the F function in the corresponding Coulomb-Green's function. If 'j' is the position
559  ! of the bit (starting from 0 for the least significant one), then the sign belongs to the Coulomb-Green's
560  ! function g(r(j+1), r(j)). There are N-1 Coulomb-Green's functions in total in the integrand, so we are iterating
561  ! only over all combinations of the first N-1 bits.
562 
563  ! these (original) coordinate indices have been processed already
564  acc = .false.
565 
566  ! assemble quantum numbers for coordinates sorted from largest to smallest
567  do p = 1, n
568 
569  ! For each coordinate, there are two Coulomb-Hankel functions. For r0, one of the Coulomb-Hankel functions
570  ! is H[sa]. For rN one of them is H[sb]. Others come from separation of g(r(j+1), r(j)). Here we are processing
571  ! coordinate r(j), where j = order[n]. This coordinate is connected by the Coulomb-Green's functions to the
572  ! previous coordinate r(j-1) and to the next coordinate r(j+1). The former connection is realized by (j-1)-th
573  ! Coulomb-Green's function, while the latter by the j-th one. We compare the current coordinate r(j) to its
574  ! neighbours r(j-1) and r(j+1) simply by checking whether we have already processed them, because we are
575  ! processing the coordinates in order of the descending order. Based on this comparison, either the appropriate
576  ! F sign (if this coordinate is smaller than neighbour) or the +1 sign for H+ (if the this coordinate is greater
577  ! then the neighbour) is used.
578 
579  sp(2*p - 1) = sa
580  sp(2*p - 0) = sb
581 
582  if (order(p) > 1) sp(2*p - 1) = merge(merge(-1, +1, btest(signs, order(p) - 2)), +1, acc(order(p) - 1))
583  if (order(p) < n) sp(2*p - 0) = merge(merge(-1, +1, btest(signs, order(p) - 1)), +1, acc(order(p) + 1))
584 
585  acc(order(p)) = .true.
586 
587  kp(2*p - 1) = k(order(p) - 0)
588  kp(2*p - 0) = k(order(p) + 1)
589 
590  lp(2*p - 1) = l(order(p) - 0)
591  lp(2*p - 0) = l(order(p) + 1)
592 
593  end do
594 
595  ! evaluate integral for this hyper-triangle
596  integ = (-1)**popcnt(signs) * nested_coul_integ(z, b, c, n, mp, sp, lp, kp)
597 
598  ! add the integral value using the floating-point-error compensating routine
599  call kahan_add(val, integ, err)
600 
601  end do
602 
603  end do
604 
605  ! add the Green's function factors
606  do p = 2, n
607  val = val * imu / k(p)
608  end do
609 
610  ! integrate over Q = (a,+∞)^N \ (b,+∞)^N numerically
611  if (a < b) then
612  select case (num_integ_algo)
613  case (1)
614  call nested_cgreen_correct_romberg(z, a, b, c, n, sa, sb, m, l, k, val)
615  case (2)
616  call nested_cgreen_correct_levin(z, a, b, c, n, sa, sb, m, l, k, val)
617  case default
618  write (msg, '(a,i0)') 'Unknown numerical integration algorithm ', num_integ_algo
619  call mpi_xermsg('multidip_integ', 'nested_cgreen_integ', trim(msg), 1, 1)
620  end select
621  end if
622 
623  ! remember this integral
624  if (cache_integrals) call nested_cgreen_integ_cache % set(a, r0, c, n, sa, sb, m, l, k, val)
625 
626  end function nested_cgreen_integ
627 
628 end module multidip_integ
Special integrals needed by MULTIDIP.
logical function get_nested_cgreen_integ_cache_t(this, a, r0, c, N, sa, sb, m, l, k, val)
Get integral from cache.
subroutine init_nested_cgreen_integ_cache_t(this, N, ne, a, r0, c)
Initialize integral cache.
subroutine set_nested_cgreen_integ_cache_t(this, a, r0, c, N, sa, sb, m, l, k, val)
Add integral to cache.
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.
complex(wp) function nested_exp_integ(Z, a, c, N, m, s, k)
Multi-dimensional triangular integral of exponentials and powers.
complex(wp) function nested_coul_integ(Z, a, c, N, m, s, l, k)
Multi-dimensional triangular integral of Coulomb-Hankel functions and powers.
type(nested_cgreen_integ_cache_t) nested_cgreen_integ_cache
Levin quadrature for numerical integration.
subroutine nested_cgreen_correct_levin(Z, a, b, c, N, sa, sb, m, l, k, v)
Numerically correct asymptotic approximation of the Coulomb-Green integral (Levin integration)
Hard-coded parameters of MULTIDIP.
integer, parameter ntermsppt
Number of terms in expansion of the exponential integral.
integer num_integ_algo
Numerical integration mode (1 = Romberg, 2 = Levin)
logical cache_integrals
Cache Coulomb-Green integrals in memory (but disables some threading)
logical coulomb_check
Skip integrals that cannot be asymptotically approximated well.
real(wp) coultol
Coulomb matching relative tolerance (decides whether to use asym. forms)
logical print_warnings
Print warnings about non-converged integrals.
integer, parameter ntermsasy
Number of terms in expansion of the Coulomb-Hankel functions.
Romberg quadrature for numerical integration.
complex(wp) function nested_cgreen_eval(Z, c, N, sa, sb, m, l, k, rs, asy)
Evaluate the Coulomb-Green's integrand.
subroutine nested_cgreen_correct_romberg(Z, a, b, c, N, sa, sb, m, l, k, v)
Numerically correct asymptotic approximation of the Coulomb-Green integral (Romberg integration)
Special functions and objects used by MULTIDIP.
subroutine kahan_add(X, dX, err)
Compensated summation.
complex(wp) function coulh(Z, s, l, Ek, r)
Coulomb-Hankel function.
complex(wp) function coulh_asy(Z, s, l, Ek, r)
Coulomb-Hankel function (asymptotic form)
real(wp) function cphase(Z, l, k)
Coulomb phase.
logical function next_permutation(p)
Construct or advance permutation.
Auxiliary routines.