📝

C++で初等関数を扱う(クラスの使用法)

2024/08/08に公開

はじめに

C++ですべての数学初等関数を扱うためのクラスSpFuncを実装して公開したので、この記事ではその使い方を紹介していきます。実装経緯や実装手法は別記事の方で書いています。

別記事: SpFuncクラスの実装経緯と実装手法

コード:ヘッダファイルとソースファイル

クラスの概要

前項で書いたように今回作成したクラスSpFuncは初等関数を統一的に扱うことを目的として作成しており、多変数関数の生成・代入操作・解析的偏微分を、C++上で数学表記に近い形で書いて行うために実装しました。

使用も改変も自由にして頂いて大丈夫ですが、もしバグっぽい挙動が起きたら教えて頂けると幸いです。

使用のための準備

本コードはstd::variantを使っているので、C++17以降でないと動作しません。もしCUDA C++で使いたければコンパイルオプションに「-std c++17」が必要だったと思います。

SpFuncクラスを使う場合はまずヘッダファイル SuperFunc.hpp をインクルードしてください。

#include "SuperFunc.hpp"

またこれは必須ではないですが、SpFuncは名前空間spfunc内で定義されているので

using namespace spfunc;

と書いておくと楽です

関数の生成

関数を生成するときには基本的に数値と変数を組み合わせて表現します。数値はdouble型で、変数はVariableというenum class型で定義されています。そのため変数を宣言して使いたい場合は

auto x = spfunc::Variable::_X;
auto t = spfunc::Variable::_A;

のように書きます。あらかじめ用意しているのは _A,_B,_C,_D,_E,_X,_Y,_Z の8種類なので、9変数以上使いたい場合はヘッダファイルに適宜追加して使ってください。

また初等関数表現のため指数関数・対数関数・三角関数・逆三角関数・双曲線関数・逆双曲線関数計14種類が用意されています。

SpFunc exp(const SpFunc&); //自然指数関数
SpFunc log(const SpFunc&); //自然対数関数
SpFunc sin(const SpFunc&); //正弦関数
SpFunc cos(const SpFunc&); //余弦関数
SpFunc tan(const SpFunc&); //正接関数
SpFunc asin(const SpFunc&); //逆正弦関数
SpFunc acos(const SpFunc&); //逆余弦関数
SpFunc atan(const SpFunc&); //逆正接関数
SpFunc sinh(const SpFunc&); //双曲線正弦関数
SpFunc cosh(const SpFunc&); //双曲線余弦関数
SpFunc tanh(const SpFunc&); //双曲線正接関数
SpFunc asinh(const SpFunc&); //逆双曲線正弦関数
SpFunc acosh(const SpFunc&); //逆双曲線余弦関数
SpFunc atanh(const SpFunc&); //逆双曲線正接関数

では実際に関数を生成するときですが、算術演算子を用いて定数・変数・関数を結合することで表現します。具体例を見た方が早いと思うのでx^{1-0.5\sin{y}}を生成したい場合のコード例を示します。

spfunc::SpFunc f = (spfunc::Variable::_X)^(1. - 0.5 * spfunc::sin(spfunc::Variable::_Y));

これは一行で書きましたが、数行に分けて書くと以下のように書けます。

using namespace spfunc;
auto x = Variable::_X;
auto y = Variable::_Y;
SpFunc f = x ^ (1.0 - 0.5 * sin(y));

ここで本来^は排他的論理和を表すbit演算子ですが、冪乗を表す演算子として定義しています。ただしそのせいで優先順位が加減算より下です。また+=,-=,*=,/=,^=のような複合演算子も定義されています。

1つ注意点として

using namespace spfunc;
auto x = Variable::_X;
SpFunc g = (x ^ 2.0)^(0.5);

のように関数g = (x^2)^{0.5} を生成した場合、内部処理のタイミングで自動的にg = xに置き換えられてしまってg(-1.0)=-1.0 となってしまう可能性があるので、指数部が整数でない冪乗を利用する場合、底が負の数のときに想定外の挙動をとる場合があります。

数値の代入

代入の仕方の前に変数群VarTableというものの表記法について書きます。

SpFuncでは多変数関数を扱えるので、各変数の値をまとめておくためにVarTableを使います。VarTableはstd::map<Variable,double>のエイリアスなので、std::mapの表記法がそのまま使えます。例えばx=0.5,y=-1.3を表す変数群を表現する場合は次のようになります。

spfunc::VarTable data1{
	{spfunc::Variable::_X,0.5},
	{spfunc::Variable::_Y,-1.3},
};

またVarTable同士の加減算、定数倍にも対応しているので、変数ベクトルのように扱えます。

代入操作を行うには、operator()に引数としてVarTableを渡すことで、その値を各変数に代入した計算結果がdouble型で返ってきます。

using namespace spfunc;
auto x = Variable::_X;
auto y = Variable::_Y;

