#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);
}
のように書くことが出来る。こう書けば、