C++ | 自動微分でニュートン法を改善する

以前の記事で C++ でのニュートン法プログラム例を紹介しました。
ニュートン法は、方程式を解くための求根アルゴリズムの一種ですが、対象関数の微分式が必要であるという特徴があります。

別の記事で、二重数による自動微分のプログラム例を紹介しました。
これを応用することで、ニュートン法での微分式の定義を不要とすることができます。

この記事では、自動微分とニュートン法の組み合わせ例を紹介します。

目次

自動微分用ヘッダファイルを準備

自動微分については、基本的に以前紹介したものをそのまま利用します。
ニュートン法で利用するため、以下のようにヘッダファイル化のみ対処しております。

dual.h
#ifndef DUAL_H
#define DUAL_H

#include <cmath>
#include <iostream>

struct Dual {
    double a;
    double b;

    Dual() : a(0.0), b(0.0) {}
    Dual(double a) : a(a), b(0.0) {}
    Dual(double a, double b) : a(a), b(b) {}

    Dual operator+() const { return *this; }
    Dual operator-() const { return Dual(-a, -b); }
};

inline Dual operator+(Dual lhs, Dual rhs) {
    return Dual{lhs.a + rhs.a, lhs.b + rhs.b};
}

inline Dual operator-(Dual lhs, Dual rhs) {
    return lhs + (-rhs);
}

inline Dual operator*(Dual lhs, Dual rhs) {
    return Dual{lhs.a * rhs.a, lhs.a * rhs.b + lhs.b * rhs.a};
}

inline Dual operator/(Dual lhs, Dual rhs) {
    return Dual{lhs.a / rhs.a, (lhs.b * rhs.a - lhs.a * rhs.b) / (rhs.a * rhs.a)};
}

/* 数学関数の定義等は省略 */

#endif // DUAL_H

なお、+= 演算子の定義不足、数学関数の定義不足などあります。
この記事はニュートン法との組み合わせが目的のため、この点は引き続き完備しておりません。

ニュートン法の改良

自動微分を利用して改良したニュートン法です。

C++
#include "dual.h"

#include <functional>
#include <iomanip>
#include <iostream>

double newton_method(
    std::function<Dual(Dual)> f,
    double x0,
    double torelance,
    std::size_t max_iter) {

    Dual x{x0, 1.0};
    for (std::size_t i = 0; i < max_iter; ++i) {
        Dual fx = f(x);
        if (std::abs(fx.a) < torelance) {
            return x.a;
        }

        x = x - fx.a / fx.b;
    }

    throw std::runtime_error("最大試行回数に達しました");
}

第1引数である関数 f について、関数の入出力方を double から Dual に変更しています。
これによって、一次微分の計算値を計算できるようになることから、微分関数の定義が不要になります。

以前の記事でのニュートン法との比較

比較のため、以前の記事でのニュートン法も掲載しておきます。

C++
#include <cmath>
#include <functional>

double newton_method(
    std::function<double(double)> f,
    std::function<double(double)> f_prime,
    double x0,
    double torelance,
    std::size_t max_iter) {

    double x = x0;
    for (std::size_t i = 0; i < max_iter; ++i) {
        double fx = f(x);
        if (std::abs(fx) < torelance) {
            return x;
        }

        x -= fx / f_prime(x);
    }

    throw std::runtime_error("最大試行回数に達しました");
}

プログラム実行例

ここで定義したニュートン法の実行例です。
一次微分式を定義していないにも関わらず、ニュートン法の収束計算ができることが確認できます。

C++
Dual f(Dual x) {
    return x * x - 2.0;
}

int main() {
    auto y = newton_method(f, 1.0, 1e-12, 100);
    std::cout << std::fixed << std::setprecision(11) << y << std::endl;
    // Output: 1.41421356237
    return 0;
}


  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次