Rでニュートン法

Rで ニュートン法 を試みます。

本ポストはこちらの続きです。

Rで逆2次補間法
Rで 逆2次補間法 を試みます。本ポストはこちらの続きです。1. 逆2次補間法とは逆2次補間法(Inverse Quadratic Interpolation)は、方程式 f(x) = 0 の解を求めるためのアルゴリズムです。基本的なアイデ...

1. ニュートン法とは

ニュートン法(またはニュートン・ラフソン法)は、方程式 f(x) = 0 の解を求めるための反復法です。これまでの手法が関数の値 f(x) のみに着目していたのに対し、ニュートン法は関数の傾き(微分係数)f'(x) の情報も利用する点が最大の特徴です。

基本的なアイデア:接線を利用した予測

ニュートン法は、ある点におけるグラフの接線を利用して、次にx軸と交わる点(より解に近いであろう点)を予測します。

  1. まず、適当な初期値 x_n からスタートします。
  2. その点 (x_n, f(x_n)) におけるグラフの接線を引きます。
  3. この接線がx軸と交わる点を見つけ、それを次の近似解 x_{n+1} とします。
  4. x_{n+1} を新しい点として、ステップ2と3を繰り返します。

初期値が解に十分近ければ、このプロセスを繰り返すことで、近似解は真の解に収束していきます。

アルゴリズムの導出

(x_n, f(x_n)) における接線の方程式は、傾きが f'(x_n) であることから、以下のように表せます。

y - f(x_n) = f'(x_n) * (x - x_n)

この接線がx軸と交わる点では y=0 となるので、そのときの xx_{n+1} として代入すると、

0 - f(x_n) = f'(x_n) * (x_{n+1} - x_n)

この式を x_{n+1} について解くと、ニュートン法の有名な反復計算式が得られます。

x_{n+1} = x_n - f(x_n) / f'(x_n)

特徴

  • 長所:

    • 収束速度: 収束が速く、2次収束として知られています。これは、1回の反復で解の正しい桁数がほぼ倍になることを意味します。例えば、誤差が10^{-2}なら次は10^{-4}、その次は10^{-8}と、精度が向上します。
  • 短所:

    • 導関数が必要: f'(x) を計算し、プログラムに組み込む必要があります。関数が複雑な場合、導関数を求めるのが困難または不可能なことがあります。
    • 初期値への高い依存性: 初期値が解から遠いと、アルゴリズムが発散してしまったり、意図しない別の解に収束したりすることがあります。
    • f'(x) = 0 で破綻: 反復の途中で傾きがゼロになる点(極値など)に当たると、ゼロ除算が発生して計算が破綻します。

2. R言語によるシミュレーションコードと実行結果

それでは、これまでと同じ方程式 f(x) = x^3 - x - 1 = 0 をニュートン法で解きます。この場合、導関数は f'(x) = 3x^2 - 1 となります。

# 必要なライブラリを読み込みます
library(ggplot2)
library(dplyr)

# -----------------------------------------------------------------
# 1. シミュレーションの対象となる関数と、その導関数を定義
# -----------------------------------------------------------------
# f(x) = x^3 - x - 1
f <- function(x) {
  return(x^3 - x - 1)
}

# f(x)の導関数 f'(x) = 3x^2 - 1
f_prime <- function(x) {
  return(3 * x^2 - 1)
}


# -----------------------------------------------------------------
# 2. ニュートン法のシミュレーションを行う関数を定義
# -----------------------------------------------------------------
newton_simulation <- function(func, func_prime, x0, tol = 1e-12, max_iter = 10) {
  cat("--- ニュートン法シミュレーション開始 ---\n\n")
  cat(sprintf("対象関数: x^3 - x - 1\n"))
  cat(sprintf("初期値: x0 = %.1f\n", x0))
  cat(sprintf("許容誤差: %e\n\n", tol))

  x <- x0

  # 開始点(x)も保存するように列を追加
  history <- data.frame(
    iter = integer(),
    x = double(), # 開始点
    fx = double(), # f(開始点)
    x_new = double(), # 次の点
    fx_new = double()
  )

  for (i in 1:max_iter) {
    fx <- func(x)
    f_prime_x <- func_prime(x)

    if (abs(f_prime_x) < 1e-12) {
      cat("導関数f'(x)の値がゼロに近いため、計算を停止します。\n")
      break
    }

    x_new <- x - fx / f_prime_x
    fx_new <- func(x_new)

    cat(sprintf("【反復 %d回目】\n", i))
    cat(sprintf("  現在の点 x = %.12f\n", x))
    cat(sprintf("  次の近似解 x_new = %.12f\n", x_new))
    cat(sprintf("  その関数値 f(x_new) = %.2e\n", fx_new))

    # 開始点(x, fx)もデータフレームに保存
    history <- rbind(history, data.frame(
      iter = i,
      x = x,
      fx = fx,
      x_new = x_new,
      fx_new = fx_new
    ))

    if (abs(x_new - x) < tol) {
      cat("\n--- 収束しました ---\n\n")
      x <- x_new
      break
    }
    x <- x_new
  }

  if (i == max_iter) {
    cat("\n--- 最大反復回数に達しました ---\n")
  }
  cat(sprintf("最終的な解の近似値 = %.12f\n", x))

  return(history)
}

# -----------------------------------------------------------------
# 3. シミュレーションの実行
# -----------------------------------------------------------------
sim_history_newton <- newton_simulation(f, f_prime, x0 = 2.0)

