一般化Fresnel積分の数値解法

#Fresnel積分の基本形

Fresnel（フレネル）積分とは
$$
\int_0^{x} e^{i\frac{\pi}{2}\sigma^{2}}\mathrm{d}\sigma=c(x)+is(x)
$$
または
$$
\begin{align}
\int_0^{x}\cos\frac{\pi}{2}\sigma^{2}\mathrm{d}\sigma&=c(x) \\\
\int_0^{x}\sin\frac{\pi}{2}\sigma^{2}\mathrm{d}\sigma&=s(x)
\end{align}
$$
と書いた時の$c(x)$と$s(x)$の組のことです。これが何を意味するかについては[Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%95%E3%83%AC%E3%83%8D%E3%83%AB%E7%A9%8D%E5%88%86#:~:text=%E3%83%95%E3%83%AC%E3%83%8D%E3%83%AB%E7%A9%8D%E5%88%86%EF%BC%88%E3%83%95%E3%83%AC%E3%83%8D%E3%83%AB%E3%81%9B%E3%81%8D%E3%81%B6%E3%82%93,%E3%81%A7%E4%BD%BF%E3%82%8F%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B%E3%80%82)に委ねます。いわゆる特殊関数に含まれているもので、解析的に計算することができません。ただし数値計算法はよく知られており、[Numerical Recipes](http://numerical.recipes/)にも書かれています。

参考までに、[ZM](https://github.com/mi-lib/zm)の中での実装コードを示します。

```C
#define Z_FRESNEL_FP_MIN ( 1.0e-30 )

/* Fresnel integral by Gilbert formula */
static bool _zFresnelIntgGilbert(double x, double *s, double *c)
{
  int i, n;
  double sum, t, t0;

  *s = sum = 0;
  *c = t = x;
  t0 = zPI_2 * x * x;
  for( n=3, i=1; i<=Z_MAX_ITER_NUM; i++, n+=2 ){
    t *= t0/i;
    sum += ( i % 4 <= 1 ? t : -t ) / n;
    if( t < fabs(sum) * zTOL ) break;
    if( i % 2 ){
      *s = sum;
      sum = *c;
    } else{
      *c = sum;
      sum = *s;
    }
  }
  if( i > Z_MAX_ITER_NUM ){
    ZITERWARN( Z_MAX_ITER_NUM );
    return false;
  }
  return true;
}

/* Fresnel integral by modified Lentz method */
static bool _zFresnelIntgLentz(double x, double *s, double *c)
{
  int i, n;
  double a, pix2;
  zComplex z1, b, ca, cd, cc, d, h, del, cs;
  bool ret = true;

  pix2 = zPI_2 * x * x;
  _zComplexCreate( &z1, 1.0, 0.0 );
  _zComplexCreate( &b, 1.0, -pix2*2 );
  _zComplexCreate( &cc, 1.0/Z_FRESNEL_FP_MIN, 0.0 );
  zComplexCDiv( &z1, &b, &d );
  _zComplexCopy( &d, &h );
  for( n=1, i=2; i<=Z_MAX_ITER_NUM; i++, n+=2 ){
    a = -n * (n+1);
    b.re += 4.0;
    _zComplexMul( &d, a, &cd );
    _zComplexAddDRC( &cd, &b );
    zComplexCDiv( &z1, &cd, &d );
    zComplexInv( &cc, &cd );
    _zComplexMulDRC( &cd, a );
    _zComplexAdd( &cd, &b, &cc );
    _zComplexCMul( &cc, &d, &del );
    zComplexCMulDRC( &h, &del );
    del.re -= 1.0;
    if( zComplexIsTiny( &del ) ) break;
  }
  if( i > Z_MAX_ITER_NUM ){
    ZITERWARN( Z_MAX_ITER_NUM );
    ret = false;
  }
  _zComplexCreateCMul( &ca, h.re, h.im, x, -x );
  _zComplexCreateCMul( &cd, cos(pix2), sin(pix2), ca.re, ca.im );
  _zComplexCreateCMul( &cs, 0.5, 0.5, 1-cd.re, -cd.im );
  *c = cs.re;
  *s = cs.im;
  return ret;
}

/* Fresnel integral */
bool zFresnelIntgPI_2(double x, double *s, double *c)
{
  double ax;
  const double xth = 1.5; /* a heuristic threshould */
  bool ret = true;

  if( ( ax = fabs( x ) ) < sqrt( Z_FRESNEL_FP_MIN ) ){ /* avoid failure of convergence */
    *s = 0;
    *c = ax;
  } else if( ax <= xth ){ /* Gilbert formula for a small argument */
    ret = _zFresnelIntgGilbert( ax, s, c );
  } else{ /* modified Lentz method for a large argument */
    ret = _zFresnelIntgLentz( ax, s, c );
  }
  if( x < 0 ){ /* antisymmetry */
    *c = -*c;
    *s = -*s;
  }
  return ret;
}
```
複素数使った修正Lentz法の所がちょっと見づらいですが、ご勘弁を。

#Fresnel積分の一般化

さて、上記のように既に知られている数値計算法を使い、次の計算をしたいというのがこの記事の本題です。
$$
\int_0^{x} \exp {i(\phi_{0}+\phi_{1}\sigma+\phi_{2}\sigma^{2})}\mathrm{d}\sigma=C(x)+iS(x)
$$
こんなもの何に使うの？という疑問は尤もで、クロソイド補間に使います。例えば次の牧野先生の方法とか。

https://www.jstage.jst.go.jp/article/jjspe1986/60/1/60_1_80/_article/-char/ja/

##基本形のスケーリング

まずは次を計算する方法から考えましょう。
$$
\int_0^{x} e^{i\phi_{2}\sigma^{2}}\mathrm{d}\sigma=C_{2}(x;\phi_{2})+iS_{2}(x;\phi_{2})
$$
混乱を避けるために、いったん元の基本形の媒介変数を$\sigma^{\prime}$、変数を$x^{\prime}$とそれぞれ置き直します。
$$
\int_0^{x^{\prime}} e^{i\frac{\pi}{2}\sigma^{\prime 2}}\mathrm{d}\sigma^{\prime}=c(x^{\prime})+is(x^{\prime})
$$
意味は変わっていません。$\phi_{2}$は任意の実数を取り得ますが、簡単のためここでは正である$(\phi_{2}>0)$と仮定しましょう。基本形を上記に揃えるため
$$
\phi_{2}\sigma^{2}=\frac{\pi}{2}\sigma^{\prime 2}
$$
なる変数変換を行えば、
$$
\sigma^{\prime}=\sqrt{\frac{2\phi_{2}}{\pi}}\sigma,\qquad
\mathrm{d}\sigma^{\prime}=\sqrt{\frac{2\phi_{2}}{\pi}}\mathrm{d}\sigma
$$
であり、また積分範囲は
$$
\begin{align}
\sigma^{\prime}&:0\rightarrow x^{\prime}
\\\
\sigma&:0\rightarrow \sqrt{\frac{2\phi_{2}}{\pi}}x^{\prime}
\end{align}
$$
と対応しますので、
$$
\sqrt{\frac{2\phi_{2}}{\pi}}\int_0^{\sqrt{\frac{2\phi_{2}}{\pi}}x^{\prime}} e^{i\phi_{2}\sigma^{2}}\mathrm{d}\sigma=c(x^{\prime})+is(x^{\prime})
$$
が言えます。よって、
$$
\begin{align}
C_{2}(x;\phi_{2})&=\sqrt{\frac{\pi}{2\phi_{2}}}c\left(\sqrt{\frac{2\phi_{2}}{\pi}}x\right)
\\\
S_{2}(x;\phi_{2})&=\sqrt{\frac{\pi}{2\phi_{2}}}s\left(\sqrt{\frac{2\phi_{2}}{\pi}}x\right)
\end{align}
$$
となります。

$\phi_{2}$が負の場合は、
$$
\begin{align}
C_{2}(x;\phi_{2})=\int_0^{x}\cos\phi_{2}\sigma^{2}\mathrm{d}\sigma&=\int_0^{x}\cos\left(-\phi_{2}\sigma^{2}\right)\mathrm{d}\sigma=C_{2}(x;-\phi_{2}) \\\
S_{2}(x;\phi_{2})=\int_0^{x}\sin\phi_{2}\sigma^{2}\mathrm{d}\sigma&=-\int_0^{x}\sin\left(-\phi_{2}\sigma^{2}\right)\mathrm{d}\sigma=-S_{2}(x;-\phi_{2})
\end{align}
$$
とすれば良いです。

[ZM](https://github.com/mi-lib/zm)での実装は次の通りです。

```C
/* a scaled version of Fresnel integral */
bool zFresnelIntgScale(double x, double f, double *s, double *c)
{
  bool ret;
  double r;

  if( zIsTiny( f ) ){ /* sin 0x = 0, cos 0x = 1 */
    *s = 0;
    *c = x;
    return true;
  }
  r = sqrt( zPI_2/fabs(f) );
  ret = zFresnelIntgPI_2( x/r, s, c );
  *s *= f > 0 ? r : -r;
  *c *= r;
  return ret;
}
```

##一般化された形の平方完成

$\phi_{2}\neq 0$ならば、
$$
\begin{align}
\int_0^{x} \exp {i(\phi_{0}+\phi_{1}\sigma+\phi_{2}\sigma^{2})}\mathrm{d}\sigma
&=\int_0^{x} \exp i\left\\{\phi_{2}\left(\sigma+\frac{\phi_{1}}{2\phi_{2}}\right)^{2}+\phi_{0}-\frac{\phi_{1}^{2}}{4\phi_{2}}\right\\}\mathrm{d}\sigma
\\\
&=\exp i\left(\phi_{0}-\frac{\phi_{1}^{2}}{4\phi_{2}}\right)
\int_0^{x} \exp i\phi_{2}\left(\sigma+\frac{\phi_{1}}{2\phi_{2}}\right)^{2}\mathrm{d}\sigma
\\\
&=\exp i\left(\phi_{0}-\frac{\phi_{1}^{2}}{4\phi_{2}}\right)
\int_{\frac{\phi_{1}}{2\phi_{2}}}^{x+\frac{\phi_{1}}{2\phi_{2}}} e^{i\phi_{2}\sigma^{2}}\mathrm{d}\sigma
\\\
&=\exp i\left(\phi_{0}-\frac{\phi_{1}^{2}}{4\phi_{2}}\right)\left(
\int_{0}^{x+\frac{\phi_{1}}{2\phi_{2}}} e^{i\phi_{2}\sigma^{2}}\mathrm{d}\sigma
-\int_{0}^{\frac{\phi_{1}}{2\phi_{2}}} e^{i\phi_{2}\sigma^{2}}\mathrm{d}\sigma
\right)
\end{align}
$$
なので、
$$
\begin{align}
\bar{c}&=\cos\left(\phi_{0}-\frac{\phi_{1}^{2}}{4\phi_{2}}\right) \\\
\bar{s}&=\sin\left(\phi_{0}-\frac{\phi_{1}^{2}}{4\phi_{2}}\right) \\\
\bar{C}(x)&=C_{2}\left(x+\frac{\phi_{1}}{2\phi_{2}};\phi_{2}\right)-C_{2}\left(\frac{\phi_{1}}{2\phi_{2}};\phi_{2}\right) \\\
\bar{S}(x)&=S_{2}\left(x+\frac{\phi_{1}}{2\phi_{2}};\phi_{2}\right)-S_{2}\left(\frac{\phi_{1}}{2\phi_{2}};\phi_{2}\right)
\end{align}
$$
とおけば、
$$
C(x)+iS(x)=(\bar{c}+i\bar{s})(\bar{C}(x)+i\bar{S}(x))
$$
すなわち
$$
\begin{align}
C(x)&=\bar{c}\bar{C}(x)-\bar{s}\bar{S}(x) \\\
S(x)&=\bar{c}\bar{S}(x)+\bar{s}\bar{C}(x)
\end{align}
$$
となります。

##例外処理

$\phi_{2}=0$のときは、
$\phi_{1}\neq 0$なら
$$
\int_0^{x} \exp {i(\phi_{0}+\phi_{1}\sigma)}\mathrm{d}\sigma
=\frac{e^{i(\phi_{0}+\phi_{1}x)}-e^{i\phi_{0}}}{i\phi_{1}}
$$
より
$$
\begin{align}
C(x)&=\frac{\sin(\phi_{0}+\phi_{1}x)-\sin\phi_{0}}{\phi_{1}} \\\
S(x)&=-\frac{\cos(\phi_{0}+\phi_{1}x)-\cos\phi_{0}}{\phi_{1}}
\end{align}
$$
$\phi_{1}=0$なら
$$
\int_0^{x} \exp {i\phi_{0}}\mathrm{d}\sigma=xe^{i\phi_{0}}
$$
より
$$
\begin{align}
C(x)&=x\cos\phi_{0} \\\
S(x)&=x\sin\phi_{0}
\end{align}
$$
となります。これで全ての場合の一般化Fresnel積分が求まりました。

[ZM](https://github.com/mi-lib/zm)での実装を最後に示します。

```C
/* generalized Fresnel integral */
bool zFresnelIntgGen(double x, double f0, double f1, double f2, double *s, double *c)
{
  double s1, c1, s2, c2;
  bool ret1 = true, ret2 = true;

  if( zIsTiny( f2 ) ){
    zSinCos( f0, &s1, &c1 );
    if( zIsTiny( f1 ) ){
      *s = x * s1;
      *c = x * c1;
    } else{
      zSinCos( f0 + f1 * x, &s2, &c2 );
      *s =-( c2 - c1 ) / f1;
      *c = ( s2 - s1 ) / f1;
    }
  } else{
    ret1 = zFresnelIntgScale( x+0.5*f1/f2, f2, &s1, &c1 );
    ret2 = zFresnelIntgScale(   0.5*f1/f2, f2, &s2, &c2 );
    s1 -= s2;
    c1 -= c2;
    zSinCos( f0 - 0.25 * f1*f1 / f2, &s2, &c2 );
    *c = c1*c2 - s1*s2;
    *s = c1*s2 + s1*c2;
  }
  return ret1 && ret2;
}
```

#クロソイド曲線

せっかくなのでクロソイド曲線を描いてみました。(C(x),S(x))の軌跡をプロットしたものです。
![clothoid.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706455/aba91b7b-29c6-d4bb-c1a8-47aba2433a26.png)
$\phi_{0}$、$\phi_{1}$、$\phi_{2}$を色々変えることで中心位置、傾き、長さが変わります。