Multidip 1.0
Multi-photon matrix elements
Loading...
Searching...
No Matches
multidip_levin.f90
Go to the documentation of this file.
1! Copyright 2021
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 Levin quadrature for numerical integration
23!> \author J Benda
24!> \date 2021
25!>
26!> This module implements the efficient Levin quadrature for numerical integration of oscillatory functions. It is used
27!> by multidip_integ to integrate products of Coulomb-Hankel functions. Its advantage lies particularly in the fact that
28!> it only requires evaluation of the Coulomb functions at the endpoints of the integration range; within the integration
29!> range it works with recurrence formulas only. Given how expensive the evaluation of the Coulomb-Hankel function is,
30!> using Levin integration in place of the straightforward Romberg integration speeds up the calculations by several
31!> orders of magnitude.
32!>
33!> See the following papers for more details on the theory:
34!> - D. Levin, *Fast integration of rapidly oscillatory functions*, J. Comput. Appl. Math. **67** (1996) 95-101.
35!> - J. L. Powell, *Recurrence formulas for Coulomb functions*, Phys. Rev. **72** (1947) 626.
36!>
38
39 use precisn_gbl, only: wp
40
41 implicit none
42
43contains
44
45 !> \brief Numerically correct asymptotic approximation of the Coulomb-Green integral (Levin integration)
46 !> \author J Benda
47 !> \date 2021 - 2023
48 !>
49 !> This function computes the integral of the Coulomb-Green's integrand over Q = (a,+∞)^N \ (b,+∞)^N numerically.
50 !> Currently, Levin integration based on the Chebyshev interpolation is used.
51 !>
52 !> \param Z Residual ion charge
53 !> \param a Lower bound
54 !> \param b Upper bound
55 !> \param c Damping factor (additional exp(-c*r) added to all r^m functions)
56 !> \param N Dimension (number of integration variables)
57 !> \param sa Sign of the right-most Coulomb-Hankel function
58 !> \param sb Sign of the left-most Coulomb-Hankel function
59 !> \param m Array of integer powers of length N
60 !> \param l Array of angular momenta of length N + 1
61 !> \param k Array of linear momenta of length N + 1
62 !> \param v Value of the asymptotic integral to correct (updated on exit from this subroutine)
63 !>
64 !> \warning This is currently only implemented for one-dimensional case containing no
65 !> Green's function at all.
66 !>
67 !> \todo Generalize for arbitrary dimension!
68 !>
69 subroutine nested_cgreen_correct_levin (Z, a, b, c, N, sa, sb, m, l, k, v)
70
71 integer, intent(in) :: N, sa, sb, m(:), l(:)
72 real(wp), intent(in) :: Z, a, b, c
73 complex(wp), intent(in) :: k(:)
74 complex(wp), intent(inout) :: v
75
76 ! WARNING: Implemented for 1D integrals only
77 if (n == 1) then
78 v = v + levin_adapt(z, a, b, c, m(1), sa, sb, l(1), l(2), k(1), k(2))
79 end if
80
81 end subroutine nested_cgreen_correct_levin
82
83
84 !> \brief One-dimensional adaptive Levin integration
85 !> \author J Benda
86 !> \date 2021 - 2023
87 !>
88 !> As needed, recursively subdivide the complete integration range and apply a fixed-order Levin quadrature to the
89 !> subintervals until further subdivision changes the resulting integral only a little (within tolerance).
90 !>
91 !> A scheme illustrating the indexing of nodes at different subdivision levels:
92 !>
93 !> \verbatim
94 !> (ra) (rb)
95 !> 1 0
96 !> |---------------------------------------| (depth 0)
97 !>
98 !> 2 3 0
99 !> |-------------------|-------------------| (depth 1)
100 !>
101 !> 4 5 6 7 0
102 !> |---------|---------|---------|---------| (depth 2)
103 !>
104 !> 8 9 10 11 12 13 14 15 0
105 !> |----|----|----|----|----|----|----|----| (depth 3)
106 !>
107 !> ... etc ...
108 !> \endverbatim
109 !>
110 !> The advantage of this scheme is that Nodes whose indices differ only by an even multiplicative factor are equivalent
111 !> (collocated) and can be uniquely indexed by the further irreducible odd number obtained by repeated division by two.
112 !> The repeated division by two can be efficiently implemented as a right bit shift (`shiftr`) by offset given by the number
113 !> of trailing zeros (`trailz`) in the binary representation of the index, often together amounting to just two machine
114 !> instructions.
115 !>
116 !> Intervals have the same index as their left point. That is, the top-level interval is the interval 1, consisting of two
117 !> subintervals 2 and 3, each of which is further composed of further subintervals.
118 !>
119 !> The positive-energy Coulomb wave functions are evaluated only in nodes adjacent to intervals where the integral has not
120 !> yet converged. The negative-energy Coulomb functions as well as the rest of the integrand is repeatedly evaluated in
121 !> Chebyshev nodes within each subinterval, see levin_integrate_2x2 and levin_integrate_4x4.
122 !>
123 !> \param Z Residual ion charge
124 !> \param ra Lower bound
125 !> \param rb Upper bound
126 !> \param c Damping factor (additional exp(-c*r) added to all r^m functions)
127 !> \param m Radial coordinate power
128 !> \param s1 Sign of the first Coulomb-Hankel function
129 !> \param s2 Sign of the second Coulomb-Hankel function
130 !> \param l1 Angular momentum of the first Coulomb-Hankel function
131 !> \param l2 Angular momentum of the second Coulomb-Hankel function
132 !> \param k1 Complex momentum of the first Coulomb-Hankel function
133 !> \param k2 Complex momentum of the second Coulomb-Hankel function
134 !>
135 complex(wp) function levin_adapt (Z, ra, rb, c, m, s1, s2, l1, l2, k1, k2) result (integ)
136
138
139 real(wp), intent(in) :: z, ra, rb, c
140 complex(wp), intent(in) :: k1, k2
141 integer, intent(in) :: m, s1, s2, l1, l2
142
143 integer, parameter :: max_intervals = 2**max_levin_level
144
145 integer :: depth
146 logical :: converged(max_intervals)
147 complex(wp) :: estimates(max_intervals)
148 complex(wp) :: hl_buffer(2, 2, max_intervals + 1)
149
150 ! at the beginning the integral over the whole integration range is definitely not converged
151 converged(1) = .false.
152
153 ! for all binary subdivision orders
154 do depth = 0, max_levin_level - 1
155
156 ! precalculate Coulomb function in subinterval endpoints for this subdivision of the integration interval
157 call levin_prepare(z, ra, rb, s1, s2, l1, l2, k1, k2, depth, converged, hl_buffer)
158
159 ! improve accuracy of not-yet-converged subintervals from previous subdivision
160 call levin_improve(z, ra, rb, c, m, l1, l2, k1, k2, 0, 1, depth, converged, hl_buffer, estimates)
161
162 ! abort on NaN
163 if (.not. estimates(1) == estimates(1)) then
164 print '(a)', 'WARNING: NaN encountered during Levin quadrature'
165 exit
166 end if
167
168 ! if the top-level integral converged, terminate
169 if (converged(1)) exit
170
171 end do
172
173 ! if full subdivision allowance was exhausted without convergence, complain
174 if (depth == max_levin_level) then
175 print '(A,I0,A)', 'WARNING: Adaptive Levin integration did not converge in ', max_levin_level, ' subdivisions'
176 end if
177
178 ! return the best stimate of the top-level integration interval
179 integ = estimates(1)
180
181 end function levin_adapt
182
183
184 !> \brief Precalculate Coulomb-Hankel functions for subsequent Levin integration
185 !> \author J Benda
186 !> \date 2021 - 2023
187 !>
188 !> Set up the given subdivision depth for subsequent use. This involves evaluation of the Coulomb functions at the
189 !> endpoints of the subintervals of the not-yet-converged parent intervals, as well as marking these subintervals
190 !> of the not-yet-converged parent intervals as "not converged", while marking subintervals of the already converged
191 !> parent intervals as "converged" (and not evaluating Coulomb functions for their sake).
192 !>
193 !> \param Z Residual ion charge
194 !> \param ra Lower bound
195 !> \param rb Upper bound
196 !> \param s1 Sign of the first Coulomb-Hankel function
197 !> \param s2 Sign of the second Coulomb-Hankel function
198 !> \param l1 Angular momentum of the first Coulomb-Hankel function
199 !> \param l2 Angular momentum of the second Coulomb-Hankel function
200 !> \param k1 Complex momentum of the first Coulomb-Hankel function
201 !> \param k2 Complex momentum of the second Coulomb-Hankel function
202 !> \param depth Current subdivision depth resulting in 2**depth subintervals
203 !> \param converged Logical flags per interval at all subdivision depths indicating whether its value needs improving
204 !> \param Hl_buffer Coulomb-Hankel functions evaluated at unique subdivision nodes (for l1, l1+1, l2 and l2+1)
205 !>
206 subroutine levin_prepare (Z, ra, rb, s1, s2, l1, l2, k1, k2, depth, converged, Hl_buffer)
207
208 integer, intent(in) :: s1, s2, l1, l2, depth
209 real(wp), intent(in) :: Z, ra, rb
210 complex(wp), intent(in) :: k1, k2
211 complex(wp), intent(inout) :: Hl_buffer(2, 2, *)
212 logical, intent(inout) :: converged(:)
213
214 real(wp) :: r
215 integer :: num_intervals, first_interval_idx, interval_idx, parent_interval_idx, evaluation_point_idx, storage_point_idx
216
217 num_intervals = 2**depth
218 first_interval_idx = 2**depth
219
220 ! when called for the first time, evaluate Coulomb functions at the end points of the total integration range
221 if (depth == 0) then
222 call levin_eval(z, ra, s1, s2, l1, l2, k1, k2, hl_buffer(:, :, 2))
223 call levin_eval(z, rb, s1, s2, l1, l2, k1, k2, hl_buffer(:, :, 1))
224 return
225 end if
226
227 ! evaluate Coulomb functions in all other needed points at this subdivision level
228 do interval_idx = first_interval_idx, first_interval_idx + num_intervals - 1, 2
229
230 ! check if the parent interval (if any) of this sub-interval is converged
231 if (depth > 0) then
232
233 parent_interval_idx = interval_idx / 2
234
235 ! if yes, mark this sub-interval as converged, too, and move on
236 if (converged(parent_interval_idx)) then
237 converged(interval_idx) = .true.
238 converged(interval_idx + 1) = .true.
239 cycle
240 end if
241
242 end if
243
244 ! otherwise mark it as unconverged
245 converged(interval_idx) = .false.
246 converged(interval_idx + 1) = .false.
247
248 ! only odd index (new in this subdivision level) needs to be precomputed, which is the right endpoint of this interval
249 evaluation_point_idx = interval_idx + 1
250 storage_point_idx = (evaluation_point_idx + 3)/2
251
252 ! calculate position
253 r = ra + (evaluation_point_idx - first_interval_idx) * (rb - ra) / num_intervals
254
255 ! perform the evaluation of Coulomb functions
256 call levin_eval(z, r, s1, s2, l1, l2, k1, k2, hl_buffer(:, :, storage_point_idx))
257
258 end do
259
260 end subroutine levin_prepare
261
262
263 !> \brief Evaluate a pair of Coulomb-Hankel functions in given point
264 !> \author J Benda
265 !> \date 2021 - 2024
266 !>
267 !> Evaluate elements of the Coulomb-Hankel functions matrix:
268 !> \f[
269 !> \mathsf{H} = \left(\begin{matrix}
270 !> H_{l_1 }^{s_1}(-1/k_1, k_1 r) & H_{l_2 }^{s_2}(-1/k_2, k_2 r) \\
271 !> H_{l_1 + 1}^{s_1}(-1/k_1, k_1 r) & H_{l_2 + 1}^{s_2}(-1/k_2, k_2 r)
272 !> \end{matrix}\right)
273 !> \f]
274 !> This subroutine will not distinguish between positive- and negative-energy Coulomb functions (the input momenta
275 !> are complex), but only the positive-energy values are actually later used in the rest of the code. If the sign
276 !> s1 or s2 is zero, the standing regular Coulomb function F is assumed instead of the Coulomb-Hankel function.
277 !>
278 !> \param Z Residual ion charge
279 !> \param r Evaluation radius
280 !> \param s1 Sign of the first Coulomb-Hankel function
281 !> \param s2 Sign of the second Coulomb-Hankel function
282 !> \param l1 Angular momentum of the first Coulomb-Hankel function
283 !> \param l2 Angular momentum of the second Coulomb-Hankel function
284 !> \param k1 Complex momentum of the first Coulomb-Hankel function
285 !> \param k2 Complex momentum of the second Coulomb-Hankel function
286 !> \param Hl Coulomb-Hankel functions evaluated at the evaluation radius (for l1, l1+1, l2 and l2+1)
287 !>
288 subroutine levin_eval (Z, r, s1, s2, l1, l2, k1, k2, Hl)
289
290 use multidip_special, only: coulh
291
292 real(wp), intent(in) :: Z, r
293 integer, intent(in) :: s1, s2, l1, l2
294 complex(wp), intent(in) :: k1, k2
295 complex(wp), intent(inout) :: Hl(2, 2)
296
297 real(wp) :: Ek1, Ek2
298
299 ek1 = real(k1**2, wp)/2
300 ek2 = real(k2**2, wp)/2
301
302 if (s1 == 0) then
303 hl(1, 1) = aimag(coulh(z, +1, l1, ek1, r))
304 hl(2, 1) = aimag(coulh(z, +1, l1 + 1, ek1, r))
305 else
306 hl(1, 1) = coulh(z, s1, l1, ek1, r)
307 hl(2, 1) = coulh(z, s1, l1 + 1, ek1, r)
308 end if
309
310 if (s2 == 0) then
311 hl(1, 2) = aimag(coulh(z, +1, l2, ek2, r))
312 hl(2, 2) = aimag(coulh(z, +1, l2 + 1, ek2, r))
313 else
314 hl(1, 2) = coulh(z, s2, l2, ek2, r)
315 hl(2, 2) = coulh(z, s2, l2 + 1, ek2, r)
316 end if
317
318 end subroutine levin_eval
319
320
321 !> \brief Perform one subdivision iteration of adaptive Levin integration and update estimates
322 !> \author J Benda
323 !> \date 2021 - 2023
324 !>
325 !> Recurse to the deepest target depth and evaluate integral estimates in all sub-intervals marked as "not converged".
326 !> Use these new values to improve estimates of all parent intervals (on all parent levels). If the updated estimate of
327 !> any interval changes within the builtin tolerance, mark it (and its subintervals) as converged.
328 !>
329 !> This subroutine checks the convergence using a relative and absolute tolerance. For convergence, either the relative
330 !> change between the base and improved estimate has to meet the relative tolerance, or the absolute difference between
331 !> these two estimates has to meet the absolute tolerance. The absolute tolerance is calculated as a product of the
332 !> relative tolerance and the estimate of the top-level integral.
333 !>
334 !> \param Z Residual ion charge
335 !> \param ra Lower bound
336 !> \param rb Upper bound
337 !> \param c Damping factor (additional exp(-c*r) added to all r^m functions)
338 !> \param m Radial coordinate power
339 !> \param l1 Angular momentum of the first Coulomb-Hankel function
340 !> \param l2 Angular momentum of the second Coulomb-Hankel function
341 !> \param k1 Complex momentum of the first Coulomb-Hankel function
342 !> \param k2 Complex momentum of the second Coulomb-Hankel function
343 !> \param depth Subdivision depth that gave rise to the current integration interval
344 !> \param interval_idx Index of the current integration interval
345 !> \param tgt_depth Current subdivision depth resulting in 2**depth subintervals
346 !> \param converged Logical flags per interval at all subdivision depths indicating whether its value needs improving
347 !> \param Hl Coulomb-Hankel functions evaluated at unique subdivision nodes (for l1, l1+1, l2 and l2+1)
348 !> \param estimates Integral estimates for all intervals in all subdivision levels
349 !>
350 recursive subroutine levin_improve (Z, ra, rb, c, m, l1, l2, k1, k2, depth, interval_idx, tgt_depth, converged, Hl, estimates)
351
352 real(wp), intent(in) :: z, ra, rb, c
353 complex(wp), intent(in) :: k1, k2
354 integer, intent(in) :: m, l1, l2, depth, interval_idx, tgt_depth
355 complex(wp), intent(in) :: hl(2, 2, *)
356 logical, intent(inout) :: converged(:)
357 complex(wp), intent(inout) :: estimates(:)
358
359 integer :: num_local_intervals, first_interval_idx, max_global_point_idx, left_point_global_idx, right_point_global_idx
360 integer :: left_point_local_idx, right_point_local_idx, left_point_unique_idx, right_point_unique_idx
361 integer :: left_point_storage_idx, right_point_storage_idx, left_subinterval_idx, right_subinterval_idx
362 real(wp) :: a, b, epsrel, epsabs, delta, reference
363
364 complex(wp) :: best_estimate,improved_estimate
365
366 ! at the target depth simply evaluate the integrals over the given subinterval and store the estimates
367 if (depth == tgt_depth) then
368
369 num_local_intervals = 2**depth
370 first_interval_idx = 2**depth
371 max_global_point_idx = 2**(depth + 1)
372
373 left_point_global_idx = interval_idx;
374 right_point_global_idx = left_point_global_idx + 1
375
376 left_point_local_idx = interval_idx - first_interval_idx
377 right_point_local_idx = left_point_local_idx + 1
378
379 left_point_unique_idx = shiftr(left_point_global_idx, trailz(left_point_global_idx))
380 right_point_unique_idx = shiftr(mod(right_point_global_idx, max_global_point_idx), trailz(right_point_global_idx))
381
382 left_point_storage_idx = (left_point_unique_idx + 3)/2
383 right_point_storage_idx = merge(1, (right_point_unique_idx + 3)/2, right_point_unique_idx == 0)
384
385 a = ra + left_point_local_idx * (rb - ra) / num_local_intervals
386 b = ra + right_point_local_idx * (rb - ra) / num_local_intervals
387
388 estimates(interval_idx) = levin_integrate(z, a, b, c, m, l1, l2, k1, k2, &
389 hl(:, :, left_point_storage_idx), &
390 hl(:, :, right_point_storage_idx))
391
392 return
393
394 end if
395
396 ! get subinterval indices
397 left_subinterval_idx = 2*interval_idx;
398 right_subinterval_idx = 2*interval_idx + 1;
399
400 ! update all non-converged sub-intervals
401 if (.not. converged(left_subinterval_idx)) then
402 call levin_improve(z, ra, rb, c, m, l1, l2, k1, k2, &
403 depth + 1, left_subinterval_idx, tgt_depth, converged, hl, estimates)
404 end if
405 if (.not. converged(right_subinterval_idx)) then
406 call levin_improve(z, ra, rb, c, m, l1, l2, k1, k2, &
407 depth + 1, right_subinterval_idx, tgt_depth, converged, hl, estimates)
408 end if
409
410 ! check convergence by comparing estimate for this interval to the sum of the estimates for the two sub.intervals
411 best_estimate = estimates(interval_idx)
412 improved_estimate = estimates(left_subinterval_idx) + estimates(right_subinterval_idx)
413 epsrel = 1e-7
414 epsabs = epsrel * abs(estimates(1)) ! TODO ... take into account there are more unconverged subintervals in this depth
415 delta = abs(best_estimate - improved_estimate)
416 reference = 0.5 * (abs(best_estimate) + abs(improved_estimate))
417 if (delta < epsabs * reference .or. delta < epsabs) then
418 converged(interval_idx) = .true.
419 converged(left_subinterval_idx) = .true.
420 converged(right_subinterval_idx) = .true.
421 end if
422
423 ! update the estimate for this interval with the sum of estimates for the two subintervals
424 estimates(interval_idx) = improved_estimate
425
426 end subroutine levin_improve
427
428
429 !> \brief Fixed-order Levin integration
430 !> \author J Benda
431 !> \date 2021 - 2024
432 !>
433 !> Integrate product of two Coulomb-Hankel functions, coordinate power and radial exponential,
434 !> \f[
435 !> I = \int\limits_{r_a}^{r_b} H_{l_1}^{s_1}(\eta_1, k_1 r) r^{m}
436 !> \mathrm{e}^{-cr} H_{l_2}^{s_2}(\eta_2, k_2 r) \mathrm{d}r
437 !> \f]
438 !> (accepting both positive- and negative-energy Coulomb functions) using the method from
439 !> "D. Levin, Fast integration of rapidly oscillatory functions, J. Comput. Appl. Math. 67 (1996) 95-101".
440 !>
441 !> Coulomb functions at end points are not evaluated in this function and need to be provided via arguments `Hla` and `Hlb`,
442 !> see levin_eval.
443 !>
444 !> \param Z Residual ion charge
445 !> \param ra Lower bound
446 !> \param rb Upper bound
447 !> \param c Damping factor (additional exp(-c*r) added to all r^m functions)
448 !> \param m Radial coordinate power
449 !> \param l1 Angular momentum of the first Coulomb-Hankel function
450 !> \param l2 Angular momentum of the second Coulomb-Hankel function
451 !> \param k1 Complex momentum of the first Coulomb-Hankel function
452 !> \param k2 Complex momentum of the second Coulomb-Hankel function
453 !> \param Hla Coulomb-Hankel functions evaluated at ra (for l1, l1+1, l2 and l2+1)
454 !> \param Hlb Coulomb-Hankel functions evaluated at rb (for l1, l1+1, l2 and l2+1)
455 !>
456 complex(wp) function levin_integrate (Z, ra, rb, c, m, l1, l2, k1, k2, Hla, Hlb) result (integ)
457
458 use mpi_gbl, only: mpi_xermsg
459
460 real(wp), intent(in) :: z, ra, rb, c
461 complex(wp), intent(in) :: k1, k2
462 integer, intent(in) :: m, l1, l2
463 complex(wp), intent(in) :: hla(2, 2), hlb(2, 2)
464
465 if (aimag(k1) == 0 .and. aimag(k2) == 0) then
466 ! free-free transition, both Coulomb functions have positive energy and oscillate -> use 4-dimensional Levin integrator
467 integ = levin_integrate_4x4(z, ra, rb, c, m, l1, l2, real(k1, wp), real(k2, wp), hla, hlb)
468 else if (aimag(k1) == 0 .and. aimag(k2) /= 0) then
469 ! free-closed transition, only H1 has positive energy and oscillates -> use 2-dimensional Levin integrator
470 integ = levin_integrate_2x2(z, ra, rb, c, m, l2, l1, aimag(k2), real(k1, wp), hla(:, 1), hlb(:, 1))
471 else if (aimag(k1) /= 0 .and. aimag(k2) == 0) then
472 ! closed-free transition, only H2 has positive energy and oscillates -> use 2-dimensional Levin integrator
473 integ = levin_integrate_2x2(z, ra, rb, c, m, l1, l2, aimag(k1), real(k2, wp), hla(:, 2), hlb(:, 2))
474 else
475 call mpi_xermsg('multidip_levin', 'levin_integrate', 'Levin integrator cannot integrate closed-closed integrals.', 1, 1)
476 end if
477
478 end function levin_integrate
479
480
481 !> \brief Fixed-order Levin integration (dim 2)
482 !> \author J Benda
483 !> \date 2021 - 2024
484 !>
485 !> Integrate product of a Coulomb-Hankel function, decreasing real Whittaker function, coordinate power and radial exponential,
486 !> \f[
487 !> I = \int\limits_{r_a}^{r_b} H_{l_1}^{s_1}(\eta_1, k_1 r) r^{m}
488 !> \mathrm{e}^{-cr} W_{1/|k_2|,l_2+1/2}(2 |k_2| r) \mathrm{d}r
489 !> \f]
490 !> using the method from
491 !> "D. Levin, Fast integration of rapidly oscillatory functions, J. Comput. Appl. Math. 67 (1996) 95-101".
492 !>
493 !> The Levin's objective function \f$ \mathbf{w}(r) \f$ has the following two components, where the first of these
494 !> it the one of interest:
495 !> \f[
496 !> \mathbf{w}(r) = \left(\begin{matrix}
497 !> H_{l_1 }^{s_1}(\eta_1, k_1 r) \\
498 !> H_{l_1 + 1}^{s_1}(\eta_1, k_1 r)
499 !> \end{matrix}\right) .
500 !> \f]
501 !>
502 !> The Levin matrix \f$ \mathsf{A}(r) = \mathsf{a}/r + \mathsf{b} \f$ is constructed from the known
503 !> Coulomb function recursion relations,
504 !> \f[
505 !> \frac{\mathrm{d}}{\mathrm{d}r} H_{l+1}(\eta, kr)
506 !> = k R_{l+1}(\eta) H_{l}(\eta, kr) - k S_{l+1}(\eta, kr) H_{l+1}(\eta, kr) \,,
507 !> \f]
508 !> \f[
509 !> \frac{\mathrm{d}}{\mathrm{d}r} H_{l}(\eta, kr)
510 !> = k S_{l+1}(\eta, kr) H_{l}(\eta, kr) - k R_{l+1}(\eta) H_{l+1}(\eta, kr) \,.
511 !> \f]
512 !> where
513 !> \f[
514 !> R_{l}(\eta) = \sqrt{1 + \eta^2/l^2} \,, \qquad
515 !> S_{l}(\eta, \rho) = l/\rho + \eta/l \,.
516 !> \f]
517 !> See for example "J. L. Powell, Recurrence formulas for Coulomb functions, Phys. Rev. 72 (1947) 626" or DLMF §33.4.
518 !>
519 !> The two-component Levin auxiliary non-oscillatory function \f$ \mathbf{p}(r) \f$ is expanded in Chebyshev polynomials
520 !> of order 5 and the expansion coefficients obtained from collocation equation of rank 6. Collocation points are chosen
521 !> to be idential to Chebyshev nodes. This should result in the best possible interpolation of \f$ \mathbf{p}(r) \f$
522 !> for given Chebyshev order.
523 !>
524 !> Coulomb functions at end points are not evaluated in this function and need to be provided via arguments `wa` and `wb`.
525 !>
526 !> \param Z Residual ion charge
527 !> \param ra Lower bound
528 !> \param rb Upper bound
529 !> \param c Damping factor (additional exp(-c*r) added to all r^m functions)
530 !> \param m Radial coordinate power
531 !> \param lc Angular momentum of the negative-energy Coulomb-Hankel function
532 !> \param lo Angular momentum of the positive-energy Coulomb-Hankel function
533 !> \param kc Magnitude of the imaginary momentum of the negative-energy Coulomb-Hankel function
534 !> \param ko Magnitude of the real momentum of the positive-energy Coulomb-Hankel function
535 !> \param wa Positive-energy Coulomb-Hankel function evaluated at ra (for lo and lo+1)
536 !> \param wb Positive-energy Coulomb-Hankel function evaluated at rb (for lo and lo+1)
537 !>
538 complex(wp) function levin_integrate_2x2 (Z, ra, rb, c, m, lc, lo, kc, ko, wa, wb) result (integ)
539
540 use blas_lapack_gbl, only: blasint
541 use mpi_gbl, only: mpi_xermsg
543 use multidip_special, only: blas_dgetrf => dgetrf, blas_dgetrs => dgetrs, coulh
544 use phys_const_gbl, only: pi
545
546 real(wp), intent(in) :: z, ra, rb, c, kc, ko
547 integer, intent(in) :: m, lc, lo
548 complex(wp), intent(in) :: wa(2), wb(2)
549
550 integer, parameter :: levin_dim = 2
551 integer, parameter :: mat_dim = levin_dim * (cheb_order + 1)
552
553 integer :: ipoly, inode, a, b, i, j, row, col, sgn, ifun
554
555 real(wp) :: x, r, rl, wl, elem, cx
556 real(wp) :: cheb_node(0 : cheb_order)
557 real(wp) :: cheb_value(0 : cheb_order, 0 : cheb_order)
558 real(wp) :: cheb_deriv(0 : cheb_order, 0 : cheb_order)
559 real(wp) :: aa(levin_dim, levin_dim)
560 real(wp) :: ab(levin_dim, levin_dim)
561 real(wp) :: mat(mat_dim, mat_dim)
562 real(wp) :: coef(mat_dim)
563 real(wp) :: pa(levin_dim), pb(levin_dim)
564
565 complex(wp) :: contrib_a, contrib_b, delta
566
567 integer(blasint) :: n, pivots(mat_dim), info
568
569 ! only allow positive non-empty intervals
570 if (ra >= rb) then
571 call mpi_xermsg('multidip_levin', 'levin_integrate_2x2', &
572 'Levin integration not implemented for non-positive intervals', 1, 1)
573 end if
574
575 ! populate split matrices of the recurrence relations (total matrix is LevinA = Aa/r + Ab)
576 rl = sqrt(ko*ko + z*z/((lo + 1)*(lo + 1)))
577
578 aa(1, 1) = + lo + 1; aa(1, 2) = 0
579 aa(2, 1) = 0; aa(2, 2) = - lo - 1
580
581 ab(1, 1) = -z/(lo + 1); ab(1, 2) = -rl
582 ab(2, 1) = +rl; ab(2, 2) = +z/(lo + 1)
583
584 ! evaluate Chebyshev nodes as well as Chebyshev polynomials and their derivatives in these nodes
585 do inode = 0, cheb_order
586
587 x = cos((inode + rhalf) * pi / (cheb_order + 1))
588
589 cheb_node(inode) = x
590
591 cheb_value(inode, 0) = 1; cheb_deriv(inode, 0) = 0
592 cheb_value(inode, 1) = x; cheb_deriv(inode, 1) = 1
593
594 do ipoly = 1, cheb_order - 1
595 cheb_value(inode, ipoly + 1) = 2*x*cheb_value(inode, ipoly) - cheb_value(inode, ipoly - 1)
596 cheb_deriv(inode, ipoly + 1) = 2*(ipoly + 1)*cheb_value(inode, ipoly) &
597 + (ipoly + 1)*cheb_deriv(inode, ipoly - 1) / max(1, ipoly - 1)
598 end do
599
600 end do
601
602 ! construct the matrix of the equations
603 do a = 1, levin_dim
604 do i = 0, cheb_order
605 row = (a - 1) * (cheb_order + 1) + i + 1
606 do b = 1, levin_dim
607 do j = 0, cheb_order
608 col = (b - 1) * (cheb_order + 1) + j + 1
609 x = cheb_node(i)
610 r = ra + (x + 1) / 2 * (rb - ra)
611 elem = aa(b, a)/r + ab(b, a)
612 mat(row, col) = elem * cheb_value(i, j)
613 if (a == b) then
614 mat(row, col) = mat(row, col) + 2/(rb - ra) * cheb_deriv(i, j)
615 end if
616 end do
617 end do
618 end do
619 end do
620
621 ! construct the right-hand side (only populate elements pertaining to the first function)
622 do a = 1, levin_dim
623 do i = 0, cheb_order
624 row = (a - 1) * (cheb_order + 1) + i + 1
625 x = cheb_node(i)
626 r = ra + (x + 1) * (rb - ra) / 2
627 if (a == 1) then
628 wl = real(coulh(z, 0, lc, -kc*kc/2, r), wp)
629 coef(row) = r**m * exp(-c*r) * wl
630 else
631 coef(row) = 0
632 end if
633 end do
634 end do
635
636 ! solve the set of equations
637 n = mat_dim
638 call blas_dgetrf(n, n, mat, n, pivots, info)
639 if (info /= 0) then
640 call mpi_xermsg('multidip_levin', 'levin_integrate_2x2', &
641 'LU decomposition in Levin integration failed', int(info), 1)
642 end if
643 call blas_dgetrs('N', n, 1_blasint, mat, n, pivots, coef, n, info)
644 if (info /= 0) then
645 call mpi_xermsg('multidip_levin', 'levin_integrate_2x2', &
646 'LU backsubstitution in Levin integration failed', int(info), 1)
647 end if
648
649 ! evaluate the boundary terms
650 do a = 1, levin_dim
651 pa(a) = 0
652 pb(a) = 0
653 do i = 0, cheb_order
654 row = (a - 1) * (cheb_order + 1) + i + 1
655 cx = coef(row)
656 sgn = merge(+1, -1, mod(i, 2) == 0)
657 pa(a) = pa(a) + sgn*cx
658 pb(a) = pb(a) + cx
659 end do
660 end do
661
662 ! evaluate the integral
663 integ = 0
664 do ifun = 1, levin_dim
665 contrib_a = wa(ifun) * pa(ifun)
666 contrib_b = wb(ifun) * pb(ifun)
667 delta = contrib_b - contrib_a
668 integ = integ + delta
669 end do
670
671 end function levin_integrate_2x2
672
673
674 !> \brief Fixed-order Levin integration (dim 4)
675 !> \author J Benda
676 !> \date 2021 - 2024
677 !>
678 !> Integrate product of two Coulomb-Hankel functions, coordinate power and radial exponential,
679 !> \f[
680 !> I = \int\limits_{r_a}^{r_b} H_{l_1}^{s_1}(\eta_1, k_1 r) r^{m}
681 !> \mathrm{e}^{-cr} H_{l_2}^{s_2}(\eta_2, k_2 r) \mathrm{d}r
682 !> \f]
683 !> using the method from
684 !> "D. Levin, Fast integration of rapidly oscillatory functions, J. Comput. Appl. Math. 67 (1996) 95-101".
685 !>
686 !> The Levin's objective function \f$ \mathbf{w}(r) \f$ has the following four components, where the first of these
687 !> it the one of interest:
688 !> \f[
689 !> \mathbf{w}(r) = \left(\begin{matrix}
690 !> H_{l_1 }^{s_1}(\eta_1, k_1 r) H_{l_2 }^{s_2}(\eta_2, k_2 r) \\
691 !> H_{l_1 }^{s_1}(\eta_1, k_1 r) H_{l_2 + 1}^{s_2}(\eta_2, k_2 r) \\
692 !> H_{l_1 + 1}^{s_1}(\eta_1, k_1 r) H_{l_2 }^{s_2}(\eta_2, k_2 r) \\
693 !> H_{l_1 + 1}^{s_1}(\eta_1, k_1 r) H_{l_2 + 1}^{s_2}(\eta_2, k_2 r)
694 !> \end{matrix}\right) .
695 !> \f]
696 !>
697 !> The Levin matrix \f$ \mathsf{A}(r) = \mathsf{a}/r + \mathsf{b} \f$ is constructed from the known
698 !> Coulomb function recursion relations,
699 !> \f[
700 !> \frac{\mathrm{d}}{\mathrm{d}r} H_{l+1}(\eta, kr)
701 !> = k R_{l+1}(\eta) H_{l}(\eta, kr) - k S_{l+1}(\eta, kr) H_{l+1}(\eta, kr) \,,
702 !> \f]
703 !> \f[
704 !> \frac{\mathrm{d}}{\mathrm{d}r} H_{l}(\eta, kr)
705 !> = k S_{l+1}(\eta, kr) H_{l}(\eta, kr) - k R_{l+1}(\eta) H_{l+1}(\eta, kr) \,.
706 !> \f]
707 !> where
708 !> \f[
709 !> R_{l}(\eta) = \sqrt{1 + \eta^2/l^2} \,, \qquad
710 !> S_{l}(\eta, \rho) = l/\rho + \eta/l \,.
711 !> \f]
712 !> See for example "J. L. Powell, Recurrence formulas for Coulomb functions, Phys. Rev. 72 (1947) 626" or DLMF §33.4.
713 !>
714 !> The four-component Levin auxiliary non-oscillatory function \f$ \mathbf{p}(r) \f$ is expanded in Chebyshev polynomials
715 !> of order 5 and the expansion coefficients obtained from collocation equation of rank 6. Collocation points are chosen
716 !> to be idential to Chebyshev nodes. This should result in the best possible interpolation of \f$ \mathbf{p}(r) \f$
717 !> for given Chebyshev order.
718 !>
719 !> Coulomb functions at end points are not evaluated in this function and need to be provided via arguments `Hla` and `Hlb`.
720 !>
721 !> \param Z Residual ion charge.
722 !> \param ra Lower bound
723 !> \param rb Upper bound
724 !> \param c Damping factor (additional exp(-c*r) added to all r^m functions)
725 !> \param m Radial coordinate power
726 !> \param l1 Angular momentum of the first Coulomb-Hankel function
727 !> \param l2 Angular momentum of the second Coulomb-Hankel function
728 !> \param k1 Complex momentum of the first Coulomb-Hankel function
729 !> \param k2 Complex momentum of the second Coulomb-Hankel function
730 !> \param Hla Coulomb-Hankel functions evaluated at ra (for l1, l1+1, l2 and l2+1)
731 !> \param Hlb Coulomb-Hankel functions evaluated at rb (for l1, l1+1, l2 and l2+1)
732 !>
733 complex(wp) function levin_integrate_4x4 (Z, ra, rb, c, m, l1, l2, k1, k2, Hla, Hlb) result (integ)
734
735 use blas_lapack_gbl, only: blasint
736 use mpi_gbl, only: mpi_xermsg
738 use multidip_special, only: blas_dgetrf => dgetrf, blas_dgetrs => dgetrs
739 use phys_const_gbl, only: pi
740
741 real(wp), intent(in) :: z, ra, rb, c, k1, k2
742 integer, intent(in) :: m, l1, l2
743 complex(wp), intent(in) :: hla(2, 2), hlb(2, 2)
744
745 integer, parameter :: levin_dim = 4
746 integer, parameter :: mat_dim = levin_dim * (cheb_order + 1)
747
748 integer :: ipoly, inode, a, b, i, j, row, col, sgn, ifun
749
750 real(wp) :: x, r, r1, r2, elem, cx
751 real(wp) :: cheb_node(0 : cheb_order)
752 real(wp) :: cheb_value(0 : cheb_order, 0 : cheb_order)
753 real(wp) :: cheb_deriv(0 : cheb_order, 0 : cheb_order)
754 real(wp) :: aa(levin_dim, levin_dim)
755 real(wp) :: ab(levin_dim, levin_dim)
756 real(wp) :: mat(mat_dim, mat_dim)
757 real(wp) :: coef(mat_dim)
758 real(wp) :: pa(levin_dim), pb(levin_dim)
759
760 complex(wp) :: hln1a, hln2a, hln1b, hln2b, hlp1a, hlp2a, hlp1b, hlp2b, wa(levin_dim), wb(levin_dim)
761 complex(wp) :: contrib_a, contrib_b, delta
762
763 integer(blasint) :: n, pivots(mat_dim), info
764
765 ! only allow positive non-empty intervals
766 if (ra >= rb) then
767 call mpi_xermsg('multidip_levin', 'levin_integrate_4x4', &
768 'Levin integration not implemented for non-positive intervals', 1, 1)
769 end if
770
771 ! populate split matrices of the recurrence relations (total matrix is LevinA = Aa/r + Ab)
772 aa = 0
773 ab = 0
774
775 r1 = sqrt(k1*k1 + z/((l1 + 1)*(l1 + 1)))
776 r2 = sqrt(k2*k2 + z/((l2 + 1)*(l2 + 1)))
777
778 aa(1, 1) = + l1 + l2 + 2
779 aa(2, 2) = + l1 - l2
780 aa(3, 3) = - l1 + l2
781 aa(4, 4) = - l1 - l2 - 2
782
783 ab(1, 1) = - z/(l1 + 1) - z/(l2 + 1)
784 ab(2, 2) = - z/(l1 + 1) + z/(l2 + 1)
785 ab(3, 3) = + z/(l1 + 1) - z/(l2 + 1)
786 ab(4, 4) = + z/(l1 + 1) + z/(l2 + 1)
787
788 ab(1, 2) = -r2; ab(1, 3) = -r1
789 ab(2, 1) = +r2; ab(2, 4) = -r1
790 ab(3, 1) = +r1; ab(3, 4) = -r2
791 ab(4, 2) = +r1; ab(4, 3) = +r2
792
793 ! evaluate Chebyshev nodes as well as Chebyshev polynomials and their derivatives in these nodes
794 do inode = 0, cheb_order
795
796 x = cos((inode + rhalf) * pi / (cheb_order + 1))
797
798 cheb_node(inode) = x
799
800 cheb_value(inode, 0) = 1; cheb_deriv(inode, 0) = 0
801 cheb_value(inode, 1) = x; cheb_deriv(inode, 1) = 1
802
803 do ipoly = 1, cheb_order - 1
804 cheb_value(inode, ipoly + 1) = 2*x*cheb_value(inode, ipoly) - cheb_value(inode, ipoly - 1)
805 cheb_deriv(inode, ipoly + 1) = 2*(ipoly + 1)*cheb_value(inode, ipoly) &
806 + (ipoly + 1)*cheb_deriv(inode, ipoly - 1) / max(1, ipoly - 1)
807 end do
808
809 end do
810
811 ! construct the matrix of the equations
812 do a = 1, levin_dim
813 do i = 0, cheb_order
814 row = (a - 1) * (cheb_order + 1) + i + 1
815 do b = 1, levin_dim
816 do j = 0, cheb_order
817 col = (b - 1) * (cheb_order + 1) + j + 1
818 x = cheb_node(i)
819 r = ra + (x + 1) / 2 * (rb - ra)
820 elem = aa(b, a)/r + ab(b, a)
821 mat(row, col) = elem * cheb_value(i, j)
822 if (a == b) then
823 mat(row, col) = mat(row, col) + 2/(rb - ra) * cheb_deriv(i, j)
824 end if
825 end do
826 end do
827 !print '(8x,SP,*(E10.3,1x))', real(mat(row, :), wp)
828 end do
829 end do
830
831 ! construct the right-hand side (only populate elements pertaining to the first function)
832 do a = 1, levin_dim
833 do i = 0, cheb_order
834 row = (a - 1) * (cheb_order + 1) + i + 1
835 x = cheb_node(i)
836 r = ra + (x + 1) * (rb - ra) / 2
837 if (a == 1) then
838 coef(row) = r**m * exp(-c*r)
839 else
840 coef(row) = 0
841 end if
842 end do
843 end do
844
845 ! solve the set of equations
846 n = mat_dim
847 call blas_dgetrf(n, n, mat, n, pivots, info)
848 if (info /= 0) then
849 call mpi_xermsg('multidip_levin', 'levin_integrate_4x4', &
850 'LU decomposition in Levin integration failed', int(info), 1)
851 end if
852 call blas_dgetrs('N', n, 1_blasint, mat, n, pivots, coef, n, info)
853 if (info /= 0) then
854 call mpi_xermsg('multidip_levin', 'levin_integrate_4x4', &
855 'LU backsubstitution in Levin integration failed', int(info), 1)
856 end if
857
858 hln1a = hla(1, 1); hlp1a = hla(2, 1)
859 hln2a = hla(1, 2); hlp2a = hla(2, 2)
860 hln1b = hlb(1, 1); hlp1b = hlb(2, 1)
861 hln2b = hlb(1, 2); hlp2b = hlb(2, 2)
862
863 wa = [ hln1a * hln2a, hln1a * hlp2a, hlp1a * hln2a, hlp1a * hlp2a ]
864 wb = [ hln1b * hln2b, hln1b * hlp2b, hlp1b * hln2b, hlp1b * hlp2b ]
865
866 ! evaluate the boundary terms
867 do a = 1, levin_dim
868 pa(a) = 0
869 pb(a) = 0
870 do i = 0, cheb_order
871 row = (a - 1) * (cheb_order + 1) + i + 1
872 cx = coef(row)
873 sgn = merge(+1, -1, mod(i, 2) == 0)
874 pa(a) = pa(a) + sgn*cx
875 pb(a) = pb(a) + cx
876 end do
877 end do
878
879 ! evaluate the integral
880 integ = 0
881 do ifun = 1, levin_dim
882 contrib_a = wa(ifun) * pa(ifun)
883 contrib_b = wb(ifun) * pb(ifun)
884 delta = contrib_b - contrib_a
885 integ = integ + delta
886 end do
887
888 end function levin_integrate_4x4
889
890end module multidip_levin
Levin quadrature for numerical integration.
complex(wp) function levin_adapt(z, ra, rb, c, m, s1, s2, l1, l2, k1, k2)
One-dimensional adaptive Levin integration.
complex(wp) function levin_integrate(z, ra, rb, c, m, l1, l2, k1, k2, hla, hlb)
Fixed-order Levin integration.
complex(wp) function levin_integrate_2x2(z, ra, rb, c, m, lc, lo, kc, ko, wa, wb)
Fixed-order Levin integration (dim 2)
subroutine levin_prepare(z, ra, rb, s1, s2, l1, l2, k1, k2, depth, converged, hl_buffer)
Precalculate Coulomb-Hankel functions for subsequent Levin integration.
recursive subroutine levin_improve(z, ra, rb, c, m, l1, l2, k1, k2, depth, interval_idx, tgt_depth, converged, hl, estimates)
Perform one subdivision iteration of adaptive Levin integration and update estimates.
complex(wp) function levin_integrate_4x4(z, ra, rb, c, m, l1, l2, k1, k2, hla, hlb)
Fixed-order Levin integration (dim 4)
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)
subroutine levin_eval(z, r, s1, s2, l1, l2, k1, k2, hl)
Evaluate a pair of Coulomb-Hankel functions in given point.
Hard-coded parameters of MULTIDIP.
real(wp), parameter pi
real(wp), parameter rhalf
real(wp), parameter rone
integer, parameter max_levin_level
Maximal nesting level for Levin integration.
integer, parameter cheb_order
Order of Chebyshev interpolation used in Levin quadrature.
Special functions and objects used by MULTIDIP.