spfunc::VarTable data1{
   {x,0.5},
   {y,-1.3},
};
 
SpFunc f = x ^ (1.0 - 0.5 * sin(y));
f(data1) // (=0.358047…)

SpFuncクラスが引数に渡していない変数を持っていた場合、未定義動作となります。

ただし1変数関数で毎回VarTableを用意するのは面倒なので、operator()に引数としてdouble型を渡すと変数にその値を代入して計算します。多変数関数の場合にはすべての変数に引数を代入した場合の計算結果が返ってきます。


using namespace spfunc;
auto x = Variable::_X;
auto y = Variable::_Y;
 
SpFunc f = x ^ (1.0 - 0.5 * sin(y));
f(2) // (=1.45938…)

代入操作の際の計算は関数が複雑であるほど計算回数が増えるために誤差が増えます。指数関数・三角関数といった各種数学関数は標準ライブラリの<cmath>を用いて計算しているので、計算精度はそちらと同じです。

その他の処理

1. 導関数

SpFuncクラスはすべて解析的偏微分が行えます。メンバ関数Diff()に引数として変数Variableを渡すと、その変数で偏微分した導関数が戻り値として返ってきます。

using namespace spfunc;
auto x = Variable::_X;
auto y = Variable::_Y;
 
SpFunc h1 = (2.0*x + y)^2;
SpFunc h2 = h1.Diff(x); // ( =4.0*(2.0*x+y) )

2. 誤差計算

n変数関数F(x_1,x_2,\ldots,x_n)について、x_i=x_{i0} \pm \sigma_i (i=1,2,\ldots,n \quad \sigmaは誤差)のときのFの絶対誤差は

\sigma _F = \left. \sqrt{\sum _i \left(\sigma_i \frac{\partial F}{\partial x_i} \right)^2} \right|_{x_i=x_{i0}(i=1,2,\ldots,n)}

で与えられます。

AbsError(),RelError()を用いるとこの式を用いてそれぞれ絶対誤差,相対誤差を求めることができます。第1引数は多変数関数F,第2引数は各変数の値を表すVarTable x_{i0},第3引数は各変数の絶対誤差を表すVarTable \sigma _i です。

using namespace spfunc;
auto x = Variable::_X;
auto y = Variable::_Y;
auto z = Variable::_Z;
 
SpFunc F = (x + z)/(x + y);
VarTable maindata{
   {x,5.0},
   {y,1.0},
   {z,2.0},
};
VarTable errdata{
   {x,0.2},
   {y,0.1},
   {z,0.15},
};
std::cout << "AbsError : " << AbsError(F,maindata,errdata) << std::endl;
std::cout << "RelError : " << RelError(F,maindata,errdata) << std::endl;

3. 関数表示

SpFuncクラスでは内部的に関数を逆ポーランド記法で管理しており、RPN_Output()に引数としてSpFuncを渡すと、std::string型で関数の逆ポーランド記法表示が返ってきます。

using namespace spfunc;
auto x = Variable::_X;
auto y = Variable::_Y;
 
SpFunc f = x ^ (1.0 - 0.5 * sin(y));
std::cout << RPN_Output(f) << std::endl; // ( X 1 Y sin 0.5 * - ^ )

4. 関数群

多変数から多変数への写像を考えるとき、その関係は

\bm{x}=(x_1,x_2,\ldots,x_m) \longrightarrow \bm{y}=( y_1,y_2,\ldots,y_n )

\bm{y} = (f_1(\bm{x}),f_2(\bm{x}),\ldots,f_n(\bm{x}))

と表せるので、f_1,f_2,\ldots,f_nを関数群として1つにまとめると、多変数関数の写像関係を表すのに便利です。そこで関数群を扱うためのクラスFuncGroupを用意しました。変数群VarTableはエイリアスでしたが、こちらはstd::map<Variable,SpFunc>をpublic継承したものなので、std::mapと微妙に挙動が違います。基本的には上の関係式のy_iをキー、f_iを要素として格納し、operator()に引数としてVarTable \bm{x}を渡すと、\bm{y}が戻り値としてVarTableで返ってきます。

例として関数群FuncGroupを表現行列のように利用すると次のようになります。

using namespace spfunc;
auto x = Variable::_X;
auto y = Variable::_Y;
auto z = Variable::_Z;

SpFunc f1 = x + y + z;
SpFunc f2 = x + y - 2.0*z;
SpFunc f3 = x - y;
VarTable data3{
   {x,1.0},
   {y,2.0},
   {z,3.0},
};
FuncGroup G;
G[x] = f1; G[y] = f2; G[z] = f3;
VarTable data4 = G(data3);

おわりに

今回作ったクラスSpFuncはプログラミング上で多くの関数を簡単に取り扱うために作ったので、汎用性は高いと思います。関数を扱うときに一人でも多くの人に使って頂ければ幸いです。

Discussion