sikino のすべての投稿

複素関数の微分と積分

複素関数論のメモです。

  1. 微分
  2. 積分
  3. 参考文献

微分


コーシー・リーマンの関係式(まとめ)


複素関数がコーシー・リーマンの関係式を満たすと、その複素関数は通常の変数通りに微分することが出来ます。複素数\(z=x+iy\)を引数に持つ複素関数\(f(z)=f(x+iy)\)が、実数値関数\(u(x,y), v(x,y)\)を用いて

と書けているとき、コーシー・リーマンの関係式

を満たすならば、\(f(z)\)は正則関数と呼ばれます。ここで、
\(f_x(x,y)=\frac{\partial f}{\partial x}, f_y(x,y)=\frac{\partial f}{\partial y}\)を意味します。式(2)を満たす場合、複素関数\(f(z)\)の複素数\(z\)による微分は

と書くことができ、式(3), (4)は同じ値となります。

コーシー・リーマンの関係式(導出)


複素関数の引数の数は1つであり、それは複素数です。すなわち1変数関数です。故に、複素関数の微分を定義しようとすれば、
\begin{align}
\frac{df(z)}{dz}
\end{align}
という量は1つに定まるはずです。しかし実数の空間で生活している我々には、1つの複素数を実部と虚部に分けて考えてしまう癖のようなものがあります。そのせいで、複素関数は平面に記述される関数であり、実部方向と虚部方向をそれぞれ独立に考えることが出来る2変数関数だと勘違いしてしまいますが、そうではありません。

すなわち、2変数の実数値関数と1変数の複素関数は全く異なる関数であり、同じ様に扱ってはいけません。しかし、我々は1次元上に複素数を表現する方法を持っておらず、本来、1変数の複素関数を2変数の実数値関数のように表現しなければイメージすることが出来ません。この場合、複素関数の表現に何らかの制約がかかることがイメージできるでしょう。その制約が正則である、という条件であり、コーシー・リーマンの関係式なのです。

では実際に本来1変数関数の複素関数を実軸と虚軸という、あたかも2変数関数のように拡張して考えて、微分を考えてみましょう。

前提として、複素関数の微分
\begin{align}
\frac{df(z)}{dz}
\end{align}
は一意に定まるとします。この下で2変数関数として\(f(z)=f(x,y)\)を拡張した場合、微分したい点への近づき方が二通りあることに気が付きます。それは、\(x\)方向と\(y\)方向ということです。2変数関数であれば、偏微分の事です。どんな方向から近づいても微分値は一意に定まると考えているので、両結果は等しくなければなりません。
微分は実数値関数と同様に差分の極限として定義して、
\begin{align}
\frac{df(z)}{dz}\equiv \lim_{\Delta z\to 0}\frac{f(z+\Delta z)-f(z)}{\Delta z}
\end{align}
と書けるとします。ここで、\(\Delta z = \Delta x+i\Delta y\)を意味しており、\(\Delta x, \Delta y\)は実数です。

点\(z=(x, y)\)に実数軸に沿って近づく場合、\(\Delta y=0\)を考えればよいので、微分は差分の極限として
\begin{align}
\frac{df(z)}{dz}=\lim_{\Delta x\to 0}\frac{f(x+\Delta x,y)-f(x,y)}{\Delta x}
\end{align}
と書かれます。ここで\(h\)は実数です。また、虚数軸に沿って近づく場合\(\Delta x=0\)を考えればよいので、
\begin{align}
\frac{df(z)}{dz}=\lim_{\Delta y\to 0}\frac{f(x,y+\Delta y)-f(x,y)}{i\Delta y}
\end{align}
と書かれるはずです。複素関数の微分は一意に決まることから、二つの近づき方を考えたとしても一致していなければなりません。複素関数が\(f(z)=u(x,y)+iv(x,y)\)と実部と虚部があらわに分離されて書かれていたとするならば、
\begin{align}
&\lim_{\Delta x\to 0}\frac{f(x+\Delta x,y)-f(x,y)}{\Delta x}\
&=\lim_{\Delta x\to 0}\frac{[u(x+\Delta,y)+iv(x+\Delta,y)]-[u(x,y)+iv(x,y)]}{\Delta x} \
&=\lim_{\Delta x\to 0}\frac{[u(x+\Delta,y)-u(x,y)]+i[v(x+\Delta,y)-v(x,y)]}{\Delta x} \
&=\frac{\partial u}{\partial x}+i\frac{\partial v}{\partial x} \
\end{align}
と書けます。また、虚軸方向は
\begin{align}
&\lim_{\Delta y\to 0}\frac{f(x,y+\Delta y)-f(x,y)}{i\Delta y}\
&=\lim_{\Delta y\to 0}\frac{[u(x,y+\Delta y)+iv(x,y+\Delta y)]-[u(x,y)+iv(x,y)]}{i\Delta y} \
&=\lim_{\Delta y\to 0}\frac{-i[u(x,y+\Delta y)-u(x,y)]+[v(x,y+\Delta y)-v(x,y)]}{\Delta y} \
&=-i\frac{\partial u}{\partial y}+\frac{\partial v}{\partial y}
\end{align}
と書くことが出来ます。
両者はもともと同じものであるとしてきましたから、実部と虚部で比較します。
すると、

という関係式が導けるという訳です。これがコーシー・リーマンの関係式と呼ばれるもので、1変数関数の複素関数を実部、虚部という2変数関数に無理矢理拡張した場合に現れる条件です。
複素関数の微分が定義できるとき、複素関数の実部虚部は、この関係式を満たしていなければなりません。ある範囲においてすべての点で複素関数の微分が定義できることを、正則であると言ったり、滑らかである、解析的であると言います。
”複素関数の滑らか”は通常の”滑らか”と意味が異なることに注意しましょう。通常は不連続点が無いことを指しますが、複素関数の場合はそれ以上の制約(コーシー・リーマンの関係式)を含んでいます。

また、ここでは述べませんが、複素関数は1度微分できると何回でも微分することが出来ます。
さらに微分が定義できる正則関数であれば定義域を超えた領域へ関数が拡張できる場合があります。これが解析接続と呼ばれるテクニックです。コーシー・リーマンの関係式を制約として今まで捉えてきたのですが、この制約があるからこそまだ見ぬ定義域外をこの関係式を手掛かりに関数を拡張できるのです。

例として、究極的に滑らかそうに見えるけど微分できない関数を上げましょう。
\begin{align}
f(z)=z=x+iy
\end{align}
はコーシー・リーマンの関係式を満たすため、複素関数として微分が定義でき、正則です。しかし、その複素共役を取った関数
\begin{align}
f(z)=z^*=x-iy
\end{align}
はコーシー・リーマンの関係式を満たさないため、複素関数として微分が定義できず、正則ではありません。近づき方によって微分の値が変わってしまうのです。

通常の微分が可能という意味を明確にするため、例を挙げます。\(f(z)\)が\(z\)の多項式で書かれている場合、微分は

として求める事が出来ます。通常と同じですね。これは、複素数の成分を持たない場合には実軸上で考えたものと同等になるので、実数軸上の微分を自然に拡張したようになっています。

積分


特異点周りの積分


端的にまとめます。
点\(a\)を囲うように複素平面上の閉じた積分経路の方向を\(C\)で指定するとします。\(z\)の多項式で表現される関数\(f(z)=(z-a)^n\)の積分は、

となります。点\(a\)の周りを反時計回りに1周回る場合、\(C\)はパラメータ\(t\)を用いて\(z(t)\)と書くと、
\begin{align}
z(t)=a+re^{it},~(0\le t \le 2\pi)
\end{align}
と書けます。\(r\)は積分経路の半径を意味します。式(6)の計算をすれば、

となります。\(n=-1\)の時、右辺の積分は\(2\pi\)に等しい事は明らかです。それ以外の\(n\ne -1\)ならば、

となります。よって、

が導かれます。

留数定理


一つ前の、ある点周りの積分をもう少し一般的に考えましょう。\(z=a\)を中心とした閉曲線に沿った複素関数\(f(z)\)の複素積分\(\oint_C f(z)dz\)は

と書けます。ここで\(\text{Res}[f,a]\)は留数と呼ばれる量です。これから留数について述べていきます。

\(\text{Res}[f,a]\)は、\(f(z)\)が点\(a\)周りで

とローラン展開出来るとします。この時、\(a_{-1}\)を留数と呼びます。すなわち、

を意味します。

では、留数を実際に求めていきましょう。\(f(z)\)が点\(a\)周りでローラン展開可能な場合、\(n\)の下限が重要になります。下限を極の位数\(k\)と表現します。\(k\)は、

を満たすときの値として定義できます。

もし、関数\(f(z)\)が\(z=a\)で\(k\)位の極であれば、\(z=a\)周りで

