#include <iostream> struct Func { double operator() (double x) { return x * x; } }; int main() { Func f; std::cout << f(3) << "\n"; }を実行すると9と表示される。Funcというクラスで()演算子をオーバーロード しているので、そのクラスのインスタンスfに(3)を作用させることが出来る。
これを用いて、簡単なニュートン法を書いた例を示す。
#include <iostream> struct Func1 { double operator() (double x) { return x * x - 2.; } }; struct Func2 { double operator() (double x) { return 2 * x - 1.; } }; template <class F> double newton(F f, double x) { int i; double d; for (i=0; i<10; i++) { d = (f(x + 0.01) - f(x)) / 0.01; x = x - f(x) / d; } return x; } int main() { std::cout << newton(Func1(), 5.) << "\n"; std::cout << newton(Func2(), 5.) << "\n"; }関数newtonは、解きたい問題の関数オブジェクトを第1引数として受け取っている。 解きたい問題毎に型が違うので、テンプレートを用いている。 関数ポインタを用いても似たようなことが実現できるが、 関数オブジェクトを用いるとポインタを通じた関数呼び出しのオーバーヘッドが無い。 この場合、関数newtonはFunc1用とFunc2用の2種類が自動的に生成されることになる。
上のnewtonの例では、Func1やFunc2はdoubleを受け取ってdoubleを返す関数であった。 しかし例えばallsolでは、
#include <kv/allsol.hpp> #include <kv/interval.hpp> #include <kv/rdouble.hpp> namespace ub = boost::numeric::ublas; struct Func1 { template <class T> ub::vector<T> operator() (const ub::vector<T>& x){ ub::vector<T> y(2); y(0) = x(0) * x(0) + x(1) * x(1) - 1.; y(1) = x(0) - x(1); return y; } }; int main() { ub::vector< kv::interval<double> > I; std::cout.precision(17); I.resize(2); I(0) = kv::interval<double>(-10., 10.); I(1) = kv::interval<double>(-10., 10.); kv::allsol(Func1(), I); }のように書く。これは、方程式
x02 + x12 - 1 = 0の([-10,10],[-10,10])における全ての解を探索する例である。
x0 - x1 = 0
まず、ベクトル値を受け取ってベクトル値を返すのに、ublasのvectorを用いる 仕様になっている。また、そのvectorの内部の型が(double等でなく)Tとtemplate になっている。 これは、例えばallsolでは、渡された関数に対して、 (例えば入力ベクトルに渡した値がinterval<double>だったとして)
なお、引数を
ub::vector<T> xではなく、
const ub::vector<T>& xのようにconst参照にしているのはコピーのオーバーヘッドを無くすためである。
1.18 x02 + x12 - 1 = 0を解くことを考える。これはどう記述するべきか。
x0 - x1 = 0
y(0) = 1.18 * x(0) * x(0) + x(1) * x(1) - 1.;だと、1.18はdoubleで正確に記述できないので、1.18に最も近いdoubleを 係数に持つ式を解いたことになってしまい、厳密で無くなる。
y(0) = 118./100. * x(0) * x(0) + x(1) * x(1) - 1.;と書けば、118も100もdoubleで正確に書けるが、除算に誤差が入ってしまうので やはりまずい。
y(0) = (kv::interval<double>)118./100. * x(0) * x(0) + x(1) * x(1) - 1.;のように書けば厳密な計算になる。 が、この方法だと、内部型をdd型にした場合にエラーになってしまう。 コンパイルを通すには、
#include <kv/allsol.hpp> #include <kv/dd.hpp> #include <kv/rdd.hpp> namespace ub = boost::numeric::ublas; struct Func1 { template <class T> ub::vector<T> operator() (const ub::vector<T>& x){ ub::vector<T> y(2); y(0) = (kv::interval<kv::dd>)118./100. * x(0) * x(0) + x(1) * x(1) - 1.; y(1) = x(0) - x(1); return y; } }; int main() { ub::vector< kv::interval<kv::dd> > I; std::cout.precision(34); I.resize(2); I(0) = kv::interval<kv::dd>(-10., 10.); I(1) = kv::interval<kv::dd>(-10., 10.); kv::allsol(Func1(), I); }のように、型を書き換える必要がある。出来ればFunc1は書き換えずに済ませたい。 また、T=doubleの場合 (Func1に対して単に近似計算したい場合) エラーになってしまう。
y(0) = "1.18" * x(0) * x(0) + x(1) * x(1) - 1.;と書くことが出来る。この方法だと、interval<double>とinterval<dd> で書き分ける必要は無くなる。が、T=doubleでエラーになるのは変わらず。 また、文字列からの変換は時間がかかるので、速度が問題となる。
#include <kv/constants.hpp> #include <kv/interval.hpp> #include <kv/rdouble.hpp> #include <kv/dd.hpp> #include <kv/rdd.hpp> int main() { std::cout.precision(17); std::cout << kv::constants<double>::pi() << "\n"; std::cout << kv::constants<double>::e() << "\n"; std::cout << kv::constants<double>::ln2() << "\n"; std::cout << kv::constants<double>::str("1.18") << "\n"; std::cout << kv::constants< kv::interval<double> >::pi() << "\n"; std::cout << kv::constants< kv::interval<double> >::e() << "\n"; std::cout << kv::constants< kv::interval<double> >::ln2() << "\n"; std::cout << kv::constants< kv::interval<double> >::str("1.18") << "\n"; std::cout << kv::constants< kv::interval<double> >::str("1.18", "1.19") << "\n"; std::cout.precision(34); std::cout << kv::constants<kv::dd>::pi() << "\n"; std::cout << kv::constants<kv::dd>::e() << "\n"; std::cout << kv::constants<kv::dd>::ln2() << "\n"; std::cout << kv::constants<kv::dd>::str("1.18") << "\n"; std::cout << kv::constants< kv::interval<kv::dd> >::pi() << "\n"; std::cout << kv::constants< kv::interval<kv::dd> >::e() << "\n"; std::cout << kv::constants< kv::interval<kv::dd> >::ln2() << "\n"; std::cout << kv::constants< kv::interval<kv::dd> >::str("1.18") << "\n"; std::cout << kv::constants< kv::interval<kv::dd> >::str("1.18", "1.19") << "\n"; }のような使い方をすることが出来る (test-constants.cc)。 型に応じたpiやeやlog2を得るために作成したものだが、関数strによって、 文字列から定数を作成できる。また、interval型については、文字列を2つ指定して 区間定数を生成することも出来る。 これを利用して、少し冗長になるが、
#include <kv/allsol.hpp> #include <kv/dd.hpp> #include <kv/rdd.hpp> namespace ub = boost::numeric::ublas; struct Func1 { template <class T> ub::vector<T> operator() (const ub::vector<T>& x){ ub::vector<T> y(2); static T c1 = kv::constants<T>::str("1.18"); y(0) = c1 * x(0) * x(0) + x(1) * x(1) - 1.; y(1) = x(0) - x(1); return y; } }; int main() { ub::vector< kv::interval<kv::dd> > I; std::cout.precision(34); I.resize(2); I(0) = kv::interval<kv::dd>(-10., 10.); I(1) = kv::interval<kv::dd>(-10., 10.); kv::allsol(Func1(), I); }のように書くことが出来る。こう書けば、