この統合コードを最適化して実行速度を上げることは可能ですか?


9
double trap(double func(double), double b, double a, double N) {
  double j;
  double s;
  double h = (b-a)/(N-1.0); //Width of trapezia

  double func1 = func(a);
  double func2;

  for (s=0,j=a;j<b;j+=h){
    func2 = func(j+h);
    s = s + 0.5*(func1+func2)*h;
    func1 = func2;
  }

  return s;
}

上記は、拡張された台形規則を使用して、台形を使用しfunc()制限間の1D数値積分の私のC ++コードです。[a,b]N1

私は実際に3D統合を行っています。このコードは再帰的に呼び出されます。私は作業して、適切な結果を出しています。N=50

さらに減らす以外に、上記のコードを最適化してより速く実行する方法を提案できる人はいますか?または、より速い統合方法を提案できますか?N


5
これは質問にはあまり関係ありませんが、より適切な変数名を選択することをお勧めします。同様trapezoidal_integrationの代わりにtrapsumまたはrunning_total代わりにs(とも使う+=代わりにs = s +)、trapezoid_widthまたはdx代わりにh(またはしない、お好みの台形規則の表記に依存する)、および変更func1func2、彼らは値ではなく、機能しているという事実を反映します。例えばfunc1-> previous_valuefunc2-> current_value、またはそのようなもの。
David Z

回答:


5

数学的には、式は次と同等です。

=h12f1+f2+f++f1+12f+Obaf2

したがって、それを実装できます。言われたように、時間はおそらく関数評価によって支配されるので、同じ精度を得るために、より少ない関数評価を必要とするより良い積分法を使用できます。

ガウシアン求積法は、現代ではおもちゃというよりは少しだけです。評価が非常に少ない場合にのみ役立ちます。実装が簡単なものを希望する場合は、シンプソンの法則を使用できますが、正当な理由がなければ、を注文することはできません。1/N

関数の曲率が大きく変化する場合は、適応ステップルーチンを使用できます。これにより、関数がフラットな場合は大きなステップが選択され、曲率が高い場合はより正確なステップが選択されます。


離れて問題に戻った後、私はシンプソンのルールを実装することにしました。しかし、実際に合成シンプソンのルールのエラーが1 /(N ^ 4)に比例していることを確認できますか(答えに示唆されているように、1 /(N ^ 3)ではありません)。
user2970116 2014

1
と数式があります。最初の係数は係数を使用し、2番目の係数。 1 / N 4 5 / 12 13 / 12 1 1 ... 1 1 13 / 12 15 / 12 1 / 3 4 / 3 2 / 3 4 / 3 .. 。1/N1/N45/12,13/12,1,1...1,1,13/12,15/121/3,4/3,2/3,4/3...
Davidmh 2014

9

おそらく、関数の評価はこの計算の中で最も時間がかかる部分です。その場合は、統合ルーチン自体を高速化するのではなく、func()の速度を改善することに焦点を当てる必要があります。

func()のプロパティによっては、より洗練された積分式を使用することにより、より少ない関数評価で積分のより正確な評価を得ることができる可能性もあります。


1
確かに。関数がスムーズである場合、たとえば5つの間隔のみでガウス4求積法を使用すると、通常は50未満の関数評価で問題を回避できます。
Wolfgang Bangerth 2014年

7

可能?はい。有用?いいえ、ここで紹介する最適化によって、実行時間の違いがわずかなパーセントを超えることはほとんどありません。優れたコンパイラは、すでにこれらを行っている場合があります。

とにかく、あなたの内側のループを見てください:

    for (s=0,j=a;j<b;j+=h){
        func2 = func(j+h);
        s = s + 0.5*(func1+func2)*h;
        func1 = func2;
    }

すべてのループ反復でj + h、外部に持ち込むことができる3つの数学演算を追加0.5hます。加算、による乗算、およびによる乗算です。最初に、反復子変数をで開始することで修正できa + h、その他は乗算を因数分解して修正できます。

    for (s=0, j=a+h; j<=b; j+=h){
        func2 = func(j);
        s += func1+func2;
        func1 = func2;
    }
    s *= 0.5 * h;

これを行うことで指摘しますが、浮動小数点の丸め誤差のため、ループの最後の反復を見逃す可能性があります。(これも元の実装の問題でした。)これを回避するには、unsigned intまたはsize_tカウンターを使用します。

    size_t n;
    for (s=0, n=0, j=a+h; n<N; n++, j+=h){
        func2 = func(j);
        s += func1+func2;
        func1 = func2;
    }
    s *= 0.5 * h;

ブライアンの答えが言うように、あなたの時間は関数の評価を最適化するためによりよく費やされますfunc。この方法の精度が十分である場合、同じ方法でより速く何かが見つかるとは思えませんN。(たとえば、Runge-Kuttaを使用するNと、精度を犠牲にすることなく全体的な統合にかかる時間が短縮されるかどうかを確認するために、いくつかのテストを実行できます。)


4

計算を改善するために推奨するいくつかの変更があります。

  • パフォーマンスと精度を高めるにstd::fma()は、融合型積和演算を実行するを使用します。
  • パフォーマンスを向上させるには、各台形の面積に0.5を掛けるのを延期します。最後に1回行うことができます。
  • h丸め誤差が累積する可能性があるの繰り返し追加を避けます。

さらに、わかりやすくするためにいくつか変更を加えます。

  • 関数にわかりやすい名前を付けます。
  • 順番入れ替えab関数のシグネチャでは。
  • 名前の変更Nnhdxjx2saccumulator
  • に変更nしますint
  • より狭いスコープで変数を宣言します。
