Cauchyの積分定理というのは以下の通りです。
下図はZIPでダウンロードできるcurve2num.xlsmのSheet2になりますが、今回用いる積分経路はこの閉曲線\(C\)です。「4.1 フリーハンド曲線の頂点の座標取得」で紹介したVBAマクロ「Sub 第1曲線頂点()」をそのまま使って頂点の座標を取得しています。ここでは、被積分関数
画像を新しいタブで開くと高解像度で見ることができます。
式\eqref{e}の \(f(z)\) は原点においてのみ微分できません。従って、内側に原点を含まない閉曲線\(C\)に沿って積分すれば、結果は0になるはずです。この複素数値積分をするPythonプログラムは以下の通りです。基本的には「4.2 取得した座標に沿った数値積分」にあるcplx_intgrl_2.pyと同じ構成ですが、積分経路が一つだけなので変数mに対するループはありません。それ以外の変更点は、4行目でsheet2を参照しており、21行目の被積分関数が式\eqref{e}になっていることです。26行目にある計算結果を見ると有効数字8~9桁で0 + 0jとなっており、Cauchyの積分定理通りになっています。
コードの表示にはPrism.jsを使用しています。
import openpyxl
wb = openpyxl.load_workbook('(path to the xlsm file)/curve2num.xlsm')
sheet = wb['Sheet2']
i0 = 12
div = 1000
vertNum = sheet.cell(row=1, column=i0+2).value
vertArX = []
vertArY = []
for n in range(vertNum):
vertArX.append(sheet.cell(row=n+3, column=i0+1).value)
vertArY.append(sheet.cell(row=n+3, column=i0+2).value)
Sum = 0. + 0.j
for k in range(vertNum - 1):
z1 = complex(vertArX[k], vertArY[k])
z2 = complex(vertArX[k+1], vertArY[k+1])
for p in range(div):
z3 = z1 + (z2 - z1) * (0.5 + p) / div
Sum += (1 / z3) * (z2 - z1) / div
print(f"{Sum.real:.10f} + {Sum.imag:.10f}j")
# ----- Output -----
# -0.0000000096 + -0.0000000359j
Cauchyの積分公式というのは以下の通りです。
積分経路には5節の閉曲線\(C\)を用いるので、その内側にある点を選んで \(z_{0} = -1 + 1 \, i\) とします。\(f(z)\) には3節と4節で用いた式\eqref{h}を用いますが、ここに再掲します。
import openpyxl
import math
wb = openpyxl.load_workbook('(path to the xlsm file)/curve2num.xlsm')
sheet = wb['Sheet2']
i0 = 12
div = 1000
vertNum = sheet.cell(row=1, column=i0+2).value
vertArX = []
vertArY = []
for n in range(vertNum):
vertArX.append(sheet.cell(row=n+3, column=i0+1).value)
vertArY.append(sheet.cell(row=n+3, column=i0+2).value)
Sum = 0. + 0.j
z0 = complex(-1., 1.)
for k in range(vertNum - 1):
z1 = complex(vertArX[k], vertArY[k])
z2 = complex(vertArX[k+1], vertArY[k+1])
for p in range(div):
z3 = z1 + (z2 - z1) * (0.5 + p) / div
Sum += (pow(z3, 2) + 3 * z3 + 1) / (z3 - z0) * (z2 - z1) / div
print(f"Integral: {Sum.real:.10f} + {Sum.imag:.10f}j")
# Calculation for 2πj∙f(z0)
dpj_fz0 = complex(0., 2. * math.pi) * (pow(z0, 2) + 3 * z0 + 1)
print(f"2πj∙f(z0): {dpj_fz0.real:.10f} + {dpj_fz0.imag:.10f}j")
# ----- Output -----
# Integral: -6.2831852832 + -12.5663706052j
# 2πj∙f(z0): -6.2831853072 + -12.5663706144j
Cauchyの積分公式から導かれるGoursatの定理についても数値積分で確認します。Goursatの定理は以下の通りです。\(n = 0\) の時がCauchyの積分公式です(拡張された階乗の定義で \(0 \, ! = 1\))。
確認は1階微分などとケチ臭いことを言わず、2階微分にしてみます。
import openpyxl
import math
wb = openpyxl.load_workbook('(path to the xlsm file)/curve2num.xlsm')
sheet = wb['Sheet2']
i0 = 12
div = 1000
vertNum = sheet.cell(row=1, column=i0+2).value
vertArX = []
vertArY = []
for n in range(vertNum):
vertArX.append(sheet.cell(row=n+3, column=i0+1).value)
vertArY.append(sheet.cell(row=n+3, column=i0+2).value)
Sum = 0. + 0.j
z0 = complex(-1., 1.)
for k in range(vertNum - 1):
z1 = complex(vertArX[k], vertArY[k])
z2 = complex(vertArX[k+1], vertArY[k+1])
for p in range(div):
z3 = z1 + (z2 - z1) * (0.5 + p) / div
Sum += (pow(z3, 2) + 3 * z3 + 1) / pow((z3 - z0), 3) \
* (z2 - z1) / div
print(f"Integral: {Sum.real:.10f} + {Sum.imag:.10f}j")
# Calculation for 2πj
dpj = complex(0., 2. * math.pi)
print(f"2πj : {dpj.real:.10f} + {dpj.imag:.10f}j")
# ----- Output -----
# Integral: 0.0000001012 + 6.2831854978j
# 2πj : 0.0000000000 + 6.2831853072j
留数定理というのは以下の通りです。
まずは留数を式\eqref{k}の周回積分で計算して、全体の周回積分が個々の周回積分の和になるということを確かめてみます。被積分関数は複数の特異点を持つ必要があるので
画像を新しいタブで開くと高解像度で見ることができます。
実際に周回積分を行うプログラムは以下のようになっています。10行目でSheet2を参照し、11~25行目で閉曲線\(C\)に沿った式\eqref{l}の積分を計算しています。28行目ではSheet3を参照し、30~47行目で閉曲線\(C_{1}\), \(C_{2}\)に沿った積分を計算します。後者では閉曲線が2つあるので「4.2 取得した座標に沿った数値積分」のcplx_intgrl_2.pyの時のように変数mによるforループを使います。後者の2つの積分値は48行目でSumArというリストに格納し、49~50行目でその和を計算して表示します。53行目のC:と56行目のC1+C2:を比較すると、小数点以下7桁まで一致しています。
import openpyxl
import math
import cmath
wb = openpyxl.load_workbook('(path to the xlsm file)/curve2num.xlsm')
i0 = 12
div = 1000
# Cに沿った周回積分を計算
sheet = wb['Sheet2']
vertNum = sheet.cell(row=1, column=i0+2).value
vertArX = []
vertArY = []
for n in range(vertNum):
vertArX.append(sheet.cell(row=n+3, column=i0+1).value)
vertArY.append(sheet.cell(row=n+3, column=i0+2).value)
Sum = 0. + 0.j
for k in range(vertNum - 1):
z1 = complex(vertArX[k], vertArY[k])
z2 = complex(vertArX[k+1], vertArY[k+1])
for p in range(div):
z3 = z1 + (z2 - z1) * (0.5 + p) / div
Sum += z3 * cmath.exp(math.pi * z3) / (pow(z3, 4) - 16.) \
* (z2 - z1) / div
print(f"C: {Sum.real:.10f} + {Sum.imag:.10f}j")
# C1, C2に沿った周回積分を計算
sheet = wb['Sheet3']
SumAr = []
for m in range(2):
col_m1 = i0 + m * 2 + 1
col_m2 = i0 + m * 2 + 2
vertNum = sheet.cell(row=1, column=col_m2).value
vertArX = []
vertArY = []
for n in range(vertNum):
vertArX.append(sheet.cell(row=n+3, column=col_m1).value)
vertArY.append(sheet.cell(row=n+3, column=col_m2).value)
Sum = 0. + 0.j
for k in range(vertNum - 1):
z1 = complex(vertArX[k], vertArY[k])
z2 = complex(vertArX[k+1], vertArY[k+1])
for p in range(div):
z3 = z1 + (z2 - z1) * (0.5 + p) / div
Sum += z3 * cmath.exp(math.pi * z3) / (pow(z3, 4) - 16.) \
* (z2 - z1) / div
print(f"C{m + 1}: {Sum.real:.10f} + {Sum.imag:.10f}j")
SumAr.append(Sum)
Sum = sum(SumAr)
print(f"C1+C2: {Sum.real:.10f} + {Sum.imag:.10f}j")
# ----- Output -----
# C: -0.0000000245 + -0.3919657209j
# C1: 0.0000000000 + 0.0007333430j
# C2: 0.0000000009 + -0.3926990832j
# C1+C2: 0.0000000009 + -0.3919657401j
ここまでの議論では留数をわざわざ定義する意味が分らないのですが、留数は周回積分以外にも様々な解析的手法で計算することができます。その基本になるのが、留数は \(f(z)\) をLaurent展開した時の \((z - c)^{-1}\) の項の係数 \(a_{-1}\) であるということです。正則関数は実関数と同様にTaylor展開できますが、1点\(c\)を除いて正則な関数はその特異点\(c\)のまわりでLaurent展開できます。
Laurent展開とTaylor展開の違いは、式\eqref{n}における\(\Sigma\)の下限が0ではなく\(-\infty\)になっており、負べきの項があるという点です。また、Taylor展開の\(n\)乗の項の係数は
特異点は、Laurent展開した時に負べきの項がどこまで続くかによって分類できます。負べきの項が \((z - c)^{-m}\) までしかない(\(a_{-m}\) より先の係数が全て0となる)場合、その特異点は\(m\)位の極と言います。負べきの項が無限に続く場合は真性特異点と呼びます。例えば、式\eqref{l}の4つの特異点は全て1位の極です。特異点 \(-2\) について言うと、式\eqref{l}の2番目の等式から \((z + 2)\) を取除くと残った関数は \(z = -2\) において微分可能です。従って、残った関数はTaylor展開可能であり、元の関数 \(f(z)\)はこれを \((z + 2)\) で割ったものになるので、負べきの項は1つだけになります。
さて、上記の \((z + 2)\) を取除いた式において \(z = -2\) とするとどうなるでしょう。Taylor展開で残る項は \((z + 2)^{0}\) の係数である定数項のみとなり、これがLaurent展開の \(a_{-1}\) となります。従って、 \((z + 2)\) を取除いた式に \(z = -2\) を代入した結果が \(z = -2\) での留数になります。
同様に \(z = 2i\) での留数も計算すると
「理屈はそうだけど、その通りにはいかないよね」というのが現実世界だという話をもう少し掘り下げます。これは何も技術者が「理屈」を軽視しているということではありません。現実世界が理屈通りにいかないのは、その理屈では考慮されていない要因が色々とあるからです。数学の世界は土俵となる世界そのものを数学者が構築しています。ですから「そこまでは考えていなかったなあ」という不意打ちを食らう心配がないのです。いくら用意周到に考えても現実世界には考慮しきれない要素が多数存在します。
とはいえ技術者が取扱う自然科学の分野では「設計」という概念が成立します。最初から設計通りにはいかないにしても、設計と現実の差分を生じる原因を分析して、さらに設計に取込んでいくことで最終目標に近付くことができます。翻って人生はどうでしょう。「人生設計」という言葉はありますが、人生を設計通りに生きることはできません。人の世には世の中を構成する人それぞれの考えや思いがあって、物理法則が支配する自然界以上に複雑です。設計と現実の差分を生じる原因が分ったとしても、年齢を元に戻すことはできません。
「いくつになっても人生はやり直せる」というのは単なる気休めの呪文だと思います。駄待ち狐が「会社の選択を間違えた」と悟ったのは30代後半ですが、その時はもう家庭の事情で身動きが取れなくなっていました。会社人生のロスタイムで最後の挑戦をしようとした時には完全に戦力外扱いでした。ただ、やり直すことはできなくても、今までの経験を基に導き出した指針を残りの人生にフィードフォワードしていくことはできます。「駄待ちの人生も捨てたもんじゃなかったなあ」と笑いながら人生の最期を迎えたいものです。