最終更新: 2021/3/20

二重積分の精度保証

柏木 雅英

1. はじめに

を提供している。

前者は、作れそうだから作ってみただけ、というレベルで、 速度、精度ともに不十分と考えている。 内部では、psa型の係数にpsa型を持たせたものを用いている。 また、わずかな変更で三角形領域での精度保証が可能だったので、 それも作ってみた。

後者は、前者よりは計算速度でいくらかマシになった。

また、

も試しに作ってみた。

2. ベキ級数演算による長方形領域及び三角形領域での二重積分の精度保証

インクルードファイルは、doubleintegral.hppを使う。 また下請けとして、psa.hpp, interval.hpp, またはそれらの下請けを使う。 参考ファイルはtest-doubleintegral.cc。
KVライブラリのwebデモの中の 二重積分(長方形領域)二重積分(三角形領域)、 で生成されるファイルも参考になるかも。

使っているアルゴリズムは、 精度保証付き二重積分の方法まとめ (Version: 2019/11/20) の第2節に説明がある。

使い方は、

  doubleintegral(被積分関数, x1, x2, y1, y2, 次数, 分割数)
のような感じ。戻り値が計算結果。

x1, x2, y1, y2はinterval型で、次の図のような積分範囲になる。

次数は用いる級数の次数、分割数をnとすると、x, yそれぞれをn等分して 計算する。

例えば、積分 \( \displaystyle \int_{-1}^1 \int_{-1}^1 \frac{1}{x^2 + 2y^2 +1} dx\,dy \) を計算する問題を考える。 このとき、次のようなプログラムを作成すればよい (sample2d1.cc)。

#include <kv/doubleintegral.hpp>

struct Func {
    template <class T> T operator() (const T& x, const T& y) {
        return 1 / (x * x + 2 * y * y + 1);
    }
};

typedef kv::interval<double> itv;

int main()
{
    std::cout.precision(17);

    std::cout << kv::doubleintegral(Func(), itv(-1), itv(1), itv(-1), itv(1), 8, 20) << "\n";
}
これを実行すると、
[2.2357751997649657,2.2357751999216653]
のように表示される。

なお、

  doubleintegral(被積分関数, x1, x2, y1, y2, 次数, 分割数, true)
のように末尾にフラグtrueを設定すると、

のような長方形の左下部分の三角形上の積分になる。 例えば、上の例に末尾trueを付けて (sample2d2.cc)、

#include <kv/doubleintegral.hpp>

struct Func {
    template <class T> T operator() (const T& x, const T& y) {
        return 1 / (x * x + 2 * y * y + 1);
    }
};

typedef kv::interval<double> itv;

int main()
{
    std::cout.precision(17);

    std::cout << kv::doubleintegral(Func(), itv(-1), itv(1), itv(-1), itv(1), 8, 20, true) << "\n";
}
は、積分 \( \displaystyle \int_{-1}^1 \int_{-1}^{-y} \frac{1}{x^2 + 2y^2 +1} dx\,dy \) を計算する。実行すると、
[1.1178875998783734,1.1178875999649421]
と表示される。

おまけで、

  doubleintegral_triangle(被積分関数, x1, y1, x2, y2, x3, y3, 次数, 分割数)
で、点(x1, y1)、点(x2, y2)、点(x3, y3)を頂点とする三角形上の 積分を行う。これは内部で変数変換してdoubleintegral(..., true)を呼び出している。

例えば、プログラム sample2d3.cc :

#include <kv/doubleintegral.hpp>

struct Func {
    template <class T> T operator() (const T& x, const T& y) {
        return 1 / (x * x + 2 * y * y + 1);
    }
};

typedef kv::interval<double> itv;

int main()
{
    std::cout.precision(17);

    std::cout << kv::doubleintegral_triangle(Func(), itv(-1), itv(-1), itv(-1), itv(1), itv(1), itv(-1), 8, 20) << "\n";
}
は、Sを点(-1, -1)、点(-1, 1)、点(1, -1)を頂点とする三角形として積分 \( \displaystyle \iint_S \frac{1}{x^2 + 2y^2 +1} dx\,dy \) を計算する。実行すると
[1.1178875998793988,1.1178875999639196]
と表示される。

3. 複合Newton-Cotes公式による長方形領域での二重積分の精度保証

複合Newton-Cotes公式による長方形領域での二重積分の精度保証を行う。 アルゴリズムは、 に従っている。 なお、このスライドに出てくる、二項からなる二重積分の誤差評価式は、 がルーツらしい。

一次元のものと同様、誤差項の計算に必要な高階微分の評価には

の方法と、 の方法を併用している。

インクルードファイルは、double-newtoncotes.hppを使う。 また下請けとして、interval.hpp, highderiv.hpp, optimize.hpp または それらの下請けを使う。 参考ファイルはtest-double-newtoncotes.cc。

