超音波流体屋のプログラム備忘録

複素数引数ベッセル関数の実装

最終更新:

usapfrog

- view
管理者のみ編集可
(2015/03/27編集, 前の関数は遠方で発散するので)
cuda上で複素数入力のハンケル関数(complex Bessel function, Hankel function)を扱いたかったんでメモ。
四角い車輪の再発明なので用途に困ったとき以外は素直にboostライブラリの関数や、fortranライブラリの流用を検討すること。


計算式

$\L J_n(z) = \sum_{m=0}^{\infty} \frac{(-1)^m}{m!(n+m)!} \left( \frac{z}{2} \right)^{2m+n} $
$\L Y_n(z) = \frac{2}{\pi} J_n(z) \ln\frac{z}{2} - \frac{1}{\pi} \sum_{m=0}^{n-1} \frac{(n-m-1)!}{m!} \left( \frac{z}{2} \right)^{2m-n} -\frac{1}{\pi} \sum_{m=0}^{\infty} \frac{\psi(m+1)+\psi(n+m+1)}{m!(n+m)!} (-1)^m \left( \frac{z}{2} \right)^{2m+n} $
$\L \psi(m) = -\gamma - \frac{1}{m} + m \sum_{i=1}^\infty \frac{1}{i(i+m)} $
(γ=0.5772...はオイラー数)

遠方でハンケル関数は以下なので、上の計算オーダーをm=25までとして、abs(z)=5πで切り分ける。
$\L H_n(|z|\gg 1) \simeq \sqrt{\frac{2}{\pi z}} \exp\left[-j\left(z-\frac{n\pi}{2}-\frac{\pi}{4}\right)\right]$

オイラー数とψ(polygamma)関数の処理をまともにやると手間食うので厳密値をもらっておく。polygamma関数の厳密値
十分入力が大きくなれば近似で十分なので、Series polygamma(x)
x=20における近似誤差=1.6e-13なのでたぶん問題ないはず。


関数本体

CUDA上で使用したかったので、thrustの複素数ルーチンを使用する。
cuComplexでやりたければ、typedefの行とオペレーターとpowとexpが複素数非対応なのに注意して実装する。

  1. #include <stdio.h>
  2. #define _USE_MATH_DEFINES
  3. #include <math.h>
  4. #include "cuda_runtime.h"
  5. #include "device_launch_parameters.h"
  6. #include <thrust/complex.h>
  7. typedef thrust::complex<double> zcmplx;
  8.  
  9. #define GAMMA 0.57721566490153286060651209008240
  10. #define PSIMAXORD 20
  11.  
  12. #define TLR2EXP (5*M_PI)
  13. #define ORD 25
  14.  
  15. double factorial(int n) { return tgamma(n + 1); }
  16. double psi(int n)
  17. {
  18.  
  19. double res;
  20. double gam[PSIMAXORD] = {
  21. 0.0, 1.0, 3.0 / 2.0, 11.0 / 6.0, 25.0 / 12.0,
  22. 137.0 / 60.0, 49.0 / 20.0, 363.0 / 140.0, 761.0 / 280.0,
  23. 7129.0 / 2520.0, 7381.0 / 2520.0,
  24. 83711.0 / 27720.0, 86021.0 / 27720.0,
  25. 1145993.0 / 360360.0, 1171733.0 / 360360.0, 1195757.0 / 360360.0,
  26. 2436559.0 / 720720.0, 42142223.0 / 12252240.0,
  27. 14274301.0 / 4084080.0, 275295799.0 / 77597520.0};
  28.  
  29. if (n < 1) res = 0.0;
  30. else if (n <= PSIMAXORD) res = gam[n - 1] - GAMMA;
  31. else
  32. {
  33. res = -1.0 / (252.0*pow(n, 6)) + 1.0 / (120.0*pow(n, 4)) - 1.0 / (12.0*pow(n, 2));
  34. res += -1.0 / (2.0*n) - log(1.0 / n);
  35. }
  36. return res;
  37. }
  38.  
  39. zcmplx hankel(int n, zcmplx z)
  40. {
  41. zcmplx hres = zcmplx(0, 0);
  42. if (abs(z) < TLR2EXP)
  43. {
  44. zcmplx jres = zcmplx(0, 0);
  45. zcmplx yres = zcmplx(0, 0);
  46. for (int m = 0; m <= n - 1; m++)
  47. {
  48. double ycoeffB = 1.0 / M_PI * factorial(n - m - 1) / factorial(m);
  49. yres -= ycoeffB* pow(z / 2.0, 2.0 * m - n);
  50. }
  51.  
  52. zcmplx ycoeff1 = 2.0 / M_PI*log(z / 2.0);
  53. for (int m = 0; m <= ORD; m++)
  54. {
  55. zcmplx jnume = pow(-1.0, m) * pow(z / 2.0, 2.0 * m + n);
  56. double jdeno = factorial(m)*factorial(n + m);
  57. jres += jnume / jdeno;
  58.  
  59. zcmplx ytermA = ycoeff1*(jnume / jdeno);
  60. double ynumeC = psi(m + 1) + psi(n + m + 1);
  61. zcmplx ytermC = (ynumeC / jdeno / M_PI)*jnume;
  62. yres += (ytermA - ytermC);
  63. }
  64. hres = jres + zcmplx(0, -1)*yres;
  65. }
  66. else
  67. {
  68. zcmplx ares = sqrt(2.0 / (M_PI*z));
  69. zcmplx pres = zcmplx(0, -1)*(z - n*M_PI / 2.0 - M_PI / 4.0);
  70.  
  71. hres = ares * exp(pres);
  72. }
  73.  
  74. return hres;
  75. }
  76.  
  77. #define CYC 10
  78. #define DIV 40
  79.  
  80. int main()
  81. {
  82. FILE *f;
  83. double dx = M_PI / DIV;
  84. zcmplx ks = sqrt(1.0 / zcmplx(0,1));
  85. f = fopen("res.dat", "w");
  86. for (int i = 1; i <= CYC*DIV; i++)
  87. {
  88. double x = i*dx;
  89. zcmplx y = ks*x;
  90. zcmplx h0_x = hankel(0, x);
  91. zcmplx h1_x = hankel(1, x);
  92. zcmplx h0_y = hankel(0, y);
  93. zcmplx h1_y = hankel(1, y);
  94.  
  95. fprintf(f, "%e, %e %e %e %e %e %e %e %e\n", x,
  96. h0_x.real(), h0_x.imag(), h1_x.real(), h1_x.imag(),
  97. h0_y.real(), h0_y.imag(), h1_y.real(), h1_y.imag());
  98. }
  99. fclose(f);
  100. return 0;
  101. }
  102.  