と展開できます。
留数は\(a_{-1}\)なので、\(a_{-1}\)について式変形を行っていけば、

と計算できます。

参考文献


原惟行、松永秀章著、『複素解析入門』(第3刷2010, 初版2007)

流速チューン?設定におけるホップ量と初速の変化

流速チューンと呼ばれるエアガンのチューニングがあるそうですね。

流速チューンとは
 ・重いピストンヘッドを使う
 ・強いばねを使う
 ・短いバレルにする
というチューニングを行う事のようです[1]。この結果、
 ・ホップを掛けるほど初速が上がる
 ・破裂音が鳴る
という効果が得られるらしいです。これらの効果を物理的な言葉に置き換えます。

「ホップを掛ける」ということは、BB弾がホップを掛ける為のゴムの所で止まりそこを抜けるために力が必要、ということです。
すなわち、内圧がある程度高まらないと動き出さないということを意味します。
「破裂音が鳴る」ということは、射出時に外気圧と内圧の差が大きいことを意味します。
また、短いバレルにするのは初速が上がりすぎるためだそうです。

以上の振る舞いがシミュレーションで再現できるでしょうか。
グラフの横軸を射出に必要な内圧にとり、バレル長を10cmに設定した時の射出速度と射出時の内圧をプロットしたのが以下の図です。

確かによく言われている通り、BB弾が動き出すまでに力が必要なほど(ホップを掛けるほど)、初速が上がる振る舞いが再現できています。
ある程度のホップの強さからは頭打ちになるようです。このある程度の強さが実際のエアガンで掛けられる強さなのかはわかりません。
射出時の内圧も3気圧と非常に高くなります。外気圧は1気圧なので、これだけ差があれば破裂音が鳴るのも納得でしょう。

流速チューン?のパラメータ設定は以下の通りです。通常の設定と比べ、ピストンヘッド+ばねの重さを22→30g, ばね定数を431→800kg s-2に変更しました。
————————-
BB弾直径 5.95mm
BB弾重さ 0.25g
バレルの直径 6.05mm
バレル長 10cm
シリンダー直径 24mm
ピストンヘッド+ばねの重さ 30g
ばね自然長 150mm
ばね定数 800kg s-2
————————-
流速チューンだ!と言っている方々のブログ等を拝見しましたが、なかなかばねの強さやピストンヘッドの重さ等を詳細に詳しく述べているページはありませんでしたので、上記パラメータはなんとなく設定しています。
また、流速チューン?とはてなマークを付けているのは、具体的なパラメータ例が無いことや、定義が個人によって違うからです。

追記)
通常のばねや、ピストンヘッドの重さでもホップを掛けるほど初速が上がるという傾向は変わらないことが分かりました。
すなわち、上記の傾向はバレル長が短いことによる普遍的な傾向だ、ということです。バレルが短ければホップを掛けるほど初速が上がる、という意味です。

上の計算は以下のパラメータで計算を行いました。
————————-
BB弾直径 5.95mm
BB弾重さ 0.25g
バレルの直径 6.05mm
バレル長 10cm
シリンダー直径 24mm
ピストンヘッド+ばねの重さ 22g
ばね自然長 150mm
ばね定数 431kg s-2
————————-

以上の計算は、下記ページで配布しているエクセルシート(ver1.3以降)にて計算することが出来ます。
弾道計算のコード(Excel)

参考資料


[1]流速チューンの原理と製作例、注意点

弾道計算のコード(Excel)

Excelのマクロ(VBA)で弾道計算を行うコードを作成しました。

https://slpr.sakura.ne.jp/qp/supplement_data/BBullet_course_excel/BBbullet_course_ver1.41.xlsm
上記プログラムの利用の際は連絡先と著作権についてをご覧ください。

空気抵抗を修正したβ版を作成しました。参考設定は機能しません。
https://slpr.sakura.ne.jp/qp/supplement_data/BBullet_course_excel/BBbullet_course_ver1.5_beta.xlsm

以下の微分方程式を計算します。

エクセルシート”bullet”
\(
\begin{align}
m\frac{d^2 \vec{r}}{dt^2}&=
-mg\vec{k}-\frac{1}{2}C_d \rho \pi R^2 |\vec{V}|\vec{V}
-C_l \frac{4}{3}\pi R^3 2\rho |\vec{\omega}| |\vec{V}|\frac{\vec{V}\times\vec{\omega}}{|\vec{V}\times\vec{\omega}|} \\
\frac{d\mathbf{\omega}}{dt}&=N/I \\
N&=\frac{\rho C_f}{2}R\frac{4\pi R^2}{2}\left(|v_{up}|v_{up}+|v_{down}|v_{down}\right)
\end{align}
\)
回転の減衰は本来、弾道計算(BB弾)の理論で導いた通り積分を行わなければなりませんが、簡単にするためにBB弾を真四角の箱として近似して導いています。
この近似は本来の積分値と1パーセント程度の違いしか出てこないので、BB弾の計算には非常に良い近似です。
回転の減衰を取り入れたくない場合、入力パラメータの”回転の減衰”項目をNOにすることでそのように設定が出来ます。
ゼロインを固定した時の弾道の探索機能は実装していないので出来ません。
Excelの設定項目では、Fortranで実装したコードから求めたパラメータをあらかじめ用意しておきました。
設定されたものと書いてあっても、高精度に積分計算をして求めた結果と上の近似をもちいた本エクセルの結果は違くなります。数10cm程度のずれは出てきますので、その点は注意してください。

エクセルシート”barrel”
\(
\begin{align}
m\frac{d^2}{dt^2}x(t)&=\left[P(t)-P_0\right]S_{BB}-\frac{1}{2}C_d \rho \pi R^2 |v(t)|v(t) \\
m_s\frac{d^2}{dt^2}x_0(t)&=-k\left[x_0(t)-x_B-l\right]-[P(t)-P_0]S_s -F_f\label{bbin2} \\
\frac{d}{dt}V(t)&=v(t)S_{\text{barrel}}-v_0(t)S_s+c'(S_{barrel}-\pi R^2)\mathrm{sgn}(P(t)-P_0)\sqrt{\frac{2}{\rho}|P(t)-P_0|}
\end{align}
\)

内部は以下の構造をしていると想定しています。詳細はバレル内部でのBB弾の方程式をご覧ください。

計算手法はどちらも陽的4次ルンゲクッタ法を用いています。

使い方


  1. ファイルのダウンロード
  2. 最新
    https://slpr.sakura.ne.jp/qp/supplement_data/BBullet_course_excel/BBbullet_course_ver1.5_beta.xlsm

    をクリックでダウンロード、もしくは
    右クリックして、”名前を付けてリンク先を保存”からダウンロードしてください。

  3. 計算パラメータの入力
  4. 入力する場所は、色付きのセルです。

    • 弾道計算について

    • 出力結果は大きく分けて2つの項目に分かれています。
      1つ目は定数、射出高さに着弾、地面に着弾の項目は重要と思われるパラメータの抜粋、2つ目は弾道の時間ごとのデータです。
      グラフは、2つ目のデータを元に表示しているだけですので、必要に応じて追加、変更を行ってください。

    • バレル内部の計算について

    • 出力結果は大きく分けて2つの項目に分かれています。
      1つ目は定数、内圧が大気圧に等しい時に射出される時と指定したバレル長におけるパラメータの抜粋、2つ目はBB弾、ピストンの位置、速度の時間ごとのデータです。
      グラフは、2つ目のデータを元に表示しているだけですので、必要に応じて追加、変更を行ってください。

    手入力で入力することも可能ですが、参考設定と書かれたボタンを押すことで、予め設定されたパラメータを読み込ませることもできます。

  5. 計算開始
  6.   入力パラメータを入力した後、計算開始ボタンを押すことで計算が開始されます。

注意点

常識的なパラメータの範囲内でしか動作確認をしておりません。
この結果はあくまで一例です。本シミュレーションを信用して設計・組立を行い、本シミュレーションと結果が違う事があります。その場合私は責任を負いませんので、その点をご了承して使用してください。
BB弾で出来るからと言って、銃弾等の計算はそれなりの信用しかできないことを考慮してください。

追記


バレル内部の計算において、抜弾抵抗の考慮はしていません。すなわちホップが全く掛かっていない状況の計算です。
もし、ホップを掛けた状況を再現したいのであれば、以下のような考えで出来るでしょう。

まず、ホップを掛けた状態でBB弾をバレル内部から引き出す際に必要な力をどうにかして実験的に求めます。
このホップを掛けた際に必要な力\(F\)は、内部圧力\(P\)とBB弾の面積\(S_{BB}\)を用いて
\(
F=PS_{BB}
\)
と書けるはずです。すなわち、内圧がこの力に達した時にBB弾が射出されるとして考え、その内部圧力に達した際にBB弾の運動方程式を解き始める、という方法で可能なはずです。

