5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

数値積分を実行する

Last updated at Posted at 2014-03-01

数値積分を実行する。

被積分関数までは解析的に書けているけれど、
積分が解析的に実行できず、さらにその値が他の計算に必要な場合。
しかも高精度に。

参考:ニュートン・コーツの公式(Wikipedia)

# include <functional>
# include <iostream>
# include <cmath>

enum IntegralMethod{
    Trapezoidal = 0,
    Simpson = 1,
    Boole = 2,
};

template<IntegralMethod>
class Integrater{
public:
    /** 一次元定積分 @f$ \int_a^b f(x) dx @f$ を積分する */
    static double calc(int N /** 空間刻み */,
                       double a /** 下限 */, double b /** 上限 */,
                       std::function<double(double)> f /** 積分する関数 */);
    /** 一次元定積分 @f$ \int_a^b f(x) dx @f$ を積分する(精度保証付き)。
     *  ただし打ち消して0になる場合は使えない。 */
    static double accurate(double a /** 下限 */, double b /** 上限 */,
                           std::function<double(double)> f /** integrand */,
                           double threshold=1e-9 /** 収束の閾値 */,
                           std::ostream* report = nullptr /** 収束の過程を表示 */)
    {
        int N = 128;
        double result_p = calc(N, a, b, f);
        double result_n;
        while(true){
            N *= 2;
            result_n = calc(N, a, b, f);
            if(report){
                *report << N << " " << result_n << std::endl;
            }
            if(std::abs(result_n - result_p) / std::abs(result_n) < threshold){
                break;
            }
            result_p = result_n;
        }
        return result_n;
    }
};

template<>
double Integrater<Trapezoidal>::calc(int N, double a, double b, std::function<double(double)> f) {
    long double sum = 0.0;
    double dx = (b - a) / N;
    for(int i=0;i<N;++i){
        double x_l = a + dx*i;
        double x_r = a + dx*(i+1);
        sum += f(x_l) + f(x_r);
    }
    return sum * dx / 2;
}

template<>
double Integrater<Simpson>::calc(int N, double a, double b, std::function<double(double)> f) {
    long double sum = 0.0;
    double dx = (b - a) / N;
    for(int i=0;i<N;++i){
        double x_l = a + dx*i;
        double x_r = a + dx*(i+1);
        double x_m = (x_l + x_r) / 2;
        sum += f(x_l) + 4*f(x_m) + f(x_r);
    }
    return sum * dx / 6;
}

template<>
double Integrater<Boole>::calc(int N, double a, double b, std::function<double(double)> f) {
    long double sum = 0.0;
    double dx = (b - a) / N;
    for(int i=0;i<N;++i){
        double x_l = a + dx*i;
        double x_r = a + dx*(i+1);
        double x_m = (x_l + x_r) / 2;
        double x_lm = (x_l + x_m) / 2;
        double x_rm = (x_r + x_m) / 2;
        sum += 7*f(x_l) + 32*f(x_lm) + 12*f(x_m) + 32*f(x_rm) + 7*f(x_r);
    }
    return sum * dx / (7+32+12+32+7);
}

台形公式(Trapezoidal)で行くつもりだったけど、
高精度を出そうとすると、台形公式は非常に効率がわるい。
そこで高次のアルゴリズムとしてSimpson, Booleを実装した。

最初はaccurateの引数にstd::functionで計算手法(Trapezoidal,...)を貰おうかと思ったけど、
この方がすっきりする。calcvirtualにして継承するよりは、こちらの方が好み。

地味なこだわりは

std::ostream* report = nullptr

でstreamをもらう事。これなら必ずしも出力する必要がない。

if(report){
...
}

の評価は通常非常に軽いはず。

もっといい実装キボンヌ

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?