はじめに
数値解析において,有限個の離散データ点から連続関数を推定する多項式補間は,実験データの可視化・数値積分・微分方程式の数値解法など幅広い場面で用いられる基礎技術である.
本稿では,多項式補間の代表的手法であるラグランジュ補間法について,ラグランジュ基底多項式の定義から母関数を用いた等価表現の導出まで数学的に解説し,Pythonによる数値実装を示す.さらに,ランダムな4点を通る3次補間曲線の描画,および正弦波上の6点から構成した5次補間多項式と真の正弦波との精度比較を行う.
1. ラグランジュ補間の理論
1.1 問題設定
$n+1$ 個の相異なるデータ点
(x_0, y_0),\ (x_1, y_1),\ \ldots,\ (x_n, y_n) \quad (x_i \neq x_j \text{ for } i \neq j)
が与えられたとき,これら全点を通る高々 $n$ 次の多項式 $P(x)$ を求めることが目標である.
$$
P(x_i) = y_i \quad (i = 0, 1, \ldots, n)
$$
代数学の基本定理より,$n+1$ 個の点を通る $n$ 次多項式は一意に存在する.ラグランジュ補間はその構成法の一つである.
1.2 ラグランジュ基底多項式の定義
各 $i = 0, 1, \ldots, n$ に対して,ラグランジュ基底多項式 $L_i(x)$ を次のように定義する.
$$
L_i(x) = \prod_{\substack{j=0 \ j \neq i}}^{n} \frac{x - x_j}{x_i - x_j}
$$
この $L_i(x)$ は以下のクロネッカーのデルタの性質を満たす.
L_i(x_j) = \delta_{ij} =
\begin{cases}
1 & (i = j) \\
0 & (i \neq j)
\end{cases}
証明:
-
$j\to i$ のとき,分子と分母が完全に一致するため $L_i(x_i)\to 1$
-
$j \neq i$ のとき,分子に $(x_j - x_j) = 0$ の因子が含まれるため $L_i(x_j) = 0$
1.3 補間多項式の構成
上記の基底多項式を用いて,補間多項式を次のように構成する.
\boxed{P(x) = \sum_{i=0}^{n} y_i\, L_i(x)}
各データ点 $x_k$ において,
P(x_k) = \sum_{i=0}^{n} y_i\, L_i(x_k) = \sum_{i=0}^{n} y_i\, \delta_{ik} = y_k
が成立するため,$P(x)$ は確かに全データ点を通る.また,$L_i(x)$ はそれぞれ $n$ 次多項式であり,$P(x)$ は高々 $n$ 次の多項式である.
1.4 母関数を用いた等価表現
数値実装の効率化のため,母関数 $w(x)$ を導入する.
w(x) = \prod_{j=0}^{n} (x - x_j)
$x = x_i$ では $(x_i - x_i) = 0$ の因子が現れるため $w(x_i) = 0$ だが,$x_i$ における微分は
w'(x_i) = \prod_{\substack{j=0 \\ j \neq i}}^{n} (x_i - x_j)
と求められる.これを用いると,ラグランジュ基底多項式は
L_i(x) = \frac{w(x)}{(x - x_i)\, w'(x_i)}
と書き直せる.この表現のメリットは,$w(x)$ を各評価点で一度だけ計算すれば全 $i$ で共有できる点にある.
| 表現 | 式 | 特徴 |
|---|---|---|
| 積形式 | $\prod_{j \neq i} \frac{x - x_j}{x_i - x_j}$ | 定義に忠実 |
| 母関数形式 | $\dfrac{w(x)}{(x - x_i), w'(x_i)}$ | 実装で $w(x)$ を使い回せる |
2. 数値実装
2.1 アルゴリズム
$w'(x_i)$ の計算には前進差分による数値微分を用いる.
w'(x_i) \approx \frac{w(x_i + h) - w(x_i)}{h}
と簡略化される.本実装では $h = 10^{-5}$ を採用する(前進差分の精度 $O(h)$ に対して十分小さい値).
評価点 $x$ における補間値の計算手順は以下のとおりである.
- $w(x) = \prod_{j=0}^{n} (x - x_j)$ を計算する.
- 各 $i$ について $w'(x_i) \approx w(x_i + h) / h$ を求める.
- $L_i(x) = w(x) / \bigl[(x - x_i), w'(x_i)\bigr]$ を計算する.
- $P(x) = \sum_{i=0}^{n} y_i, L_i(x)$ を集計する.
2.2 コア実装
h = 1e-5 # 数値微分のステップ幅
def mother_function(x, a_n):
"""母関数 w(x) = Π_{i=0}^{n} (x - a_n[i]) を計算する"""
result = 1
for i in range(n):
result *= (x - a_n[i])
return result
for k in range(m):
f = 0.0
ff = mother_function(x_ary[k], a_n) # w(x_k)
for i in range(n):
aa = a_n[i]
# 前進差分: w'(x_i) ≈ w(x_i + h) / h (w(x_i)=0 を利用)
df_an = (mother_function(aa + h, a_n) - mother_function(aa, a_n)) / h
Li = ff / ((x_ary[k] - aa) * df_an) # L_i(x)
f += b_n[i] * Li # P(x) = Σ y_i L_i(x)
f_ary[k] = f
3. 実験1:ランダムな4点を通る3次補間曲線
3.1 設定
$[0, 1]$ 区間内にランダムに配置した4点(次数 $n = 3$)を用いて,3次ラグランジュ補間多項式を構成する.4点が決まると3次多項式は一意に定まる.
3.2 プログラム
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
h = 1e-5
nn = 3 # 多項式の次数
n = nn + 1 # データ点の個数(4点)
np.random.seed(42)
a_n = np.sort(np.random.rand(n)) # x 座標(ソートして左から右へ配置)
b_n = np.random.rand(n) # y 座標
def mother_function(x, a_n):
result = 1
for i in range(n):
result *= (x - a_n[i])
return result
m = 300
x_ary = np.linspace(0, 1, m)
f_ary = np.zeros(m)
for k in range(m):
f = 0.0
ff = mother_function(x_ary[k], a_n)
for i in range(n):
aa = a_n[i]
df_an = (mother_function(aa + h, a_n) - mother_function(aa, a_n)) / h
Li = ff / ((x_ary[k] - aa) * df_an)
f += b_n[i] * Li
f_ary[k] = f
plt.figure(figsize=(8, 5))
plt.plot(x_ary, f_ary, color='blue', label='3次補間曲線')
plt.scatter(a_n, b_n, color='red', zorder=5, s=80, label='データ点(4点)')
plt.xlabel('x')
plt.ylabel('y')
plt.title('ラグランジュ補間(3次):ランダムな4点')
plt.legend()
plt.grid()
plt.tight_layout()
plt.savefig('lagrange_cubic.png', dpi=150)
plt.show()
3.3 結果と考察
実行結果として,4つのデータ点(赤点)をすべて通る滑らかな3次曲線が得られる.
ラグランジュ補間は与えられた全データ点を必ず通ることが数学的に保証されており,補間誤差はデータ点上でゼロとなる.データ点間の関数形状は,与えられた点の位置と値に応じて自動的に決定される.
ただし,今回は数値微分を用いたため,数学的な厳密解ではなくあくまで数値解であることに注意する.
4. 実験2:正弦波の5次ラグランジュ補間
4.1 設定
真の関数を $f(x) = \sin(2\pi x)$ とする.$[0, 1]$ 区間に等間隔に配置した6点のデータから5次ラグランジュ補間多項式を構成し,真の正弦波との誤差を視覚化する.
注意(次数と点数の関係): $n$ 次多項式を一意に決定するには $n+1$ 個のデータ点が必要である.5次多項式($n = 5$)を構成するには6点が必要となる.
4.2 プログラム
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
h = 1e-5
nn = 5 # 多項式の次数
n = nn + 1 # データ点の個数(6点)
a_n = np.linspace(0, 1, n) # [0,1] に等間隔な6点
b_n = np.sin(a_n * 2 * np.pi) # 正弦波上の y 値
def mother_function(x, a_n):
result = 1
for i in range(n):
result *= (x - a_n[i])
return result
m = 300
x_ary = np.linspace(0, 1, m)
f_ary = np.zeros(m)
for k in range(m):
f = 0.0
ff = mother_function(x_ary[k], a_n)
for i in range(n):
aa = a_n[i]
df_an = (mother_function(aa + h, a_n) - mother_function(aa, a_n)) / h
Li = ff / ((x_ary[k] - aa) * df_an)
f += b_n[i] * Li
f_ary[k] = f
sin_ary = np.sin(x_ary * 2 * np.pi) # 真の正弦波
plt.figure(figsize=(8, 5))
plt.plot(x_ary, sin_ary, color='green', linestyle='--', linewidth=2,
label='$\\sin(2\\pi x)$(真の関数)')
plt.plot(x_ary, f_ary, color='blue', linewidth=1.5,
label='5次補間曲線')
plt.scatter(a_n, b_n, color='red', zorder=5, s=80,
label='データ点(6点)')
plt.fill_between(x_ary, f_ary, sin_ary,
alpha=0.25, color='orange', label='誤差領域')
plt.xlabel('x')
plt.ylabel('y')
plt.title('ラグランジュ補間(5次):正弦波との比較')
plt.legend()
plt.grid()
plt.tight_layout()
plt.savefig('lagrange_sine.png', dpi=150)
plt.show()
4.3 結果と考察
$\sin$関数を多項式補完で近似した場合のグラフを以下に示す.
等間隔ノードによる5次補間の結果,以下の傾向が観察される.
- データ点付近:補間誤差はほぼゼロであり,補間曲線(青)と正弦波(緑破線)がおおよそ一致する.
- 区間端部($x \approx 0$ および $x \approx 1$ 付近):誤差(橙色領域)がやや拡大する傾向がある.
この端部での誤差拡大は,等間隔ノードを用いた高次多項式補間において区間端部で誤差が増大するルンゲ現象(Runge's phenomenon)の萌芽的な兆候と解釈できる.$\sin(2\pi x)$ のように滑らかな周期関数であっても,等間隔配置では端部のノード密度が不足するためこの現象が生じやすい.
もちろん、端点では、多項式の最高もしくは最低次数の影響を受けやすい。つまり、正弦波曲線は、有限次数の多項式で表現することができないため、誤差が大きくなる部分がどうしても存在する。
| 評価項目 | 内容 |
|---|---|
| 補間次数 | 5次 |
| データ点数 | 6点(等間隔) |
| 参照関数 | $\sin(2\pi x)$ |
| 誤差の特徴 | 中央部は小さく,端部でやや増大 |
| 改善策候補 | チェビシェフノードへの変更,スプライン補間 |
5. まとめ
本稿では,ラグランジュ補間法の理論的導出から数値実装,および実験的検証を行った.得られた知見を以下に整理する.
- ラグランジュ基底多項式 $L_i(x)$ は $i$ 番目のノードで $1$,他のノードで $0$ をとり,この性質が補間の正確性を保証する.
- 母関数 $w(x) = \prod_j (x - x_j)$ を用いると,各評価点で $w(x)$ を一度計算するだけで全基底多項式の分子を共有できるため,実装効率が向上する.
- $w'(x_i)$ の計算には数値微分(前進差分)を適用でき,$w(x_i) = 0$ の性質により安定して近似できる.
- 等間隔ノードによる高次補間では,区間端部でルンゲ現象が現れる場合がある.高精度が要求される場面ではチェビシェフノードの採用が有効である.
参考文献
- 森正武,数値解析(第2版),共立出版,2002.
- W. H. Press et al., Numerical Recipes in C, 3rd ed., Cambridge University Press, 2007.
- 山本哲朗,数値解析入門(増訂版),サイエンス社,2003.
- https://mathlog.info/articles/2270
- https://amzn.asia/d/0cfNowaW
付録:プログラム全文
実験1:ランダムな4点を通る3次補間曲線
# ラグランジュ補間法(実験1):ランダムな4点を通る3次補間曲線
#
# P(x) = Σ y_i * L_i(x)
# L_i(x) = w(x) / ((x - x_i) * w'(x_i))
# w(x) = Π (x - x_j) (母関数)
# w'(x_i) ≈ w(x_i + h) / h (数値微分,w(x_i)=0 を利用)
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
h = 1e-5 # 数値微分のステップ幅
nn = 3 # 多項式の次数
n = nn + 1 # データ点の個数(4点)
np.random.seed(42)
a_n = np.sort(np.random.rand(n)) # x 座標: [0,1) の一様乱数(ソート済み)
b_n = np.random.rand(n) # y 座標: [0,1) の一様乱数
def mother_function(x, a_n):
"""母関数 w(x) = Π_{i=0}^{n} (x - a_n[i]) を計算する"""
result = 1
for i in range(n):
result *= (x - a_n[i])
return result
m = 300
x_ary = np.linspace(0, 1, m)
f_ary = np.zeros(m)
for k in range(m):
f = 0.0
ff = mother_function(x_ary[k], a_n) # w(x_k)
for i in range(n):
aa = a_n[i]
# 前進差分で w'(x_i) を近似(w(x_i)=0 のため差分が安定)
df_an = (mother_function(aa + h, a_n) - mother_function(aa, a_n)) / h
# ラグランジュ基底多項式: L_i(x) = w(x) / ((x - x_i) * w'(x_i))
Li = ff / ((x_ary[k] - aa) * df_an)
f += b_n[i] * Li # P(x) = Σ y_i * L_i(x)
f_ary[k] = f
plt.figure(figsize=(8, 5))
plt.plot(x_ary, f_ary, color='blue', label='3次補間曲線')
plt.scatter(a_n, b_n, color='red', zorder=5, s=80, label='データ点(4点)')
plt.xlabel('x')
plt.ylabel('y')
plt.title('ラグランジュ補間(3次):ランダムな4点')
plt.legend()
plt.grid()
plt.tight_layout()
plt.savefig('lagrange_cubic.png', dpi=150)
plt.show()
実験2:正弦波上の6点を通る5次補間曲線と正弦波との比較
# ラグランジュ補間法(実験2):正弦波の5次補間と真値との比較
#
# P(x) = Σ y_i * L_i(x)
# L_i(x) = w(x) / ((x - x_i) * w'(x_i))
# w(x) = Π (x - x_j) (母関数)
# w'(x_i) ≈ w(x_i + h) / h (数値微分)
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
h = 1e-5 # 数値微分のステップ幅
nn = 5 # 多項式の次数
n = nn + 1 # データ点の個数(6点)
a_n = np.linspace(0, 1, n) # [0,1] に等間隔な6点
b_n = np.sin(a_n * 2 * np.pi) # 正弦波上の y 値
def mother_function(x, a_n):
"""母関数 w(x) = Π_{i=0}^{n} (x - a_n[i]) を計算する"""
result = 1
for i in range(n):
result *= (x - a_n[i])
return result
m = 300
x_ary = np.linspace(0, 1, m)
f_ary = np.zeros(m)
for k in range(m):
f = 0.0
ff = mother_function(x_ary[k], a_n) # w(x_k)
for i in range(n):
aa = a_n[i]
df_an = (mother_function(aa + h, a_n) - mother_function(aa, a_n)) / h
Li = ff / ((x_ary[k] - aa) * df_an)
f += b_n[i] * Li
f_ary[k] = f
sin_ary = np.sin(x_ary * 2 * np.pi) # 真の正弦波
plt.figure(figsize=(8, 5))
plt.plot(x_ary, sin_ary, color='green', linestyle='--', linewidth=2,
label='$\\sin(2\\pi x)$(真の関数)')
plt.plot(x_ary, f_ary, color='blue', linewidth=1.5,
label='5次補間曲線')
plt.scatter(a_n, b_n, color='red', zorder=5, s=80,
label='データ点(6点)')
plt.fill_between(x_ary, f_ary, sin_ary,
alpha=0.25, color='orange', label='誤差領域')
plt.xlabel('x')
plt.ylabel('y')
plt.title('ラグランジュ補間(5次):正弦波との比較')
plt.legend()
plt.grid()
plt.tight_layout()
plt.savefig('lagrange_sin.png', dpi=150)
plt.show()