参考文献

[1] 弾道計算の理論 -シキノート https://slpr.sakura.ne.jp/qp/bullet-course/
[2] BB弾の弾道学 -ハイパー道楽 http://www.hyperdouraku.com/colum/ballistics/index.html

極値を求めるニュートン法

ニュートン法に関するお話です。
”ニュートン法”と呼ばれる方法は
2種類(1.初期値近傍の極値を求めるニュートン法、2. ゼロ点を求めるためのニュートン法
があるようですが、ここでは1. 初期値近傍の極値を求める方のニュートン法についてのお話です。

まとめ

ベクトル\(\mathbf{x}=(x_1, x_2, \cdots, x_N)\)で指定される実数値関数\(f(\mathbf{x})\)の極値を求める事を考えます。
ここで、初期値近傍の点\(\mathbf{x}=\mathbf{x}^{(0)}\)が分かっている時、その近傍にある\(f(\mathbf{x})\)の極小値を与える\(\mathbf{x}=\mathbf{a}\)は適当な反復計算を行い、
\begin{equation}
\mathbf{a}=\lim_{k\to\infty}\mathbf{x}^{(k)}
\end{equation}
で与えられます。極値を求めるニュートン法を用いる場合、\(\mathbf{x}^{(k)}\)は漸化式として与えられ、
\begin{align}
\mathbf{x}^{(k+1)}&=\mathbf{x}^{(k)}+\mathbf{d}^{(k)} \\
\mathbf{d}^{(k)}&=-\left[H f\bigl(\mathbf{x}^{(k)}\bigr)\right]^{-1}\cdot \left[\nabla f\bigl(\mathbf{x}^{(k)}\bigr)\right]
\end{align}
として逐次的に与えられます。ここで、\(H f(\mathbf{x}), \nabla f\bigl(\mathbf{x}\bigr)\)は関数\(f(\mathbf{x})\)のヘッセ行列、傾き(Gradient)を表し、
\begin{equation}
\hat{H}f=
\begin{pmatrix}
\frac{\partial^2 f}{\partial x_1^2} & \frac{\partial^2 f}{\partial x_1 \partial x_2} &\cdots &\frac{\partial^2 f}{\partial x_1 \partial x_n} \\
\frac{\partial^2 f}{\partial x_2 \partial x_1} & \frac{\partial^2 f}{\partial x_2^2} &\cdots & \frac{\partial^2 f}{\partial x_2 \partial x_n} \\
\vdots & \vdots &\ddots & \vdots \\
\frac{\partial^2 f}{\partial x_n \partial x_1} & \frac{\partial^2 f}{\partial x_n \partial x_2} &\cdots &\frac{\partial^2 f}{\partial x_n^2} \\
\end{pmatrix}
,~~\nabla f=
\begin{pmatrix}
\frac{\partial f}{\partial x_1} \\
\frac{\partial f}{\partial x_2} \\
\vdots \\
\frac{\partial f}{\partial x_n}
\end{pmatrix}
\end{equation}
で与えられます。

特に\(f\)が\(x, y\)の2変数関数\(f(x,y)\)であるならば、
\begin{align}
\begin{pmatrix}
x^{(k+1)} \\
y^{(k+1)}
\end{pmatrix}
&=
\begin{pmatrix}
x^{(k)} \\
y^{(k)}
\end{pmatrix}
-\frac{1}{f_{xx}f_{yy}-f_{xy}f_{yx}}
\begin{pmatrix}
f_{yy} & -f_{xy} \\
-f_{yx} & f_{xx}
\end{pmatrix}
\begin{pmatrix}
f_x \\
f_y
\end{pmatrix}
\end{align}
ここで、
\begin{equation}
\hat{H}f
=
\begin{pmatrix}
f_{xx} & f_{xy} \\
f_{yx} & f_{yy}
\end{pmatrix}
,~~
\nabla f=
\begin{pmatrix}
f_x \\
f_y
\end{pmatrix}
\end{equation}
です。ここで、\(f_p\equiv\frac{\partial f}{\partial p}, ~f_{pq}\equiv\frac{\partial^2 f}{\partial p\partial q}, (p, q=x,y)\)と書きました。
微分を差分で近似するとします。\(x, y\)方向の刻み幅を\(h=h_x=h_y\)とすればそれぞれの偏微分は点\((x,y)\)近傍の7点を用いて
\begin{align}
f_x &= \frac{f(x+h, y) – f(x-h, y)}{2h} +O(h^2)\\
f_y &= \frac{f(x, y+h) – f(x, y-h)}{2h} +O(h^2)\\
f_{xx} &= \frac{f(x+h, y) – 2f(x, y)+ f(x-h, y)}{h^2} +O(h^2)\\
f_{xy} &=f_{yx}= \frac{1}{2h^2}\bigl[f(x+h, y) + f(x-h, y) + f(x, y+h) \\
&\hspace{1em}+ f(x, y-h)-2f(x,y)-f(x+h,y+h)-f(x-h,y-h)\bigr]+O(h^2) \\
f_{yy} &= \frac{f(x, y+h) – 2f(x, y)+ f(x, y-h)}{h^2} +O(h^2)
\end{align}
と書けます[2]。

導出


詳しくは述べませんが、1次元の問題における極小値を求めるニュートン法くらいは導いておきましょう。
多次元の場合は拡張すれば良く、考え方は変わりません。多次元では他の文献、例えば[1]を参考にして下さい。

関数\(f(x)\)を\(x=x^{(k)}\)周りでテイラー展開を行えば、
\begin{equation}
f(x)=f\bigl(x^{(k)}\bigr)+f’\bigl(x^{(k)}\bigr)\bigl(x-x^{(k)}\bigr)+\frac{1}{2}f^{\prime\prime}\bigl(x^{(k)}\bigr)\bigl(x-x^{(k)}\bigr)^2+O(\Delta^3)
\end{equation}
となります。ここで、\(\Delta=|x-x^{(k)}|\)とおきました。
今、欲しい解\(a\)は関数\(f(x)\)の極値なので、
\(\frac{df}{dx}=0\)を満たす\(x\)を探せばよいのです。よって、両辺を\(x\)で微分して、
\begin{align}
\frac{df(x)}{dx}=f’\bigl(x^{(k)}\bigr)+f^{\prime\prime}\bigl(x^{(k)}\bigr)\bigl(x-x^{(k)}\bigr)+O(\Delta^2)
\end{align}
を得ます。これが\(0\)になる\(x\)が解\(a\)なので、
\begin{equation}
\left. \frac{df(x)}{dx}\right|_{x=a}=f’\bigl(x^{(k)}\bigr)+f^{\prime\prime}\bigl(x^{(k)}\bigr)\bigl(a-x^{(k)}\bigr)+O(\Delta^2) = 0
\end{equation}
となります。今、\(O(\Delta^2)\)を無視すれば得られる解\(a\)は近似解\(a\approx x^{(k+1)}\)となるので、
\begin{equation}
f’\bigl(x^{(k)}\bigr)+f^{\prime\prime}\bigl(x^{(k)}\bigr)\bigl(x^{(k+1)}-x^{(k)}\bigr) = 0 \\
\end{equation}
を解いて
\begin{equation}
x^{(k+1)} = x^{(k)}-\frac{f’\bigl(x^{(k)}\bigr)}{f^{\prime\prime}\bigl(x^{(k)}\bigr)}
\end{equation}
となります。…ここまで来てしまうとニュートン・ラフソン法と同じですね。関数値がゼロになる点を探すのか、導関数値がゼロになる点を探すのかの違いなだけです。
多次元だと行列になってしまいシンプルではない形になり難しく見えますが、1次元では非常に単純です。

問題は1階(傾き)、2階導関数(ヘッセ行列)をどのように求めるかです。厳密な値が無い場合は近似的にしか得るしかありません。基本的にこれらの計算量は膨大になります。
最も簡単な方法は微分を差分に置き換えてしまうことですが、差分は余り良い方法とは言えません。

厳密なヘッセ行列は、例えば自動微分の考えで得ることが出来ます。例えば、Hyper-dual numbersによる二階偏微分の計算を参考にして得ることが出来ます。

また、引数が複素数で計算結果が実数で返ってくる関数\(f(z)\)の最小値を求めたいのであれば、\(z=x+iy\)として、\(f(x,y)\)の二変数関数としてその極致を探せば良いです。

2次元のプログラム


ここでは2次元の問題に限り、傾き、ヘッセ行列を差分で近似した場合のプログラムを記述します。

下のプログラムは関数
\begin{equation}
f(x,y)=x e^{-(x^2+y^2)/2}
\end{equation}
の、点\((x_0, y_0)=(-1.2, -0.3)\)近傍にある極値を求めるプログラムです。
グラフで示せば、以下の通りになります。
この関数の極値は黒点、初期値は赤点で示しています。

以下に実行例を示します。

$ gfortran main.f90
$ ./a.out
  -1.2000000000000000      -0.29999999999999999      -0.55840071716917605    
  -1.0000000016663813        3.4150669573473641E-013 -0.60653065971263342      4
$

1行目1,2,3列目はそれぞれ\(x,y\)の初期値, 関数\(f(x,y)\)の値を示しています。
また、2行目1,2,3,4列目は求まった\(x,y\)の値, 関数\(f(x,y)\)の値、そして収束に至るまでに行った繰り返しの回数を示しています。

参考文献


[1]塩浦昭義, 数理計画法 第13回
[2]Abramowitz and Stegun, “Handbook of Mathematical Functions”, http://people.math.sfu.ca/~cbm/aands/page_883.htm, http://people.math.sfu.ca/~cbm/aands/page_884.htm

反復計算における収束判定について

収束判定


漸化式
\begin{equation}
x^{(k+1)}=x^{(k)}+\Delta^{(k)}
\end{equation}
に従って、\(\lim_{k\to\infty}x^{(k)}=a\)が適当な定数に収束する問題を考えます。
絶対誤差\(\varepsilon_A\)、相対誤差\(\varepsilon_R\)とすると、数値計算を終了する時は、以下の不等式が満たされる時にすると良いです。
\begin{equation}
|x^{(k+1)}-x^{(k)}|\lt \varepsilon_A + \varepsilon_R(|x^{(k)}|+|x^{(k+1)}|)
\end{equation}

判定式の意味


上記判定式の意味を考えましょう。上の判定式は絶対誤差による評価と相対誤差による評価をまとめて評価する式になっています。

相対誤差が重要な場合

本当の解が\(a=100\)であるとします。その時、\(x^{(k)}=100.3, x^{(k+1)}=100.2\)であったと仮定すると、大雑把に計算して
\begin{align}
|x^{(k+1)}-x^{(k)}| &\lt \varepsilon_A + \varepsilon_R(|x^{(k)}|+|x^{(k+1)}|) \\
0.1 &\lt \varepsilon_A + \varepsilon_R\times 100
\end{align}
となります。右辺は本来2倍ですが、2倍はそんなに重要ではないので無視しています。もし仮に、\(\varepsilon\equiv\varepsilon_A=\varepsilon_R\)としていれば、
\begin{align}
0.1 &\lt 100\varepsilon_R
\end{align}
と近似できます。以上から、本当の解が1より十分大きい場合、相対誤差で収束を評価する式になっていると言えます。
つまり上記の問題設定の場合、\(\varepsilon_R\gt 10^{-3}\)にしてあれば”収束した”として判定します。

絶対誤差が重要な場合

本当の解が\(a=0.01000\)であるとします。その時、\(x^{(k)}=0.01002, x^{(k+1)}=0.01001\)であったと仮定すると、大雑把に計算して
\begin{align}
|x^{(k+1)}-x^{(k)}| &\lt \varepsilon_A + \varepsilon_R(|x^{(k)}|+|x^{(k+1)}|) \\
0.00001 &\lt \varepsilon_A + \varepsilon_R\times 0.01
\end{align}
となります。右辺は本来2倍ですが、2倍はそんなに重要ではないので無視しています。もし仮に、\(\varepsilon\equiv\varepsilon_A=\varepsilon_R\)としていれば、
\begin{align}
0.00001 &\lt 0.01\varepsilon_A
\end{align}
と近似できます。以上から、本当の解が1より十分小さい場合、絶対誤差で収束を評価する式になっていると言えます。
つまり上記の問題設定の場合、\(\varepsilon_A\gt 10^{-3}\)にしてあれば”収束した”として判定します。

参考


[1]杉山耕一朗, OBOROの収束判定条件の設定

動かない壁に対する束縛運動と反射

動かない壁に対する束縛運動と反射を考えます。
例えば、初め跳ねてた運動が、壁に沿って動く運動に変化する、という状況です。
あんまり見たことが無いので、面白そうだと思いました。

束縛されている時と反発するときは、式(1),(2)によってあらわされます。それらは
壁に束縛されている場合

壁と反発する場合

です。壁と反発する場合、反発後の速度は式(3)に沿って動きます。

※式(1), (2)では壁が時間依存しても良いように定式化しています。この定式化は恐らく正しいです。また、本稿に載せているプログラムも壁が時間依存しても良いように作成していますが、動く壁の場合、プログラムではうまく計算が出来ません。

ここで、\(e\)は反発係数、\(\mathbf{n}\)は壁の法線ベクトルであり、

\(\nabla\)はナブラ演算子であり

と与えられます。また、\(\hat{H}f\)は関数\(f\)のヘッセ行列であり,

と与えられます。

定式化や数値計算手法の詳細は以下のページを参照してください。

壁との反発と束縛運動の定式化

質点と壁との反発を表す運動方程式
束縛条件下の運動 – 自由度がうまく落とせない運動

数値計算手法

ルンゲ=クッタ法の説明と刻み幅制御
Hyper-dual numbersによる二階偏微分の計算
ゼロ点を探す(二分法、挟み撃ち法、Anderson-Björk法、Brent法、Newton法、Steffensen法)


解放・束縛判定


ここでいう”解放”とは、束縛されていなく、反射を繰り返している状態を表します。また、”束縛”は壁に沿って運動している状態です。

前提として、壁を通り抜けることは無いと考えます。

すなわち、時刻\(t=t_0\)で位置\(\mathbf{r}=\mathbf{r}_0\)の時、任意の時刻\(t\)について

が満たされるとします。
もし、\(f(\mathbf{r}_0,t_0) =0\)ならば判断がつかないため、プラスかマイナスはこちらから与えます。

解放→解放判定、解法→束縛判定


解放状態から壁によって単に反発する場合

関数\(f(\mathbf{r},t)\)の符号が変化した時、壁との反発を考えます。
壁の法線方向の速度が十分に大きい場合、壁と反発し、そうでない場合、壁に束縛されると考えます。
その前提の元、

を満たす\(t=t_i, \mathbf{r}=\mathbf{r}_i\)を見つけます。
その後、式(6)に従い、速度を変化させます。

数値計算的には、衝突直前の時刻を採用します。すなわち、
ゼロ点を探す際にある範囲\(t_a \leq t_i \leq t_b\)で挟み込んでいくのですが、\(t_b\)は壁を越えてしまうので採用しません。

解放状態から壁沿いに束縛される場合

もし、壁の法線方向の速度が十分に小さい場合(ある閾値を下回った場合)、壁に束縛されると考えます。
この時、壁の法線方向の速度はゼロに変更します。
すなわち、速度は時刻\(t=t_i\)で

を持ちますが、\(v_{\parallel}=0\)にしてから、束縛運動に移行するということです。
これは、\(\mathbf{v}\)と壁の法線方向のベクトル\(\mathbf{e}_n\)の内積を取ることで得られます。
また、束縛された瞬間(\(t=t_i\))の束縛力\(C(t_i)\)を計算し、その符号を記録しておきます。
束縛力\(C(t)\)は、

です。
この符号が変化した瞬間が壁からの束縛が無くなる時なので、そのために記録します。

束縛→解放判定

束縛力\(C(t)\)の符号と\(C(t_i)\)の符号が変わるまで、式(1)に従い、時間発展させます。
すなわち、

を満たす\(t=t’, \mathbf{r}=\mathbf{r}’\)を見つけます。
その後、式(2)に従い運動します。
式(2)の運動では束縛力は働かないので、符号は自然と初期条件の符号と同一になる(※)。

※この条件はあまり良くありません。この判別方法のせいで、壁が時間依存している場合、束縛力が働いていない一瞬で質点が壁を超えてしまいます。プログラム自体は壁は時間依存しても良いことになっていますが、この条件分岐は上手く動きません。以下に示す計算プログラムは、質点が束縛されている場合に壁が時間依存しなければ正しいです。

プログラム


プログラムは以下のリンク先においておきます。

https://slpr.sakura.ne.jp/qp/supplement_data/lag_ver1.0.tar.gz

適当なディレクトリで展開し、lag_ver1.0というディレクトリに移動してから以下のコマンドで実行できます。

 $ sh cm.sh
 $ ./a.out
&INPUT
 MASS=  1.0000000000000000     ,
 G=  1.0000000000000000     ,
 TA=  0.0000000000000000     ,
 TB=  20.000000000000000     ,
 NT=        101,
 ELS= 0.59999999999999998     ,
 RX0= -1.0000000000000000     ,
 RY0=  1.0000000000000000     ,
 VX0=  3.0000000000000000     ,
 VY0=  0.0000000000000000     ,
 RKTOL=  1.0000000000000000E-008,
 ZRTOL=  1.0000000000000000E-008,
 TRTOL=  1.0000000000000000E-008,
 REGION=          1,
 /
           0
 $ gnuplot

    G N U P L O T
    Version 4.6 patchlevel 4    last modified 2013-10-02
    Build System: Linux x86_64

    Copyright (C) 1986-1993, 1998, 2004, 2007-2013
    Thomas Williams, Colin Kelley and many others

    gnuplot home:     http://www.gnuplot.info
    faq, bugs, etc:   type "help FAQ"
    immediate help:   type "help"  (plot window: hit 'h')

Terminal type set to 'wxt'
gnuplot> load "anime.plt"
gnuplot>

動かすと以下のような動画が描画されます。

デフォルトでは
ポテンシャル\(V=mgy\)(サブルーチンfp2d)
壁\(f(x,y)=x^2+y^2-4\)(サブルーチンfw2d)
に書かれています。
摩擦、空気抵抗は入っていません。唯一、反発係数(els)だけがinputファイルの中に書かれています。

初期条件の

rx0  = -2d0,  ! Initial condition
ry0  =  2d0,  !     position and velocity
vx0  =  1d0,  !  \mathbf{r} = (rx, ry)
vy0  =  0d0,  !  \mathbf{v} = (vx, vy)

だけを上の通り変更すると以下のような振る舞いをします。

確かめ


確かめを行います。
重力\(g\)の下で、質量\(m\)の物体が、半径\(r\)の円形の壁の内側に沿ってボールが進み、壁からの抗力が無くなり、壁から離れる状況を考えます(下の図を参照)。

円形の壁に沿っている時、垂直抗力\(N\)は

と書けます。エネルギー保存則より、

が成り立っています。ここで、\(v_0\equiv v(t=0)\)と置きました。
垂直抗力\(N\)がゼロになる点が壁から離れる条件ですので、\(v_0\)を用いて

と書けます。初速度が分かっている時、壁から離れる角度は

です。もしくは、壁から離れる角度が分かっている場合、初速度は

と与えられます。重力加速度, 半径を\(g=1, r=2\)とし、\(y=1\)と壁との交点、すなわち\(\theta=2\pi/3\)の場合、初速度は

です。実際に本稿のプログラムを動かしますと、\(y=1\)でちょうど離れていることが確認できます。

ここで、青線は壁に沿って動いて運動している状態であり、赤線は壁から離れている運動している状態です。

剛体に対する散乱

ポテンシャルを無くし(g=0)、弾性散乱(els=1)を考えると
古典的な散乱問題的なものもできます。

RLC並列回路の過渡現象

RLC並列回路を考えます。

良くあるページではインピーダンスを考えるだけでよし、としていますが、過渡現象が知りたいですよね。私はそうです。ですので、ラプラス変換を用いて解いていきましょう。

考える回路は以下の図の通りです。

回路方程式を立てれば、



なります。この式(1)と式(2)の計4本の連立方程式を解いて、未知の関数\(i(t),i_1(t),i_2(t),i_3(t)\)を求めることが目標です。

電圧、電流のラプラス変換を

と書くことにします。式(2)を式(1)に代入して、ラプラス変換を施せば、

を得ます。もっと厳密に書けば、式(1b)のラプラス変換には\(i_2(0^-)\)という項が含まれますが、\(i_2(0^-)=0\), すなわち電源がオンになるまでは電流は存在しないと仮定します。

行列表示にすれば

となります。関数\(F_k(s)\)にとって線形の問題です。表記を簡単にするために、

と書くことにします。ここで、\(x, y, z\)は

を意味します。ただの定数です。
クラメルの公式を用いて式(6)を解きます。関数

を定義すれば、式(6)の解は

と書くことが出来ます。あらわに\(G(x,y,z)\)を書けば

\(\displaystyle
\begin{align}
~~~~~~~~~G(x,y,z)&=xyszs^{-1}+xys+yszs^{-1}+xzs^{-1} ~~~~~~~~~~(10a)\\
&=\frac{xy}{s}\left[s^2+z\Bigl(1+\frac{1}{x}\Bigr)s+\frac{z}{y}\right]~~~~~~~~~~(10b)\\
&=\frac{xy}{s}(s-\alpha)(s-\beta)~~~~~~~~~~(10c)\\
\end{align}\)

と表すことが出来ます。ここで、\(\alpha, \beta\)は

\begin{align}
s^2+z\Bigl(1+\frac{1}{x}\Bigr)s+\frac{z}{y}=(s-\alpha)(s-\beta)~~~~~~~~~~(11)
\end{align}
を満たすような解として書きました。
以降、\(\alpha, \beta\)は同じでは場合を考えていきます。
具体的に式(9)に現れる量を計算していけば、

となります。それぞれ、



という結果が得られます。ここにいたるまでに、



という関数を定義しました。
すると、それぞれの電流は

と書くことができ、具体的に

と求められます。
この結果を導くにあたって使用した仮定をまとめますと、
1. \(\alpha\ne \beta\)
2. \(i(t\lt 0)=0\)
という仮定の下、導き出された結果です。

導いた式(20)が合っているのか、直流電圧源を考えて考察してみましょう。
直流電圧源が時刻\(t=t_0\geq 0\)にスイッチオンする場合、電圧は

と書くことが出来ます。式(20)に出てくる積分は

と計算できますので、それぞれの素子を流れる電流は

と書きあらわすことが出来ます。
では、\(t\to\infty\)の漸近形を考えてみましょう。
\(t\to\infty\)の振る舞いは指数関数の型の\(t\)にかかる係数の実部の符号によって支配されます。式(11)より、具体的に\(\alpha, \beta\)を書くと

と求められます。今、\(R, C, L\)は全て正の実数ですので、ルートの項がその前の項の絶対値よりも大きくなることはありません。
よって、\(\text{Re}(\alpha)\lt 0, \text{Re}(\beta)\lt 0\)が導けます。すなわち、

という結果です。
これを式(23)に代入すれば、

という結果が得られます。
電源が入った後に系が十分に落ち着いた定常状態では、コンデンサーは開放、コイルは短絡されたものと見なしてよいので、その結果に見合った振る舞いであることが分かります。

メモとして書いておきますが、式(11)を計算するにあたって\(\alpha \beta\)を計算する必要があります。これは式(24)から求めるのではなく、式(11)の右辺を展開して、\(s^0\)の項を比較すれば

という結果が得られます。

ラプラス変換による回路方程式の解

  1. 回路方程式の組立
  2. ラプラス変換、畳み込み、超関数の積分について
  3. RC直列回路
  4. RL直列回路
  5. RLC直列回路

回路方程式の組立


閉じた回路があって、既知の電圧源が与えられたとき、回路に流れる電流を考えます。
色々考えた結果、間違えないようにするためにキルヒホッフの法則を信じて、
以下のように組み立てれば良いと思いました。

キルヒホッフの法則
閉じた回路内の起電力の和が、閉回路内の電圧降下の和に等しい
という法則を考えます。

ですが、起電力と電圧降下を別に考える、すなわち符号を取り換えるのは良く分かりません。
本稿では、上の起電力と電圧降下を区別しないことにします。
すなわち、キルヒホッフの法則を単純に
\(\displaystyle
\sum_k E_k =0
\)

と書くことにします。
素子の電圧降下分\(E_k\)はそれぞれ

で表されるとします。

電圧源の符号は、考えた閉経路の方向(電流の方向)に沿って電位が上がる場合は正、
反対方向に沿う場合は負として考えます。
素子の電圧降下は何も考えずに、上の表に対応する電圧降下分を書けば良いです。

(補足)回路方程式と運動方程式の対応

回路方程式とニュートンの運動方程式は物理的には全く異なりますが、数学的に似ています。すなわち

電荷\(q(t)\)は、質点の位置\(x(t)\)に対応し、
電流\(i(t)\)は、質点の速度\(v(t)\)に対応、
電圧\(v(t)\)(電位差)は、保存力\(F(t)\)に対応するとみることが出来ます。

ですが、キルヒホッフの法則は、古典力学で対応する法則がありません。
強いていうのであれば、元の位置に戻ってきたときに電位差がゼロなわけですから、
回路方程式は必ず保存力である、もしくはポテンシャルが必ず定義できなければならない、みたいなことでしょうか?
あんまりしっくりきません。

テレゲンの法則と呼ばれる法則が元にあると考えるのが良さそうです。これは、
\(\displaystyle
\sum_k E_k i_k =0
\)

と書かれるので、電圧と電流の積、すなわち単位時間当たりのエネルギー保存則に対応するわけです。
誤解を恐れず、単なるイメージ的に言えば、テレゲンの法則はハミルトニアン的な物として捉えることが出来るでしょう。

ラプラス変換、畳み込み、超関数の積分について


本稿では、ある時刻\(t\)から突然電源が入る、とかそういう現象を扱いたいので、
ラプラス変換を用いて回路方程式を解いていきます。

詳細は述べませんが、関数\(f(t)\)のラプラス変換\(F(s)\)を\(\mathcal{L}[f(t)](s)\)、逆ラプラス変換は\(\mathcal{L}^{-1}[F(s)](t)\)と書くと、

と定義されます。ラプラス変換は変換表を用いて計算することが殆どなので、詳細について知らなくても良いでしょう。
別の言い方をすれば、初等的な範囲でラプラス変換表に乗ってない関数場合、数値的に解くしかないと考えても良いです。

ラプラス変換表はLaplace transform -wikipedia や、wolfram のlaplace transform of exp(-a*t) -wolfram alphaで調べれば良いでしょう。逆変換もinverseとか付け加えれば可能です。

関数同士の掛け算の逆ラプラス変換は畳み込みと関係しています。なので、畳み込みも書いておきましょう。

また、ヘヴィサイド関数\(\theta(t)\)とディラックのデルタ関数\(\delta(t)\)を含んだ積分も頻出しますので、書いておきます。必要になったら参照してください。

RC直列回路


キルヒホッフの法則から出発して、RC直列回路を考えます。

回路方程式は

と書けます。この方程式を解いて、電流\(i(t)\)を求める事が目標です。

辺々にラプラス変換を施せば、

を得ます。ここで、

と置きました。式(2)を\(F(s)\)について解けば、

と書くことが出来ます。ここで、\(f\ast g(x)=\int_{0^-}^t f(t-\tau)g(\tau)d\tau\)は関数\(f(t)\)と\(g(t)\)の畳み込みを表し、さらに

を定義しました。式(4c)に現れる\(w(t)\)を求めるには、\(W(s)\)の逆ラプラス変換を行えばよいです。計算すれば、

となります。ここで、\(\delta(x)\)はディラックのデルタ関数、\(\theta(x)\)はヘヴィサイド関数です。

式(4d)の両辺を逆ラプラス変換を施すことで、電流\(i(t)\)を得ることが出来ます。計算すれば、

となり、求めたかった電流のあらわな式(7e)が得られます。
ここに至るまで、電圧源が直流であるとか、交流であるとかそういう条件は用いていません。

直流電圧源の場合


直流電圧源を考えます。状況としては、時刻\(t=t_0\)にスイッチがオンになり、電圧\(E_0\)の定電圧が掛かり、電流が流れ始めるという状況を考えます。
すると、電圧源\(v(t)\)は
\(\displaystyle
v(t)=E_0\theta(t-t_0)
\)
と書くことが出来ます。実際に式(7e)に代入して、

となります。もしも\(t_0>0\)ならば、

という結果を得ます。抵抗\(R\), キャパシタンス\(C\)は正ですので、\(t\to\infty\)の漸近で電流は流れなくなることが分かります。

RL直列回路


続いて、RL直列回路を考えます。

回路方程式は

と書くことが出来ます。式(10)の両辺にラプラス変換を施せば

を得ます。\(F(s)\)は\(i(t)\)のラプラス変換を意味しています。\(F(s)\)について解き、RC回路の時と同様に\(i(t)\)を求めれば、

と書くことが出来ます。すなわち電流\(i(t)\)は式(12c)に逆ラプラス変換を施して

と得ることが出来ます。具体的に\(w(t)\)について解けば、

と解けてしまうので、

と解を求める事が出来ます。

直流電圧源の場合


RC回路の時と同様に直流電圧源を考えます。電圧源\(v(t)\)は
\(\displaystyle
v(t)=E_0\theta(t-t_0)
\)
と書くことが出来ます。式(15)に代入して、

と書けます。抵抗\(R\), インダクタンス\(L\)は正ですので、\(t\to\infty\)の漸近でコイルの影響はなくなる、ということを表しています。

RLC直列回路


最後に、RLC直列回路を考えます。

回路方程式は

です。今までと同様に、ラプラス変換を施して

を得ます。\(i(t)\)のラプラス変換である\(F(s)\)について解けば、

を得ますので、\(i(t)\)は

と書けます。\(w(t)\)は

と書くことが出来ます。
これから、\(w(t)\)を具体的に求めるのですが、(式(21b)の括弧内の分母)=0が異なる2つの解を持つか、重解を持つかで場合分けをしなければなりません。
まず、異なる2つの解を持つ場合について計算し、その後、重解を持つ場合について考えましょう。

\(s^2+\frac{R}{L}s+\frac{1}{CL}=(s-\alpha)(s-\beta),~~\alpha\ne \beta\)の場合


(式(21b)の分母)=0 が異なる2つの解\(\alpha, \beta\)を持つ場合を考えます。すると、\(W(s)\)は

と部分分数分解することが出来ます。ここで、式が長くなるので\(x=\frac{\alpha}{\alpha-\beta},~~y=\frac{-\beta}{\alpha-\beta}\)と置きました。

すると、式(21b)は計算できて、

となります。
よって、電流\(i(t)\)は

と求められます。

\(s^2+\frac{R}{L}s+\frac{1}{CL}=(s-\alpha)^2\)の場合


続いて、(式(21b)の分母)=0 が重解\(\alpha\)を持つ場合を考えます。すると、\(W(s)\)は

と部分分数分解することが出来ます。
よって、\(w(t)\)は

と求められるので、電流\(i(t)\)は

と求める事が出来ました。

Cubic補間

多項式補間です。

Cubic補間 (1次元)


4点を厳密に通る3次多項式によって関数を補間します。
概要は以下の図の通りです。
補間したい点よりも小さいデータ点を2点、大きいデータ点を2点使って補間します。
いわば小さい区間で区切ったラグランジュ補間です。
補間は,既知の係数\(a_{i}\)を用いて関数
\(\displaystyle
g(x)=\sum_{i=0}^{3}a_{i}x^i
\)

誤差は\(O(h^4)\)です。

Fortranプログラムはこちら。

program main
  implicit none
  integer::i,N,M
  double precision::x,f,df,df2,s
  double precision,allocatable::xdata(:),fdata(:)
  double precision::pi=dacos(-1d0)
  double precision,external::cubic
 
  N=30
  M=100
  allocate(xdata(0:N),fdata(0:N))
  xdata=0d0
  fdata=0d0
 
  do i=0,N
     xdata(i)=dble(i)*0.1d0*pi
     fdata(i)=sin(xdata(i))
     write(10,*)xdata(i),fdata(i)
  enddo
 
  ! Cubic-spline interpolation given position as point
  do i=0,M
     x=dble(i)*0.03d0*pi-1d0
     write(11,'(2e20.7e2)')x,cubic(x,N,xdata,fdata)
  enddo
 
  stop
end program main

double precision function cubic(x,N,x0,f0)
  implicit none
  integer,intent(in)::N
  double precision,intent(in)::x,f0(0:N),x0(0:N)
 
  integer::i,i0,i1,i2,i3
  double precision::tx  
  double precision::a,b,c,d,p,q,r,s,t,u

  tx = x-x0(0)
  i1 = 0
  do i=1,N-2
     tx = x-x0(i)
     if(tx.gt.0d0)then
        i1 = i
     else
        exit
     endif
  enddo
  if(i1.eq.0)i1=1
  i0=i1-1
  i2=i1+1
  i3=i1+2

  a = x-x0(i0)
  b = x-x0(i1)
  c = x-x0(i2)
  d = x-x0(i3)  
 
  p = x0(i1)-x0(i0)
  q = x0(i2)-x0(i1)
  r = x0(i3)-x0(i2)

  s = x0(i2)-x0(i0)
  t = x0(i3)-x0(i1)

  u = x0(i3)-x0(i0)

  cubic = a*c*( d*f0(i1)/(p*q) + b*f0(i3)/(u*r) )/t &
        - b*d*( c*f0(i0)/(p*u) + a*f0(i2)/(q*r) )/s
 
  return
end function cubic

Bicubic補間 (2次元)


16点を厳密に通る多項式によって関数を補間します。
補間したい点を囲むように存在する16点を用いて補間します。
補間は,既知の係数\(a_{i,j}\)を用いて関数
\(\displaystyle
g(x,y)=\sum_{i=0}^{3}\sum_{j=0}^{3}a_{i,j}x^i y^j
\)

によって行います[1]。誤差は\(O(h^4)\)です。

Fortranプログラムはこちら。

program main
  implicit none
  integer::i,j
  integer::Nx,Ny
  double precision::x,y
  double precision::xa,xb,ya,yb
  double precision,allocatable::x0(:),y0(:),z0(:,:)
  double precision,external::f,ip16

  Nx=20
  Ny=20
  allocate(x0(0:Nx), y0(0:Ny), z0(0:Nx,0:Ny))

  xa=-3d0
  xb= 3d0
  ya=-3d0
  yb= 3d0
   
  ! generate references points
  do i=0,Nx
     x0(i)=dble(i)*(xb-xa)/Nx+xa
  enddo
  do j=0,Ny
     y0(j)=dble(j)*(yb-ya)/Ny+ya
  enddo
  do i=0,Nx
     do j=0,Ny
        z0(i,j)=f(x0(i),y0(j))
        write(10,*)x0(i),y0(j),z0(i,j)
     enddo
     write(10,*)
  enddo
 
  ! Return interpolated results
  do i=0,50
     do j=0,50
        x=i*0.03d0-1d0
        y=j*0.03d0-1d0
        write(11,*)x,y,ip16(x,y,Nx,Ny,x0,y0,z0)
     enddo
     write(11,*)
  enddo

  stop
end program main

function f(x,y)
  implicit none
  double precision::f
  double precision,intent(in)::x,y
 
  f=x*y*exp(-x*x-y*y)
 
  return
end function f

double precision function ip16(x,y,Nx,Ny,x0,y0,z0)
  implicit none
  integer,intent(in)::Nx,Ny
  double precision,intent(in)::x,y,x0(0:Nx),y0(0:Ny),z0(0:Nx,0:Ny)
  !
  ! Bicubic interpolation
  !   x0,y0 are sorted by ascending order,
  !   suppose x0 & y0 are equal interval.
  !   z0(x0,y0)
  !
  integer::i,j
  integer::i0,i1,i2,i3
  integer::j0,j1,j2,j3
  double precision::tx,u,u2,u3
  double precision::ty,v,v2,v3
  double precision::a00,a01,a02,a03,a10,a11,a12,a13
  double precision::a20,a21,a22,a23,a30,a31,a32,a33
  double precision::p(0:3,0:3)
 
  if(Nx.le.3.or.Ny.le.3)then
     write(6,*)" *** Error at ip16"
     stop
  endif
 
  tx = x-x0(0)
  i1 = 0
  do i=1,Nx-2
     tx = x-x0(i)
     if(tx.gt.0d0)then
        i1 = i
     else
        exit
     endif
  enddo
  if(i1.eq.0)i1=1
  i0=i1-1
  i2=i1+1
  i3=i1+2

  ty = y-y0(0)
  j1 = 0
  do j=1,Ny-2
     ty = y-y0(j)
     if(ty.gt.0d0)then
        j1 = j
     else
        exit
     endif
  enddo
  if(j1.eq.0)j1=1
  j0=j1-1
  j2=j1+1
  j3=j1+2

 
  p(0,0) = z0(i0,j0)
  p(0,1) = z0(i0,j1)
  p(0,2) = z0(i0,j2)
  p(0,3) = z0(i0,j3)
  p(1,0) = z0(i1,j0)
  p(1,1) = z0(i1,j1)
  p(1,2) = z0(i1,j2)
  p(1,3) = z0(i1,j3)
  p(2,0) = z0(i2,j0)
  p(2,1) = z0(i2,j1)
  p(2,2) = z0(i2,j2)
  p(2,3) = z0(i2,j3)
  p(3,0) = z0(i3,j0)
  p(3,1) = z0(i3,j1)
  p(3,2) = z0(i3,j2)
  p(3,3) = z0(i3,j3)
   
  a00 = p(1,1)
  a01 = -0.5d0*p(1,0) + 0.5d0*p(1,2)
  a02 = p(1,0) - 2.5d0*p(1,1) + 2*p(1,2) - 0.5d0*p(1,3)
  a03 = -0.50d0*p(1,0) + 1.50d0*p(1,1) - 1.50d0*p(1,2) + 0.5d0*p(1,3)
  a10 = -0.50d0*p(0,1) + 0.50d0*p(2,1)
  a11 =  0.25d0*p(0,0) - 0.25d0*p(0,2) - 0.25d0*p(2,0) + 0.25d0*p(2,2)
  a12 = -0.50d0*p(0,0) + 1.25d0*p(0,1) - p(0,2) + 0.25d0*p(0,3) &
       + 0.50d0*p(2,0) - 1.25d0*p(2,1) + p(2,2) - 0.25d0*p(2,3)
  a13 =  0.25d0*p(0,0) - 0.75d0*p(0,1) + 0.75d0*p(0,2) - 0.25d0*p(0,3) &
       - 0.25d0*p(2,0) + 0.75d0*p(2,1) - 0.75d0*p(2,2) + 0.25d0*p(2,3)
  a20 = p(0,1) - 2.5d0*p(1,1) + 2.d0*p(2,1) - 0.5d0*p(3,1)
  a21 = -0.50d0*p(0,0) + 0.5d0*p(0,2) + 1.25d0*p(1,0) - 1.25d0*p(1,2) &
       - p(2,0) + p(2,2) + 0.25d0*p(3,0) - 0.25d0*p(3,2)
  a22 =  p(0,0) - 2.5d0*p(0,1) + 2.0d0*p(0,2) - 0.50d0*p(0,3) &
       - 2.5d0*p(1,0) + 6.25d0*p(1,1) - 5d0*p(1,2) + 1.25d0*p(1,3) &
       + 2.0d0*p(2,0) -  5.0d0*p(2,1) + 4d0*p(2,2) - p(2,3) &
       - 0.50d0*p(3,0) + 1.25d0*p(3,1) - p(3,2) + 0.25d0*p(3,3)
  a23 = -0.50d0*p(0,0) + 1.50d0*p(0,1) - 1.50d0*p(0,2) + 0.5d0*p(0,3) &
       + 1.25d0*p(1,0) - 3.75d0*p(1,1) + 3.75d0*p(1,2) - 1.25d0*p(1,3) &
       - p(2,0) + 3*p(2,1) - 3*p(2,2) + p(2,3) &
       + 0.25d0*p(3,0) - 0.75d0*p(3,1) + 0.75d0*p(3,2) - 0.25d0*p(3,3)
  a30 = -0.50d0*p(0,1) + 1.50d0*p(1,1) - 1.50d0*p(2,1) + 0.5d0*p(3,1)
  a31 =  0.25d0*p(0,0) - 0.25d0*p(0,2) - 0.75d0*p(1,0) + 0.75d0*p(1,2) &
       + 0.75d0*p(2,0) - 0.75d0*p(2,2) - 0.25d0*p(3,0) + 0.25d0*p(3,2)
  a32 = -0.50d0*p(0,0) + 1.25d0*p(0,1) - p(0,2) + 0.25d0*p(0,3) &
       + 1.50d0*p(1,0) - 3.75d0*p(1,1) + 3*p(1,2) - 0.75d0*p(1,3) &
       - 1.50d0*p(2,0) + 3.75d0*p(2,1) - 3*p(2,2) + 0.75d0*p(2,3) &
       + 0.50d0*p(3,0) - 1.25d0*p(3,1) + p(3,2) - 0.25d0*p(3,3)
  a33 =  0.25d0*p(0,0) - 0.75d0*p(0,1) + 0.75d0*p(0,2) - 0.25d0*p(0,3) &
       - 0.75d0*p(1,0) + 2.25d0*p(1,1) - 2.25d0*p(1,2) + 0.75d0*p(1,3) &
       + 0.75d0*p(2,0) - 2.25d0*p(2,1) + 2.25d0*p(2,2) - 0.75d0*p(2,3) &
       - 0.25d0*p(3,0) + 0.75d0*p(3,1) - 0.75d0*p(3,2) + 0.25d0*p(3,3)
 
  ! Parallel translation u,v [0:1]
  u  = (x-x0(i1))/(x0(i2)-x0(i1))
  v  = (y-y0(j1))/(y0(j2)-y0(j1))
  u2 = u*u
  u3 = u2*u
  v2 = v*v
  v3 = v2*v
  ip16=  (a00 + a01 * v + a02 * v2 + a03 * v3)      &
       + (a10 + a11 * v + a12 * v2 + a13 * v3) * u  &
       + (a20 + a21 * v + a22 * v2 + a23 * v3) * u2 &
       + (a30 + a31 * v + a32 * v2 + a33 * v3) * u3

  return
end function ip16

補間 (2次元)


Bicubic補間の低次元バージョンです。
補間したい点を囲むように存在する4点を用いて補間します。
一応Four point Formulaと名づけられています[2]。
補間は,既知の係数\(a_{i,j}\)を用いて関数
\(\displaystyle
g(x,y)=\sum_{i=0}^{1}\sum_{j=0}^{1}a_{i,j}x^i y^j
\)

によって行います[2]。誤差は\(O(h^2)\)です。

function ip4(x,y,Nx,Ny,x0,y0,z0)
  implicit none
  integer,intent(in)::Nx,Ny
  double precision,intent(in)::x,y,x0(1:Nx),y0(1:Ny),z0(1:Nx,1:Ny)
  double precision::ip4
  !
  ! 2D Interpolation, 4-point formula,
  !           xy equal distance grid
  !
  integer::i,ix0,ix1,iy0,iy1
  double precision::tx,ty,dx,dy,p,q

  tx=1d100
  ix0=1
  do i=2,Nx
     if(abs(x-x0(i)).le.tx)then
        tx=abs(x-x0(i))
        ix0=i
     endif    
  enddo
  tx=1d100
  ix1=1
  do i=2,Nx
     if(i.ne.ix0)then
        if(abs(x-x0(i)).le.tx)then
           tx=abs(x-x0(i))
           ix1=i
        endif
     endif
  enddo

  ty=1d100
  iy0=1
  do i=2,Ny
     if(abs(y-y0(i)).le.ty)then
        ty=abs(y-y0(i))
        iy0=i
     endif    
  enddo
  ty=1d100
  iy1=1
  do i=2,Ny
     if(i.ne.iy0)then
        if(abs(y-y0(i)).le.ty)then
           ty=abs(y-y0(i))
           iy1=i
        endif
     endif
  enddo
 
  dx=x0(ix1)-x0(ix0)
  dy=y0(iy1)-y0(iy0)
  p=(x-x0(ix0))/dx
  q=(y-y0(iy0))/dy
 
  ip4=(1d0-p)*(1d0-q)*z0(ix0,iy0) &
     +p*(1d0-q)*z0(ix1,iy0) &
     +(1d0-p)*q*z0(ix0,iy1) &
     +p*q*z0(ix1,iy1)
   
  return
end function ip4

参考文献


[1]Cubic interpolation
[2]Abramowitz and Stegun.
Handbook of Mathematical Functions.

LU分解による連立一次方程式の解法

数値計算ライブラリLapackを使って線形連立一次方程式
\(
\begin{equation}
A\mathbf{x}=\mathbf{b}
\end{equation}
\)

をLU分解を用いて数値的に解くプログラムを載せます。

詳しくは説明しませんが、逆行列を用いて解く方法と比べ、
行列が疎である時(行列要素にゼロが多い時)に比較的計算量が減らせます。
また、逆行列を求める際に生じる桁落ちの問題を回避することが出来ます。

解法


連立一次方程式
\(
\begin{equation}
A\mathbf{x}=\mathbf{b}
\end{equation}
\)

を行列\(A\)のLU分解を利用して解きます。
ここで、未知なのはベクトル\(\mathbf{x}\)、既知なのは行列\(A\)とベクトル\(\mathbf{b}\)です。

\(A\)がLU分解されているとします。
すなわち、行列\(A\)に適当な操作を施して、

\(
\begin{align}
A=LU
\end{align}
\)

と書けているとします。ここで、行列\(L, U\)は下三角行列、上三角行列であり、
\(
\begin{align}
L=
\left(
\begin{array}{*4{>{\displaystyle}c}}
1&0&\cdots &0 \\[1em]
l_{2,1}&1&\cdots &0 \\[0.5em]
\vdots&\vdots&\ddots&\vdots \\[0.3em]
l_{s,1}&l_{s,2}&\cdots &1
\end{array}
\right) ,~~~
U=
\left(
\begin{array}{*4{>{\displaystyle}c}}
u_{1,1}&u_{1,2}&\cdots &u_{1,s} \\[1em]
0&u_{2,2}&\cdots &u_{2,s} \\[0.5em]
\vdots&\vdots&\ddots&\vdots \\[0.3em]
0 & 0 &\cdots &u_{s,s}
\end{array}
\right)
\end{align}
\)

という行列です。
もし、行列\(A\)がLU分解されていれば、

\(
\begin{align}
& A\mathbf{x}=\mathbf{b} \\
& LU\mathbf{x} = \mathbf{b} \\
& L\mathbf{w} = \mathbf{b}~~~…(*)
\end{align}
\)

と書けます。ここで、\(\mathbf{w} \equiv U\mathbf{x}\)と置きました。
まず、式(*)を解いて、\(\mathbf{w}\)を求めます。これは、下三角行列に対する問題なので、前進代入を用いて簡単に解くことが出来ます。

続いて、
\(
U\mathbf{x}=\mathbf{w}
\)

を後退代入を利用して解\(\mathbf{x}\)を求めます。

以上がLU分解を利用して連立一次方程式を解く方法です。
数値計算のルーチンは大きく2つのステップに分かれており、

  1. 行列\(A\)のLU分解を求める(ルーチン dgetrf)
  2. LU分解された行列\(A\)を用いて連立一次方程式を解く(ルーチン dgetrs)

というステップです。
これは、例えば問題
\(
\begin{equation}
A\mathbf{x}=\mathbf{b}
\end{equation}
\)


\(
\begin{equation}
A\mathbf{x}=\mathbf{b’}
\end{equation}
\)

の右辺だけが変わる複数の問題を解きたい場合なんかに便利です。この問題の場合、行列\(A\)は変わらないため、LU分解した結果を両方の問題に流用することが出来るのです。

プログラム


プログラムは以下の通りです。
いつもと同じように、ワーク配列を減らす為だけに導入したルーチンを挟んでいます。

program main
  implicit none
  integer::i,N
  double precision,allocatable::a(:,:),b(:),x(:)
  integer,allocatable::ipiv(:)
 
  N=3
  allocate(a(1:N,1:N))
  allocate(ipiv(1:N),b(1:N),x(1:N))
  a(1,1:N)=(/1d0,3d0,5d0/)
  a(2,1:N)=(/0d0,3d0,1d0/)
  a(3,1:N)=(/6d0,2d0,5d0/)
  b(1:N)=(/33d0,10d0,66d0/)

  do i=1,N
     write(6,'(3f10.5)')a(i,1:N)
  enddo

  call LUfact(N,a,ipiv)
  call axbsolve(N,a,ipiv,b,x)

  write(6,*)"---- solution ----"
  do i=1,N
     write(6,'(2f10.5)')x(i)
  enddo
 
  stop
end program main

subroutine LUfact(N,LU,ipiv)
  implicit none
  integer,intent(in)::N
  double precision,intent(inout)::LU(1:N,1:N)
  integer,intent(out)::ipiv(1:N)  

  ! LU factorization for lapack
  ! Overwrite matrix LU by factorized LU matrix
 
  integer::m,lda,info

  m=N
  lda=N
  call dgetrf(m,N,LU,lda,ipiv,info)
  if(info.ne.0)then
     write(6,*)"**error at LUlapack"
     stop
  endif
 
  return
end subroutine LUfact

subroutine axbsolve(N,LU,ipiv,b,x)
  implicit none
  integer,intent(in)::N
  integer,intent(in)::ipiv(1:N)
  double precision,intent(in)::LU(1:N,1:N),b(1:N)
  double precision,intent(out)::x(1:N)
  !
  ! Solve simultaneous equations by Lapack
  !
  double precision,allocatable::bl(:,:)
  integer::nrhs,lda,ldb,info
  character(1)::trans  
 
  nrhs=1
  trans='N'
  allocate(bl(1:N,1:nrhs))  
  bl(1:N,1)=b(1:N)
  lda=N
  ldb=N
  call dgetrs(trans,N,nrhs,LU,lda,ipiv,bl,ldb,info)
  if(info.ne.0)then
     write(6,*)"**error at LUlapack"
     stop
  endif
 
  x(1:N)=bl(1:N,1)  

  return
end subroutine axbsolve

上のプログラムをlapackと一緒にコンパイルして動かすと、端末上で

> ./a.out
   1.00000   3.00000   5.00000
   0.00000   3.00000   1.00000
   6.00000   2.00000   5.00000
 -------------
   7.00000
   2.00000
   4.00000
>

という結果が得られるかと思います。
これは、連立一次方程式
\(
\begin{align}
\left(
\begin{array}{ccc}
1& 3& 5 \\
0& 3& 1 \\
6& 2& 5
\end{array}
\right)
\left(
\begin{array}{ccc}
x \\
y \\
z
\end{array}
\right)
=
\left(
\begin{array}{ccc}
33 \\
10 \\
66
\end{array}
\right)
\end{align}
\)
を解いた結果です。
解析解(\(x=7, y=2, z=4\))は、例えばwolframを利用して求められます。
{{1,3,5},{0,3,1},{6,2,5}}.{x,y,z}=={33,10,66} –wolfram alpha