# -----------------------------------------------------------------
# 4. ggplot2による結果の可視化 (2つのグラフを生成)
# -----------------------------------------------------------------
# --- グラフ1: 収束プロット  ---
final_solution_value <- tail(sim_history_newton, 1)$x_new
newton_process_plot <- ggplot(sim_history_newton, aes(x = iter, y = x_new)) +
  geom_hline(yintercept = final_solution_value, linetype = "dashed", color = "red", linewidth = 1) +
  geom_text(aes(x = 0.5, y = final_solution_value, label = sprintf("真の解\n(約 %.6f)", final_solution_value)),
    color = "red", hjust = 0, vjust = -0.2
  ) +
  geom_line(color = "gray50", linewidth = 1) +
  geom_point(color = "purple", size = 5, stroke = 1.5) +
  labs(
    title = "ニュートン法の収束プロセス",
    x = "反復回数 (iter)", y = "近似解 (x_new) の値"
  ) +
  scale_x_continuous(breaks = 1:nrow(sim_history_newton)) +
  theme_minimal(base_family = "sans")

# --- グラフ2: 接線による可視化 ---
# 関数の曲線を描画するためのデータ
curve_data <- data.frame(x = seq(1.2, 2.1, length.out = 200)) %>%
  mutate(y = f(x))

# 最終的な解の点
final_solution_point <- tail(sim_history_newton, 1)

# プロットを作成
tangent_plot <- ggplot() +
  # y=0 の線
  geom_hline(yintercept = 0, linetype = "dashed", color = "gray50") +
  # f(x)のグラフ
  geom_line(data = curve_data, aes(x = x, y = y), color = "blue", linewidth = 1.2) +

  # 各反復ステップでの接線を描画
  # (x, f(x)) から (x_new, 0) へ線を引く
  geom_segment(
    data = sim_history_newton,
    aes(x = x, y = fx, xend = x_new, yend = 0, color = iter),
    linetype = "dashed", linewidth = 1
  ) +

  # 接点をプロット
  geom_point(data = sim_history_newton, aes(x = x, y = fx, color = iter), size = 4) +
  # x軸との交点をプロット
  geom_point(data = sim_history_newton, aes(x = x_new, y = 0, color = iter), size = 4, shape = 1) +

  # 最終的な解を強調
  geom_point(aes(x = final_solution_point$x_new, y = 0), size = 5, shape = 8, color = "red") +
  geom_text(
    aes(
      x = final_solution_point$x_new, y = 0.5,
      label = sprintf("近似解\nx = %.5f", final_solution_point$x_new)
    ),
    color = "red", hjust = 0, vjust = 0
  ) +

  # スケールとラベルの設定
  scale_color_gradient(low = "lightblue", high = "purple4") +
  labs(
    title = "ニュートン法による求解プロセス(接線による可視化)",
    subtitle = "各点での接線がx軸と交わる点を次の近似解とする様子",
    x = "x",
    y = "f(x)"
  ) +
  theme_minimal(base_family = "sans") +
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold"),
    plot.subtitle = element_text(hjust = 0.5),
    legend.position = "none" # 凡例は非表示
  )

# 2つのプロットを表示
print(newton_process_plot)
print(tangent_plot)
--- ニュートン法シミュレーション開始 ---

対象関数: x^3 - x - 1
初期値: x0 = 2.0
許容誤差: 1.000000e-12

【反復 1回目】
  現在の点 x = 2.000000000000
  次の近似解 x_new = 1.545454545455
  その関数値 f(x_new) = 1.15e+00
【反復 2回目】
  現在の点 x = 1.545454545455
  次の近似解 x_new = 1.359614915915
  その関数値 f(x_new) = 1.54e-01
【反復 3回目】
  現在の点 x = 1.359614915915
  次の近似解 x_new = 1.325801345006
  その関数値 f(x_new) = 4.62e-03
【反復 4回目】
  現在の点 x = 1.325801345006
  次の近似解 x_new = 1.324719049417
  その関数値 f(x_new) = 4.66e-06
【反復 5回目】
  現在の点 x = 1.324719049417
  次の近似解 x_new = 1.324717957246
  その関数値 f(x_new) = 4.74e-12
【反復 6回目】
  現在の点 x = 1.324717957246
  次の近似解 x_new = 1.324717957245
  その関数値 f(x_new) = 2.22e-16
【反復 7回目】
  現在の点 x = 1.324717957245
  次の近似解 x_new = 1.324717957245
  その関数値 f(x_new) = 2.22e-16

--- 収束しました ---

最終的な解の近似値 = 1.324717957245
Figure 1
Figure 2

注目点: 7回の反復で、許容誤差1e-12をクリアする精度の解が得られました。

Figure 1 の読み方

  • 横軸: シミュレーションの反復回数です。
  • 縦軸: 各ステップで計算された近似解 x_new の値です。
  • 赤い破線: 方程式の真の解の位置を示します。
  • 紫の点と灰色の線: 各ステップで得られた近似解が、収束していく様子を示しています。

Figure 2 の読み方

  • 青い曲線: 対象関数 f(x) = x^3 - x - 1 のグラフです。
  • 灰色の破線: y=0 のライン(x軸)です。
  • 色のついた点と線: 各反復ステップを示します。色は反復回数(水色→紫)を表します。

    • 実線の点 (●): 各ステップの開始点 (x, f(x)) です。
    • 破線: その点から引かれた接線です。
    • 白抜きの点 (○): 接線がx軸と交わった点、つまり次の近似解 (x_new, 0) です。
  • 赤い星印: 最終的に得られた近似解です。

以上です。