リスト,配列,式の扱い方
Wolfram Symbolic Transfer Protocol (WSTP)を利用して,あらゆる型のデータを外部プログラムと交換することができる.非常に基本的な型については,WSTPテンプレートファイル中の:ArgumentTypes:や:ReturnType:を適当に設定すればよい.
Wolfram言語の仕様 |
Cの仕様
| |
Integer | 整数 | int |
Real | 浮動小数点数 | double |
IntegerList | 整数のリスト | int*,long |
RealList | 浮動小数点数のリスト | double*,long |
String | 文字列 | char* |
Symbol | シンボル名 | char* |
Manual | WSTPルーチンを直接呼び出す | void |
:Begin:
:Function: h
:Pattern: h[a_List]
:Arguments: {a}
:ArgumentTypes: {IntegerList}
:ReturnType: Integer
:End:
int h(int *a, long alen) {
int i, tot=0;
for(i=0; i<alen; i++)
tot += a[i];
return tot;
}
パターンはマッチするが,リストの要素の外部コードの期待するタイプではない.そのため$Failedが返っている:
:ArgumentTypes: {IntegerList, RealList, Integer}
void f(int *a, long alen, double *b, long blen, int c)
WSPutInteger32(stdlink,int i) | 整数を1個送信 |
WSPutReal64(stdlink,double x) | 浮動小数点数を1個送信 |
WSPutInteger32List(stdlink,int*a,int n) | |
a から始まる n 個の整数のリストを送信 | |
WSPutReal64List(stdlink,double*a,int n) | |
a から始まる n 個の浮動小数点数のリストを送信 | |
WSPutInteger32Array(stdlink,int*a,int*dims,NULL,int d) | |
次元 dims,深さ d のリストとして配列を送信 | |
WSPutReal64Array(stdlink,double*a,int*dims,NULL,int d) | |
浮動小数点数の配列を送信 | |
WSPutString(stdlink,char*s) | 文字列を送信 |
WSPutSymbol(stdlink,char*s) | シンボル名を送信 |
WSPutFunction(stdlink,char*s,int n) | |
頭部が s,引数 n 個の関数の送信を開始 |
WSTPテンプレートファイルを使用する場合にmprepやmccが実際に行うことは,WSTPライブラリ関数を明示的に呼び出すCプログラムを生成することである.そのCプログラムのコードを読めば,WSTPライブラリ関数を直接使用する方法の例が見られる.mccを使う場合,-gオプションを忘れないようにする.そうしないと生成されたソースコードは自動的に消去されてしまう.
もし,外部関数が整数か浮動小数点数を1つだけ返すようなものなら,WSTPテンプレートファイル中の:ReturnType:にIntegerあるいはRealを与えるだけでよい.しかし,Cのメモリアロケーション/デアロケーションの仕方が原因して,:ReturnType:にIntegerListやRealListを与えることはできない.そのような構造体を返すようにしたいなら,WSTPライブラリ関数を明示的に呼び出すCプログラムを書く必要がある.この場合,:ReturnType:はManualを指定する.
:Begin:
:Function: bits
:Pattern: bits[i_Integer]
:Arguments: {i}
:ArgumentTypes: {Integer}
:ReturnType: Manual
:End:
void bits(int i) {
int a[32], k;
for(k=0; k<32; k++) {
a[k] = i%2;
i >>= 1;
if (i==0) break;
}
if (k<32) k++;
WSPutInteger32List(stdlink, a, k);
return ;
}
int a[8][16][100];
int dims[] = {8, 16, 100};
WSPutInteger32Array(stdlink, a, dims, NULL, 3);
いかなるWolfram言語の式も,WSTP関数で作ることができる.基本的に,作成したいWolfram言語式のFullForm(完全形)に直接相当するWSTP関数を順に呼び出せばよい.
2つの引数のWolfram言語関数Plusを設定する:
WSPutFunction(stdlink, "Plus", 2);
WSPutInteger32(stdlink, 77);
WSPutSymbol(stdlink, "x");
一般に,まず,WSPutFunction()を呼び,作成したいWolfram言語関数の頭部とその関数が引数をいくつ取るかを与える.その後,もうひとつ,WSTP関数を呼んで関数の引数を順に設定する.Wolfram言語の式の一般的な構造と頭部の概念については「式」で解説している.
WSPutFunction(stdlink, "List", 2);
WSPutInteger32List(stdlink, r, 10);
WSPutFunction(stdlink, "List", 2);
WSPutReal64(stdlink, 4.5);
WSPutInteger32(stdlink, 11);
WSPutInteger32Array()とWSPutReal64Array()とを使えば,Cが前もって一次元的に割り付けたメモリ上の配列を送信することができる.しかし,Cプログラムの実行中に作った配列は,ネストされたポインタの集まりであることが普通である.そのような配列をWolfram言語に送信するには,WSPutFunction()を何度か呼び,最後にWSPutInteger32List()を呼べばよいだろう.
int ***a;
WSPutFunction(stdlink, "List", n1);
for (i=0; i<n1; i++) {
WSPutFunction(stdlink, "List", n2);
for (j=0; j<n2; j++) {
WSPutInteger32List(stdlink, a[i][j], n3);
}
}
WSTP関数を利用して作成した式はWolfram言語に送信された直後に評価されることに注意する.このことは,例えば,Wolfram言語に送信した配列を転置したいとき,配列を表す式をTransposeで囲むだけでよいことを意味する.すなわち,配列を表す式を生成する前に,WSPutFunction(stdlink,"Transpose",1);を呼び出すだけでよい.
Flattenはリストのネストを外す:
Sequenceは自動的にネストを外す:
WSPutInteger32List()を呼び出すには,送信したいリストの長さを知る必要がある.しかし,ネストされたSequenceの列を作成することにすれば,リスト全体の長さを送信前に知る必要がなくなる.
結果をListで囲んでおく:
WSPutFunction(stdlink, "List", 1);
while( 条件 ) {
/* 要素を生成 */
次のレベルのSequenceを作成する:
WSPutFunction(stdlink, "Sequence", 2);
WSPutInteger32(stdlink, i );
}
最後のSequenceオブジェクトを閉じる:
WSPutFunction(stdlink, "Sequence", 0);
WSGetInteger32(stdlink,int*i) | 整数を取得し,アドレス i に格納する |
WSGetReal64(stdlink,double*x) | 浮動小数点数を取得し,アドレス x に格納する |
WSTPは,外部プログラムからWolfram言語へデータを送信するWSPutInteger32()のような関数を提供する.また,WSTPにはWolfram言語のデータを外部プログラムに渡すためのWSGetInteger32()のような関数も用意されている.
:Begin:
:Function: f
:Pattern: f[i_Integer, x_Real, y_Real]
:Arguments: {i, x, y}
:ArgumentTypes: {Integer, Manual}
:ReturnType: Real
:End:
double f(int i) {
double x, y;
WSGetReal64()を呼んでリンクからデータを明示的に取ってくる:
WSGetReal64(stdlink, &x);
WSGetReal64(stdlink, &y);
return i+x+y;
}
WSGetInteger32(link,pi)のようなWSTP関数は標準的なCのライブラリ関数fscanf(fp,"%d",pi)とほとんど同じように動作する.最初の引数はどのリンクからデータを取得するかを示し,最後の引数は受け取ったデータをどのアドレスに格納するかを示している.
WSCheckFunction(stdlink,"name",int*n) | |
関数の頭部をチェックし,その関数がいくつの引数を持っているかを保存する |
:Begin:
:Function: f
:Pattern: f[a:{___Integer}]
:Arguments: {a}
:ArgumentTypes: {Manual}
:ReturnType: Integer
:End:
int f(void) {
int n, i;
int a[MAX];
WSCheckFunction(stdlink, "List", &n);
for (i=0; i<n; i++)
WSGetInteger32(stdlink, a+i);
ほとんどの場合,Wolfram言語側で外部プログラムに送るデータが期待通りの構造かどうかを確認することができる.WSCheckFunction()の戻り値は一般に,データが指定された名前の関数からなる場合にのみ,MLSUCCESSとなる.
ネストされたリストやその他のオブジェクトを受信することは,WSCheckFunction()を適切な順番で呼び出すことで可能である.
WSGetInteger32List(stdlink,int**a,int*n) | |
整数のリストを取得し,それを格納するに十分なメモリを割り付ける | |
WSGetReal64List(stdlink,double**a,int*n) | |
浮動小数点数のリストを取得する | |
WSReleaseInteger32List(stdlink,int*a,int n) | |
整数リストに関連したメモリを解放する | |
WSReleaseReal64List(stdlink,double*a,int n) | |
浮動小数点数リストに関連したメモリを解放する |
外部関数がWolfram言語からデータを取得するとき,データを保管する場所を確保しなければならない.もし,そのデータが整数1個であったら,WSGetInteger32(stdlink,&n)のように,整数がintnの場所を使うように宣言するだけで十分である.
WSGetInteger32List(stdlink,&a,&n)は自動的にこのメモリ割付けを実行して,a をその割付けの結果を指すポインタとする.WSGetInteger32List()のような関数で割り付けられたメモリはすべて特殊な領域に配置される.そのメモリを修正したり,直接解放することはできない.
int f(void) {
int n;
int *a;
WSGetInteger32List(stdlink, &a, &n);
WSReleaseInteger32List(stdlink, a, n);
...
}
:ArgumentTypes:としてIntegerListを指定した場合,WSTPは外部関数が終了した直後,リスト用に使ったメモリを自動的に解放する.しかし,WSGetInteger32List()で明示的に整数リストを受け取った場合は,そのリストの利用が終ったら忘れずにメモリを解放することが重要である.
WSGetInteger32Array(stdlink,int**a,int**dims,char***heads,int*d) | |
あらゆる深さの整数配列を受信する | |
WSGetReal64Array(stdlink,double**a,int**dims,char***heads,int*d) | |
あらゆる深さの浮動小数点数配列を受信する | |
WSReleaseInteger32Array(stdlink,int*a,int*dims,char**heads,int d) | |
整数配列に関連するメモリを解放する | |
WSReleaseReal64Array(stdlink,double*a,int*dims,char**heads,int d) | |
浮動小数点数配列に関連するメモリを解放する |
WSGetInteger32List()はWolfram言語のリストから一次元の整数配列を抜き出す.WSGetInteger32Array()はあらゆる深さのリストやWolfram言語式から整数配列を抜き出す.
複素数のリストを外部プログラムに渡すとき,WSGetReal64Array()は実部と虚部のペアからなる列を含む二次元配列を作るだろう.このとき,heads[0]は"List",heads[1]は"Complex"となるだろう.
WSGetString(stdlink,char**s) | 文字列を取得する |
WSGetSymbol(stdlink,char**s) | 文字列を取得する |
WSReleaseString(stdlink,char*s) | 文字列に関連したメモリを解放する |
WSReleaseSymbol(stdlink,char*s) | シンボル名に関連したメモリを解放する |
:ArgumentTypes:にStringを指定すると,WSTPは外部関数が終了した直後,文字列用に使っていたメモリを自動的に解放する.その文字列を参照し続けたい場合には,メモリを割付け,文字列に含まれる文字のそれぞれを明示的にコピーする必要がある.
WSGetString()を利用する場合は,関数が終了しても文字列を保持するメモリは解放されない.そのため,文字列を参照し続けることが可能である.WSGetString()で返されたメモリに書き込むことで文字列の内容を修正しないように注意しなければならない.文字列が必要でなくなったら,WSReleaseString()を呼んで文字列に関連したメモリを解放しなければならない.
WSGetFunction(stdlink,char**s,int*n) | |
関数情報を取り込む(頭部名は s に,引数の数は n に入れられる) | |
WSReleaseSymbol(stdlink,char*s) | 関数情報を保管しているメモリ領域を解放する |
外部プログラムにおいてどの関数型のオブジェクトを取り込むかがはっきりしている場合は,WSCheckFunction()を使えばよいので簡単である.しかし,関数型が判明しないときは,WSGetFunction()を使うしかない.後者を使った場合は終了時にWSReleaseSymbol()を呼び出して,WSGetFunction()で見付けた関数頭部を保管するのに使ったメモリを解放する必要がある.