例えば、積分 \( \displaystyle \int_{-1}^1 \int_{-1}^1 \frac{1}{x^2 + 2y^2 +1} dx\,dy \) を計算するには、次のようなプログラムを作成すればよい (sample2d4.cc)。

#include <kv/double-newtoncotes.hpp>

struct Func {
    template <class T> T operator() (const T& x, const T& y) {
        return 1 / (x * x + 2 * y * y + 1);
    }
};

typedef kv::interval<double> itv;

int main()
{
    std::cout.precision(17);

    std::cout << kv::double_newtoncotes(Func(), itv(-1), itv(1), itv(-1), itv(1), 6, 0, 0) << "\n";
}
double_newtoncotesを呼び出す。引数は
  double_newtoncotes(被積分関数, x1, x2, y1, y2, 分割区間の複合数, xの分割数, yの分割数, 誤差項の計算時の分割数)
の意味。戻り値が計算結果。

実行すると、

[2.235775199814181,2.2357751998437357]
と表示される。

4. 複合Newton-Cotes公式による曲がった辺を持つ長方形領域での二重積分の精度保証

下の図のように、xに関しては区間[a,b]で、yに関しては下端関数 l(x) と 上端関数 u(x) で挟まれた領域での二重積分、 すなわち、 \( \displaystyle \int_a^b \int_{l(x)}^{u(x)} f(x, y) \,dy\,dx \) を精度保証付きで計算できる。

アルゴリズムは、 精度保証付き二重積分について のp.42のように変数変換し、内部では上の 複合Newton-Cotes公式による長方形領域での二重積分を用いている。

インクルードファイルは、doubleint-curvededge.hppを使う。 下請けとして、double-newtoncotes.hpp、 それらの下請けを使う。 参考ファイルはtest-doubleint-curvededge.cc。

例えば、積分 \( \displaystyle \int_{-1}^1 \int_{-1 + \sin(3x) / 16}^{1 + \sin(5x) / 16} \frac{1}{x^2 + 2y^2 +1} \,dy\,dx \) を計算するには、次のようなプログラムを作成すればよい (sample2d5.cc)。

#include <iostream>
#include <kv/doubleint-curvededge.hpp>

struct Func {
    template <class T> T operator() (const T& x, const T& y) {
        return 1 / (x * x + 2 * y * y + 1);
    }
};

struct Edge_l {
    template <class T> T operator() (const T& x) {
        return -1 + sin(3*x) / 16;
    }
};

struct Edge_h {
    template <class T> T operator() (const T& x) {
        return 1 + sin(5*x) / 16;
    }
};

typedef kv::interval<double> itv;

int main() {
    std::cout.precision(17);

    std::cout << kv::doubleint_curvededge(Func(), Edge_l(), Edge_h(), itv(-1), itv(1), 6, 0, 0) << "\n";
}
doubleint_curvededgeを呼び出す。引数は
  doubleint_curvededge(被積分関数, yの下端関数, yの上端関数, xの下端, xの上端, 分割区間の複合数, xの分割数, yの分割数, 誤差項の計算時の分割数)
の意味で、積分範囲の指定以外の引数の意味は前のdouble-newtoncotes関数と同じ。

これを実行すると、

[2.2342787259328842,2.2342787260078528]
と表示される。

また、xとyを入れ替えたdoubleint_curvededge_sも提供されている。 引数は

  doubleint_curvededge_s(被積分関数, xの下端関数, xの上端関数, yの下端, yの上端, 分割区間の複合数, xの分割数, yの分割数, 誤差項の計算時の分割数)
となる。

例えば、積分 \( \displaystyle \int_{-1}^1 \int_{-1 + \sin(3y) / 16}^{1 + \sin(5y) / 16} \frac{1}{x^2 + 2y^2 +1} \,dx\,dy \) を計算プログラムは次のようになる (sample2d6.cc)。

#include <iostream>
#include <kv/doubleint-curvededge.hpp>

struct Func {
    template <class T> T operator() (const T& x, const T& y) {
        return 1 / (x * x + 2 * y * y + 1);
    }
};

struct Edge_l {
    template <class T> T operator() (const T& x) {
        return -1 + sin(3*x) / 16;
    }
};

struct Edge_h {
    template <class T> T operator() (const T& x) {
        return 1 + sin(5*x) / 16;
    }
};

typedef kv::interval<double> itv;

int main() {
    std::cout.precision(17);

    std::cout << kv::doubleint_curvededge_s(Func(), Edge_l(), Edge_h(), itv(-1), itv(1), 6, 0, 0) << "\n";
}
[2.2345032143918106,2.2345032147042741]

5. おわりに

我ながら関数名に一貫性がない。同じ関数で、内部で使うメソッドを パラメータで切り替えられるようにするべきか。