GiNaCライブラリの使い方

といった C 言語標準の数学関数が ex 型に拡張されているが、ex 型
を返すためには引数に ex 型を与える必要がある。つまり例えば、
公開コピー誌
ex eq=1+sqrt(ex(4));
GiNaC ライブラリの使い方
cout << eq <<endl;
というふうに ex 型のコンストラクタで数値を与える。std::string
暗黒通信団
型で与えた式を使えるというのは大きく、例えば std::cin あたりか
ら読んだ文字列を str() メンバで std::string にすれば、すぐさま
GiNaC は C
用の数式処理ライブラリである。ライセンスは
電卓のできあがりだ。パーサを書いたことある人ならばこれだけで
++
GPL なのでフリーである。もとは素粒子物理学の人々が作ったも
もかなり感動モノである。
の*1 なので物理で使うような機能もあるが、Maple *2 の代替となるこ
GiNaC は文字変数も扱える。以下の例は
√
ab+0.3b を定義してか
とを目的に開発されているというあたり、特に物理のことは考えなく
ら b = 2 のときの値を出力する。この例以降、ヘッダと namespace
ても単なる数式処理ツールとして十分に使える。本稿は GiNaC に感
と int main() は省略する。
動した筆者が、日本語文献のほとんどない状況を悲しみ、普及と啓蒙
symbol a("a"), b("b");
のために書いたものである。ただしテンソルやクリフォード代数や、
ex eq=sqrt(a*b)+0.3*b;
その他の物理に特化した機能については書かない。また行列について
cout << eq.subs(b==2) <<endl;
は GiNaC 固有の行列実装よりも Eigen などの汎用テンプレートラ
symbol で文字変数を定義し、ex で式を定義し、ex の各メンバ関数
イブラリと組み合わせたほうが自由度が高いので、そちらを参照され
で微分したり数値代入したりするというのが GiNaC における定番の
たい。Ubuntu/Debian 系であれば標準で提供されており「apt-get
使い方である。次節からそれぞれについて詳述する。ちなみに、本稿
install libginac-dev」だけでインストールできる。このライブ
にでてくるクラスは以下である。他にも式を構成するための様々な
ラリを利用するときのコンパイルオプションは g++、clang++ であ
オブジェクトがあるがそれは割愛する*3 。GiNaC を使うには知って
れば-lginac -lcln である。両方つけなくてはいけない。また、完
おいたほうがよい。
全な (英語の) マニュアルは HTML と PS と PDF で提供されてお
• numeric: 数値
り http://www.ginac.de/tutorial/から取得できる。特に PDF
• symbol: 変数
マニュアルは http://www.ginac.de/tutorial.pdf である。
• lst: リスト
• ex: 式
1 基本的な使い方
●
2 数値
●
文字列を動的に解釈して計算するコードを書くには、古くから yacc
などのパーサジェネレータが使われてきた。しかし数式に限ってい
えば、GiNaC で std::string 型で与えた式をそのまま計算するこ
1 定数
◆
とができる。以下はそのサンプルである。
定数は正確に言えば constant(定数クラス)なのだが、この型は意
#include <iostream>
識しなくてもよい。以下の三つの定数が定義されている。
#include <string>
• Pi 円周率
#include <ginac/ginac.h>
• Catalan カタラン定数 ∼ 0.91596559
using namespace std;
• Euler オイラー定数
using namespace GiNaC;
int main(){
2 numeric 型
◆
string st("2+3*sqrt(4)");
数値の設定にはいくつかの方法がある。
ex sample(st, 0);
cout << sample <<endl;
• 厳密な整数の定義:numeric p=2;
return 0;
• 有理数の定義:numeric p(2,3);
ちなみに numeric p(3/2) では先に 3/2 が int 型として評
}
価されてしまうのでダメだ。この場合は numeric(1)/2 なら
式は ex という型で表現する。sqrt のほか log や exp や sin や cos
適切に設定できる。
*1
チュートリアルにはエルミート多項式の計算が出ていたりするが、民間人には難しすぎると思う。
有料の数式処理ソフト。Mathematica みたいなもの。
*3 多項式を構成するための、加算や乗算や比較演算子や累乗の結果を格納するオブジェクト、組み込みの数学関数もオブジェクトとして実装されている。
*2
1
• 浮動小数点:numeric p(2.71828);
• real(z) 実数分を取り出す。
• 文字列からの浮動小数点:numeric p="3.14159";
• imag(z) 虚数部分を取り出す。
• csgn(z) 複素数かどうか。返り値は int 型。
p("1.08410151E-2") といった指数表記も使える。
• 複素数の設定:numeric p=2-3*I;
• step(x) 階級化。返り値は numeric 型。
整数計算精度は、例えば Digits = 60; と指定すれば 60 桁で処理さ
• numer(z) 有理数の分子を取り出す。
れる。これはグローバル変数である。数値を出力するには cout <<
• denom(z) 有理数の分母を取り出す。
evalf(p) <<endl; か、cout << p.evalf() <<endl; とする。
• Li2(z) 対数積分関数
• zeta(z) リーマンのζ関数
通常の基本型数値への変換は以下のメンバ関数を使う。なお、虚数
• tgamma(z) ガンマ関数
部は無視される。
• numeric::to int();
• lgamma(z) 対数ガンマ関数
• numeric::to long();
• psi(z) ψ 関数
• numeric::to double();
• psi(n, z) ψ 関数
• factorial(n) 階乗 (n!)
実際の使用例をあげておく。まずはまどろっこしい例。
ostringstream tmp;
• doublefactorial(n) n!!
siki.subs(la==x).print(print_csrc_double(tmp,0),0);
• binomial(n, k) 二項係数
double data=atof(tmp.str().c_str());
• bernoulli(n) ベルヌーイ数
• fibonacci(n) フィボナッチ吸う
次はいくぶんエレガントな例である。
symbol x("x");
• irem(a, b) 剰余
ex e = series(sin(x/Pi), x==0, 6);//6 次まで展開
• iquo(a, b) 整数商を返す。
ex f = evalf(e.subs(x == 0.1));//0.1 のときの数値
• gcd(a, b) 最大公約数を返す。
if (is_a<numeric>(f)){//数値チェックが必要
• lcm(a, b) 最小公倍数を返す。
numeric 型の数値についているメソッドは次の通りである。
double d = ex_to<numeric>(f).to_double();
• .is zero()
}
• .is positive() 実数値かつ 0 以上かどうか
numeric 型には次の関数が定義されている。意味が C 言語と同じ
• .is negative() 実数値かつ 0 未満かどうか
ものはコメントを割愛する。
• pow(a, b)
• .is integer() 実数整数かどうか
• sin(z)
• .is pos integer()
• cos(z)
• .is nonneg integer()
• tan(z)
• .is even()
• asin(z)
• .is odd()
• acos(z)
• .is prime()
• atan(z)
• .is rational()
• atan(y, x)
• .is real()
• sinh(z)
• .is cinteger() 複素整数かどうか
• cosh(z)
• .is crational() 複素有理数かどうか
• tanh(z)
• asinh(z)
3 記号変数
●
• acosh(z)
変数の宣言は symbol である。式の中で使うシンボルはコンストラ
• atanh(z)
クタで文字列として与える。つまり symbol X("x")l と宣言すると、
• exp(z)
式中の x という記号が変数と見なされる。もしも単に symbol X と
• log(z)
だけ宣言すると、文字列は symbolXXX という名前が指定されたもの
• abs(z)
として内部的に勝手に割り振られる。逆に配列などで名前なしで記
• mod(a, b)
号だけを定義することもできる。
• sqrt(z)
実は GiNaC は出力を LATEX 形式にすることができるが、その際
• isqrt(n) 整数平方根
LATEX 用に使う文字列をコンストラクタの第二引数に与えることが
• inverse(z) 1/z を返す。
2
できる。つまり symbol a("a","\alpha") とすれば、数式入力中で
}
は「a」を使い、印刷出力では「α」にしたりすることができる。小学
これを使って式を std::cout に渡すことができる。
校算数の課題を作るには symbol x("x", "
std::copy(e.begin(), e.end(),
Box") という変換もよい。
std::ostream_iterator<ex>(cout, "\n"));
symbol 宣言で作られた変数は(数値と同様に)複素数として扱わ
ex 型は std::vector に入れることもできる。しかし式の大小関
れる。もし実数として扱う変数を作るのなら realsymbol x("x")
係が定義されていないので std::set や std::map に入れることは
とする。さらに正の数として定義するなら possymbol x("x") で
できない。式表現において累乗は使えるが、2*x^2 は (2x)2 と解釈
ある。
される。
式について様々な処理をする関数群が用意されている。例えば偏
微分 (diff) や有理化 (normal)、式の簡約 (simplify_indexed) な
4 リスト
●
どである。Mathematica とまではいかないが、これらを組み合わせ
lst 型は複数のデータをまとめて一つにする型である。ex 型や
てある程度の数式処理系を作ることができる。簡約化などを使えば、
symbol 型を含めることができる。イテレータを使って逐次処理をす
例えば乱数をふって問題を出し、答えを計算することで正解かどうか
ることもできる。
自動評価するような教育ソフトも作れるだろう。
よく使う例としては、複数の変数をまとめて ex に与えるために
使う。
2 拡張された数学関数
◆
string st("2*a+3*sqrt(4)*b");
ex sample2(st, lst(a,b));
• abs(x)
cout << sample2 <<endl;
lst は動的に構成することができる。以下は lst 操作のための基本
• step(x) step function
的なメンバ関数である。
• csgn(x) 複素数かどうか
• 最後尾に追加:append()
• conjugate(x) 複素共役
無名の記号を追加するには append(symbol("x")) などと
• real part(x) 実数部
する。
• imag part(x) 虚数部
• 前に追加:.prepend()
• sqrt(x)
• 最初の要素を削除:.remove first()
• sin(x)
• 最後の要素を削除:.remove last()
• cos(x)
• 全クリア:.remove all()
• tan(x)
• ソート:.sort()
• asin(x)
• ユニーク:.unique()
• acos(x)
• 要素数を返す:.nops()
• atan(x)
• 要素の中身を返す:.op()
• atan2(y, x)
• sinh(x)
例えば 2 番目の要素の中身を返すなら l.op(2) である。
• 要素に中身を設定:.let op()
• cosh(x)
例 え ば l.let op(1)=7 と い う ふ う に 使 う 。ち な み に
• tanh(x)
l[1]=42 というふうに直接アクセスすることもできる。
• asinh(x)
• acosh(x)
• atanh(x)
5 式とその処理
●
• exp(x)
• log(x)
1 全般
◆
• eta(x,y) イータ関数 log(xy) − log(x) − log(y)
• Li2(x) 対数積分関数
式は ex 型である。式は数値や変数や関数から成る。こうした式の
• Li(m, x) 多重対数関数
各要素は lst 同様にイテレータで順に参照することができる。
• G(a, y) 多重対数関数
for(auto i = e.begin();i!=e.end();++i){
• S(n, p, x) ニールセンの一般化多重対数関数
cout << *i << endl;
• H(m, x) 調和多重対数関数
3
• zeta(m) リーマンの ζ 関数
次は通常関数による expand と collect() による整列の例である。
• zeta(m, s) オイラー和
ex e=expand((sin(x)+sin(y))*(1+p+q)*(1+d));
• zetaderiv(n, x) 微分 ζ 関数
collect(e,{p,q});
• tgamma(x) ガンマ関数
collect(e,find(e,sin(\$1)));
• lgamma(x) 対数ガンマ関数
collect_common_factors(a*x+a*y);
• beta(x, y) β 関数
collect_common_factors(
• psi(x) ψ 関数
a*(b*(a+c)*x+b*((a+c)*x+(a+c)*y)*y)
• factorial(n) 階乗 (n!)
);
• binomial(n, k) 二項係数
degree() は最大次数を返し、ldegree() は最小次数*4 を返す。式
• Order(x) 多項式展開のオーダー項
p の次数 n の項の係数は coeff(p, int n) で求める。次の例はシ
ンプルで分かりやすい。
symbol x("x"), y("y");
3 代入と零点計算
◆
ex ptmp=4*pow(x,3)*y+5*x*pow(y,2)
代入は p.subs(f,x==2) などとする。複数の数値を設定するには
+3*y-pow(x+y,2)+2*pow(y+2,2)-8;
lst で与える。
ex p=ptmp.expand();
ex e=x*y+x;
for(int i=p.ldegree(x);i<=p.degree(x);++i){
cout << e2.subs(lst(x==-2,y==4))<<endl;
cout <<p.coeff(x,i)<< endl;
subs に よ る 複 数 の 値 設 定 は パ ラ レ ル に 動 く の で
subs(lst(x==y,y==x)) というのは記号 x と y を交 換すること
5 微分と積分と級数展開
◆
になる。一般的に記号置換にこの方法が使える。
GiNaC の微分は基本的に偏微分である。
記号的に式を解くには、例えば lsolve(1+x*3==5,x) と書く。数
値的に解くには fsolve(x==cos(x),x,0,2) と書く。連立方程式を
symbol x("x"), y("y"), z("z");
解くには lst で式と変数を与える。
ex p=pow(x,5)+pow(x,2)+y;
symbol a("a"), b("b"), x("x"), y("y");
cout << p.diff(x,2) << endl;//20*x^3+2
lst eqns = a*x+b*y==3, x-y==b;
cout << p.diff(y) << endl;// 1
lst vars = x, y;
cout << p.diff(z) << endl;// 0
ちなみに積分は integral(x, 0, 1, x*x) などとするが、多項式
cout << lsolve(eqns, vars) << endl;
の積分しかできない。
//x==(3+b^2)/(b+a),y==(3-b*a)/(b+a)
級 数 展 開 は series() を 使 う 。例 え ば ex gamma = 1/sqrt(1
中カッコでまとめれば lsolve(a*x+b*y==3,x-y==b,x,y) と直接書
- pow(v/c,2)) と い う 式 を v
いてもよい。
=
0 で 6 項まで展開
す る た め に ex gamma.series(v==0, 6) と す る 。あ る い
は series(gamma,v==0, 6) で も よ い 。ど ち ら に せ よ 結 果
4 展開と並べ替え
◆
は 1+(1/2*c^(-2))*v^2+(3/8*c^(-4))*v^4+Order(v^6) と な
expand() と collect() が 基 本 で あ る 。expand() に は オ プ シ
る 。オ ー ダ ー 項 は 通 常 邪 魔 な の で series to poly() を
ョンが 2 つある。expand options::expand transcendental と
使 っ て オ ー ダ ー 項 を 削 除 す る 。す な わ ち 例 え ば ex a =
expand options::expand function args である。実例を見るの
series to poly(atan(x).series(x,3)) などとする。
がよい。
ex e=exp(pow(x+y,2));
6 整数論的処理
◆
cout << e.expand() << endl; //exp((x+y)^2)
cout << e.expand(
• 因数分解
expand_options::expand_transcendental
factor(e) とする。expand してからでないとまともに動か
) << endl; //exp((x+y)^2)
ない。
cout << e.expand(
• 無平方分解
expand_options::expand_function_args
sqrfree(e, lst(x,y))
) << endl; //exp(2*x*y+x^2+y^2)
*4
次数 0 は 0 である。
4
な ど と す る 。sqrfree(e,
lst(y,x)) とすれば結果は変わる。
for(uint i=0;i<mdim;i++){
• 最小公倍数、最大公約数
for(uint j=0;j<mdim;j++){
gcd(e1, e2) および lcm(e1, e2) である。
m(i,j)=ex("0",vals);
• 終結式
}}
ex e1 = x+pow(y,2), e2 = 2*pow(x,3)-1, r;
//cout <<"Sylvester’s metrix"<<endl;
ex r=resultant(e1, e2, x); //1+2*y^6
for(uint i=0;i<dim-1;i++){
r = resultant(e1, e2, y);
for(uint j=i;j<dim+1+i;j++){
//1-4*x^3+4*x^6
• 多項式の割り算
ostringstream tmp2; tmp2 << "a" << dim-j+i;
a = b × quo(a, b, x) + rem(a, b, x) という関数で求
m(i,j)=ex(tmp2.str(),vals);
める。割り切れるかどうかの判定は divide(a, b, q) で
}}
bool 値が返る。
for(uint i=0;i<dim;i++){
for(uint j=i;j<dim+i;j++){
7 行列
◆
ostringstream tmp2;
GiNaC にも固有の行列実装があるが、実際には Eigen などの汎用テ
tmp2 << dim-j+i << "*a" << dim-j+i;
ンプレートライブラリに入れたほうが統一的に扱える。本稿は Eigen
m(i+dim-1,j)=ex(tmp2.str(),vals);
の解説ではないので実例のみを紹介する。以下は Sylvester 行列を計
}}
算することで n 次関数の判別式を求める完全なプログラムである。n
ex det(m.determinant());
は 4 か 5 くらいまでにしておかないと指数爆発する。
cout << det << endl;
#include <ginac/ginac.h>
return 0;
#include <eigen3/Eigen/Core>
}
#include <eigen3/Eigen/Eigen>
#include <iostream>
#include <fstream>
6 出力
●
#include <sstream>
通常の出力は std::cout で良い。std::ostream の出力なので任意
のファイルに振り向けることもできる。注意点としては pow(x,2)
は x^2 と表示される点である。
using namespace std;
GiNaC では LATEX 形式、数値表現、内部表現の出力もできる。
using namespace Eigen;
• cout << latex:出力を LATEX 形式に変更する。string 型
using namespace GiNaC;
を ex 型を経由して LATEX 形式に出力すれば、GiNaC を C++
における LATEX コンバータとして使える。
int main(int ac, char *av[]){
• cout << tree:内部表現形式に変更する。デバッグ時など、
if(ac<2){cout << "./a.out dim\n";return -1;}
中で何が行われているかを見るには良い。
uint dim=atoi(av[1]);
• cout << csrc float:出力形式を float 型に。
uint mdim=2*dim-1;
• cout << csrc double:出力形式を double 型に。
lst vals;
• cout << csrc cl N:出力形式を CLN の整数型に。
for(uint i=0;i<=dim;i++){
• cout << dflt:出力形式を元に戻す。
ostringstream tmp1; tmp1 << "a" << i;
GiNaC ライブラリの使い方
vals.append(symbol(tmp1.str()));
2016 年 4 月 1 日 PDF 版発行
著 者 シ
発行者 星野 香奈 (ほしの かな)
発行所 同人集合 暗黒通信団(http://ankokudan.org/)
〒277-8691 千葉県柏局私書箱 54 号 D 係
頒 価 0 円
}
Matrix<ex, Dynamic, Dynamic> m;
∑
∞
·`·
m.resize(mdim, mdim);
ミスがあれば指摘下さい。
c
⃝Copyright
2016 暗黒通信団
5
Printed in Japan