#include <cmath>

double trapezoidal_integration(double func(double), double a, double b, int n) {
    double dx = (b - a) / (n - 1);   // Width of trapezoids

    double func_x1 = func(a);
    double accumulator = 0;

    for (int i = 1; i <= n; i++) {
        double x2 = a + i * dx;      // Avoid repeated floating-point addition
        double func_x2 = func(x2);
        accumulator = std::fma(func_x1 + func_x2, dx, accumulator); // Fused multiply-add
        func_x1 = func_x2;
    }

    return 0.5 * accumulator;
}

3

関数が多項式であり、何らかの関数(ガウスなど)で重み付けされている可能性がある場合、3次式で正確に積分を公式(例:http ://people.sc.fsu.edu/~jburkardt/c_src/)で行うことができます。stroud / stroud.html)または疎グリッド(例:http: //tasmanian.ornl.gov/)。これらのメソッドは、ポイントと重みのセットを指定して関数値を乗算するだけなので、非常に高速です。関数が滑らかで多項式で近似できる場合でも、これらのメソッドは非常に良い答えを与えることができます。数式は、統合する関数のタイプに特化しているため、適切なものを見つけるのに少し時間がかかる場合があります。


3

整数を数値で計算しようとする場合、最小の労力で必要な精度を取得しようとするか、固定の努力で可能な限り最高の精度を取得しようとします。特定のアルゴリズムのコードをできるだけ速く実行する方法を尋ねるようです。

それはあなたに少しの利益を与えるかもしれませんが、それは少しでしょう。数値積分には、はるかに効率的な方法があります。「シンプソンの法則」、「ルンゲクッタ」、「フェルバーグ」のグーグル。それらはすべて、関数のいくつかの値を評価し、それらの値の倍数を巧妙に追加して、同じ数の関数評価ではるかに小さなエラーを生成するか、はるかに少ない評価数で同じエラーを生成することによって、非常によく似ています。


3

統合を行う方法はたくさんありますが、台形のルールは最も単純なものです。

統合する実際の機能について少しでも知っている場合は、それを活用すればより良いことができます。アイデアは、許容可能なエラーレベル内でグリッドポイントの数を最小限に抑えることです。

たとえば、台形は連続する点に線形近似を行っています。二次近似を作成できます。曲線が滑らかな場合は、より近似し、粗いグリッドを使用できます。

軌道は円錐曲線に非常によく似ているため、軌道シミュレーションは円錐曲線を使用して行われることがあります。

私の作品では、ベル型の曲線に近い形状を統合しているので、そのようにモデル化することが効果的です(この作品では、適応ガウス求積法が「ゴールドスタンダード」と見なされています)。


1

したがって、他の回答で指摘されているように、これは関数のコストに大きく依存します。trapzコードの最適化は、それが本当にボトルネックである場合にのみ価値があります。完全に明らかでない場合は、コードをプロファイリングしてこれを確認する必要があります(Intel V-tune、Valgrind、またはVisual Studioなどのツールでこれを実行できます)。

ただし、まったく異なるアプローチを提案します。それは、モンテカルロ統合です。ここでは、ランダムなポイントで関数をサンプリングして結果を追加することにより、積分を近似します。詳細については、Wikiページに加えてこの PDFを参照してください。

これは高次元データに対して非常によく機能し、通常、1次元積分で使用される求積法よりもはるかに優れています。

単純なケースは実装が非常に簡単です(pdfを参照)。c++ 98の標準ランダム関数は、パフォーマンスと品質の両方で非常に悪いことに注意してください。c ++ 11では、Mersenne Twisterをで使用できます。

関数の一部の領域で多くの変動があり、他の領域では変動が少ない場合は、層別サンプリングの使用を検討してください。自分で書くのではなく、GNU科学ライブラリを使用することをお勧めし ます。


1
私は実際に3D統合を行っています。このコードは再帰的に呼び出されます。

「再帰的に」が鍵です。大きなデータセットを調べて、多くのデータを2回以上検討するか、実際に(ピースワイズ?)関数からデータセットを生成しています。

再帰的に評価される統合は、途方もなく高価であり、再帰において力が増大するにつれて、途方もなく不正確になります。

データセットを補間するモデルを作成し、区分的シンボリック統合を行います。その後、多くのデータが基本関数の係数に折りたたまれるので、より深い再帰の複雑さは、指数関数的にではなく多項的に(そして通常はかなり低いパワーで)増大します。そして、「正確な」結果が得られます(妥当な数値パフォーマンスを得るには、依然として優れた評価スキームを理解する必要がありますが、台形積分よりも優れていることは、まだ実現可能です)。

台形ルールのエラー推定値を見ると、それらが関連する関数のいくつかの導関数に関連していることがわかります。また、統合/定義が再帰的に行われる場合、関数は適切に動作する導関数を持たない傾向があります。 。

あなたの唯一の道具がハンマーなら、すべての問題は釘のように見えます。説明ではほとんど問題に触れていませんが、台形規則を再帰的に適用することは不適切な一致であると疑っています。不正確さと計算要件の両方が爆発的に増加します。


1

1/21/2

    double trap(double func(double), double b, double a, double N){
double j, s;
double h = (b-a)/(N-1.0); //Width of trapezia

double s = 0;
j = a;
for(i=1; i<N-1; i++){
  j += h;
  s += func(j);
}
s += (func(a)+func(b))/2;

return s*h;
}

1
変更の理由とコードを教えてください。コードのブロックはほとんどの人にとってかなり役に立ちません。
Godric Seer 2014年

同意した あなたの答えを説明してください。
Geoff Oxberry 2014年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.