精度検証用Matlabプログラム

5π=15.7なのでその辺を目視で確認したり。
  1. clear;
  2. j=1i;
  3.  
  4. load res.dat;
  5. x=res(:,1);
  6. X0=res(:,2)+j*res(:,3);
  7. X1=res(:,4)+j*res(:,5);
  8. Y0=res(:,6)+j*res(:,7);
  9. Y1=res(:,8)+j*res(:,9);
  10.  
  11. ks=sqrt(1/j);
  12. y=ks*x;
  13.  
  14. x0=besselh(0,2,x);
  15. x1=besselh(1,2,x);
  16. y0=besselh(0,2,y);
  17. y1=besselh(1,2,y);
  18.  
  19. plot(x, real(x0), x, real(X0), 'o', x, imag(x0), x, imag(X0), 'o'); xlim([15 20]);
  20. plot(x, real(x1), x, real(X1), 'o', x, imag(x1), x, imag(X1), 'o'); xlim([15 20]);
  21. plot(x, real(y0), x, real(Y0), 'o', x, imag(y0), x, imag(Y0), 'o'); ylim([-1e-5 1e-5]);
  22. plot(x, real(y1), x, real(Y1), 'o', x, imag(y1), x, imag(Y1), 'o'); ylim([-1e-5 1e-5]);
  23.  
  24. ex0 = abs(x0-X0); semilogy(x, ex0);
  25. ex1 = abs(x1-X1); semilogy(x, ex1);
  26. ey0 = abs(y0-Y0); semilogy(x, ey0);
  27. ey1 = abs(y1-Y1); semilogy(x, ey1);
  28.  
  29. plot(x, real(x0), x, real(X0), '.', x, imag(x0), x, imag(X0), '.',...
  30. x, real(y0), x, real(Y0), '.', x, imag(y0), x, imag(Y0), '.');
  31. xlim([0 20]);
  32. xlabel('x'); ylabel('Hankel H0(x), H0(y)');
  33. print -dpng "hankel.png"
  34.  
  35. semilogy(x, ex0, x, ey0); xlim([0 30]);
  36. xlabel('x'); ylabel('Error');
  37. print -dpng "error.png"
  38.  

結果

遠方の近似自体がそんなに精度良くないので精度を求めるときは使わないように。

添付ファイル
目安箱バナー