外挿法で円周率を求める
Introduction
外挿法は,粗い計算結果から極限値を推定する方法です.この記事では,円周率近似の例を出発点にして,多角形近似,誤差展開,Richardson extrapolation の考え方を整理します.題材は円周率ですが,見ているものは有限要素法にも現れる「誤差の主項を消す」という考え方です.
1.外挿法で何をしたいのか
数値計算では,ある量 \( a_0 \) を直接求めることが難しいため,パラメータ \( h>0\) に依存する近似値 \( a(h)\) を計算し,
\[
a_0 = \lim_{h\to 0} a(h)
\]
として目的の値を得ようとすることが多くあります.ただし,\( h\) を小さくするだけでは計算量が増えます.また,丸め誤差も無視できなくなります.外挿法の基本的な考え方は,いくつかの \( h\) に対する値
\[
a(h_0),\ a(h_1),\ a(h_2),\ldots
\]
を使い,\( h=0 \) での値を推定することです.重要なのは,単に点を結ぶことではありません.背後に
\[
a(h)=a_0+a_1h^q+a_2h^{2q}+a_3h^{3q}+\cdots
\]
のような漸近展開があるとき,外挿は特に強力になります.つまり,外挿法は「誤差の構造を利用する方法」です.
2.円周率を多角形で近似する
半径1の円を考えます.円周の長さは \(2\pi\) です.この円に内接する正 \(n\) 角形を考えると,一辺の長さは \[ 2\sin\frac{\pi}{n} \] です.したがって,正 \(n\) 角形の周長の半分は
\[ T_n := n\sin\frac{\pi}{n} \] となります.これは円周の半分 \(\pi\) より小さいので, \[ T_n < \pi \] です.一方,外接正 \(n\) 角形を用いれば \[ \pi < U_n := n\tan\frac{\pi}{n} \] も得られます.したがって, \[ n\sin\frac{\pi}{n} < \pi < n\tan\frac{\pi}{n} \] という上下評価が得られます.
ここでは実行用の計算では \(\sin(\pi/n)\) を直接使いません.円周率を求めたいのに,計算の中で \( \pi\) を使うと,ちょっと手品のタネが見えてしまうからです.
3.\(\pi\) を使わずに辺数を倍にする
単位円に内接する正 \(n\) 角形を考え,その一辺の長さを \(s_n\) とします.中心角は \(2\pi/n\) なので, \[ s_n=2\sin\frac{\pi}{n} \] です.特に,正六角形では \[ s_6=2\sin\frac{\pi}{6}=1 \] となります.
正 \(n\) 角形から正 \(2n\) 角形へ移るには, \[ s_{2n}=2\sin\frac{\pi}{2n} \] を求めればよいです.半角公式を使うと, \[ s_{2n} = \sqrt{2-\sqrt{4-s_n^2}} \] が得られます.しかし,この形では \(n\) が大きいときに桁落ちが起こりやすいです. そこで有理化して, \[ s_{2n} = \frac{s_n}{\sqrt{2+\sqrt{4-s_n^2}}} \] と計算します.これが数値的に安定な形です.
以上により,正六角形の一辺 \(s_6=1\) から出発して,平方根だけを用いる更新式により \(s_{12},s_{24},s_{48},\ldots\) を順に計算できます.そして \[ \pi_n:=\frac{n s_n}{2} \] とおけば,これは単位円の周長と直径の比,すなわち \(\pi\) の近似値になります.実際,理論的には \(s_n=2\sin(\pi/n)\) なので, \[ \pi_n=n\sin\frac{\pi}{n} \] です.これは内接正 \(n\) 角形による近似であり, \[ \pi_n<\pi,\quad \pi_n\to\pi\quad(n\to\infty) \] が成り立ちます.
重要なのは,実際の計算では \(\pi\) や \(\sin(\pi/n)\) を使っていない点です.\(\pi\) を含む式はあくまで理論的な説明であり,計算自体は正六角形の値 \(s_6=1\) と平方根の反復だけで行われます.したがって,ここで得られる \(\pi_n\) は,\(\pi\) を前もって知っているから出てくる値ではなく,多角形近似から作られる純粋な近似列です.
ここまでで,\(\pi\) に近づく計算可能な列 \[ \pi_6,\pi_{12},\pi_{24},\ldots \] が得られました.しかし,この列はそのままでは比較的ゆっくり収束します.外挿法の考え方は,この近似列の誤差構造を利用して,より高精度な近似値を作ることにあります.
4.誤差展開を見る
理論を調べるために,ここでは一時的に \[ T_n = n\sin\frac{\pi}{n} \] という表示を使います.\(h=1/n\) とおくと, \[ T(h) = \frac{1}{h}\sin(\pi h) \] です.Taylor 展開より, \[ T(h) = \pi - \frac{\pi^3}{3!}h^2 + \frac{\pi^5}{5!}h^4 - \frac{\pi^7}{7!}h^6 + \cdots \] となります.つまり,\(T(h)\) は \[ T(h)=\pi+c_2h^2+c_4h^4+c_6h^6+\cdots \] という偶数冪の誤差展開を持っています.この「偶数冪で展開される」という事実が,外挿で大きく効きます.
5.1回の Richardson extrapolation
いま, \[ T_n = \pi + c_2h^2 + c_4h^4 + O(h^6), \quad h=\frac{1}{n}, \] とします.辺数を倍にすると \(h\) は \(h/2\) になるので, \[ T_{2n} = \pi + c_2\left(\frac{h}{2}\right)^2 + c_4\left(\frac{h}{2}\right)^4 + O(h^6) \] です.ここで \[ S_n := \frac{4T_{2n}-T_n}{3} \] とおくと,\(c_2h^2\) の項が消えます.実際, \[ S_n = \pi + O(h^4) \] となります.もとの多角形近似は \(O(h^2)\) でしたが,この線形結合により \(O(h^4)\) の近似に改善されます.
外挿法の要点
\(h\) を小さくするだけでなく,\(h\) と \(h/2\) の計算結果を組み合わせて,誤差の主要項を消します.これが Richardson extrapolation の最も基本的な形です.
6.繰り返し外挿
さらに高次の誤差項も順に消したい場合は,外挿表を作ります.まず, \[ R_{k,0}=T_{6\cdot 2^k} \] とおきます.そして \(j\geq 1\) に対して, \[ R_{k,j} =\frac{4^jR_{k,j-1}-R_{k-1,j-1}}{4^j-1} \] と定めます.ここで \(4^j\) が出てくるのは,誤差展開が \[ h^2,\ h^4,\ h^6,\ldots \] という偶数冪で進むからです.
7.Pythonで実行する
以下のコードでは,正六角形から始めて,辺数を倍々に増やします.計算本体では \(\pi\) を使いません.最後の誤差表示だけ,確認用に math.pi を使っています.
import math
def inscribed_polygon_pi_sequence(levels=8):
"""内接正多角形による円周率近似を返す."""
n = 6
s = 1.0 # 正六角形の一辺の長さ
values = []
for k in range(levels):
pi_n = n * s / 2.0
values.append((k, n, pi_n))
# 半角公式による安定な更新式.
# s_n = 2 sin(theta) とすると,
# s_{2n} = 2 sin(theta/2)
# = s_n / sqrt(2 + 2 cos(theta)).
cos_theta = math.sqrt(1.0 - (s / 2.0) ** 2)
s = s / math.sqrt(2.0 + 2.0 * cos_theta)
n *= 2
return values
def richardson_table(values):
"""偶数冪の誤差展開を仮定した Richardson 外挿表を作る."""
table = []
for k, (_, _, value) in enumerate(values):
row = [value]
for j in range(1, k + 1):
improved = (4 ** j * row[j - 1] - table[k - 1][j - 1]) / (4 ** j - 1)
row.append(improved)
table.append(row)
return table
def main():
levels = 8
values = inscribed_polygon_pi_sequence(levels)
table = richardson_table(values)
print(f"{'k':>2} {'n':>7} {'polygon_pi':>20} {'raw_error':>12} "
f"{'extrapolated_pi':>20} {'extrap_error':>12}")
print("-" * 78)
for k, n, pi_n in values:
extrapolated = table[k][-1]
raw_error = abs(math.pi - pi_n)
extrap_error = abs(math.pi - extrapolated)
print(f"{k:2d} {n:7d} {pi_n:20.15f} {raw_error:12.3e} "
f"{extrapolated:20.15f} {extrap_error:12.3e}")
if __name__ == "__main__":
main()
8.実行結果
k n polygon_pi raw_error extrapolated_pi extrap_error
------------------------------------------------------------------------------
0 6 3.000000000000000 1.416e-01 3.000000000000000 1.416e-01
1 12 3.105828541230249 3.576e-02 3.141104721640332 4.879e-04
2 24 3.132628613281238 8.964e-03 3.141592453897650 1.997e-07
3 48 3.139350203046867 2.242e-03 3.141592653577892 1.190e-11
4 96 3.141031950890509 5.607e-04 3.141592653589793 0.000e+00
5 192 3.141452472285462 1.402e-04 3.141592653589793 4.441e-16
6 384 3.141557607911857 3.505e-05 3.141592653589793 0.000e+00
7 768 3.141583892148318 8.761e-06 3.141592653589792 8.882e-16
正 \(96\) 角形による素朴な近似では,誤差はまだ約 \(5.6\times 10^{-4}\) です.しかし,外挿表の対角成分を使うと,同じ程度のデータから倍精度計算の限界に近い値まで到達します.
ここで誤差が 0.000e+00 と表示される部分がありますが,数学的に誤差がゼロという意味ではありません.倍精度浮動小数点数で見える範囲では,math.pi との差が表示されなくなっているという意味です.
9.この小さな例から見えること
この例で重要なのは,円周率そのものではありません.重要なのは,近似値の誤差が \[ c_2h^2+c_4h^4+c_6h^6+\cdots \] という形を持つと分かれば,その構造を利用して精度を上げられるという点です.
これは有限要素法でも同じです.たとえば,ある点 \(P\) における Ritz 射影の誤差が \[ R_hu(P)=u(P)+h^2 e(P)+O(h^4) \] のような展開を持てば,\(h\) と \(h/2\) の近似解を組み合わせることで,\(h^2 e(P)\) の主誤差を消すことができます.ただし PDE の場合には,解の正則性,領域の角,境界近傍のメッシュ構造,安定性評価などが絡みます.そこが円周率の例との大きな違いです.
参考文献
- R. Rannacher, Special Topics in Numerics III: Extrapolation and Defect Correction, Lecture Notes, Heidelberg University, SS 2017, Section 6.1.1 and 6.2.
- L. F. Richardson and J. A. Gaunt, The deferred approach to the limit, Philosophical Transactions of the Royal Society of London. Series A, 1927.
