|
2.13.4 リスト,配列,式の扱い方
MathLinkを利用して,あらゆる型のデータを外部プログラムと交換することができる.非常に基本的な型については,MathLinkテンプレートファイル中の :ArgumentTypes:や:ReturnType:を適当に設定すればよい.

基本的な型
整数のリストを引数に取る MathLinkテンプレート.
:Begin:
:Function: h
:Pattern: h[a_List]
:Arguments: a
:ArgumentTypes: IntegerList
:ReturnType: Integer
:End:
テンプレートに対応するCのソースコード.リストの長さを示す引数 alenが関数に渡されていることに注意する.
int h(int *a, long alen) 
int i, tot=0;
for(i=0; i<alen; i++)
tot += a[i];
return tot;

これは関数hの指定を含む外部プログラムをインストールする.
In[1]:= Install["hprog"]
Out[1]= 
外部コードを呼び出す.
In[2]:= h[{3, 5, 6}]
Out[2]= 
パターン h[a_List]にマッチしないので外部コードは呼び出されない.
In[3]:= h[67]
Out[3]= 
パターンはマッチするが,リストの要素の外部コードの期待するタイプではない.そのため$Failedが返っている.
In[4]:= h[{a, b, c}]
Out[4]= 
基本的な型は自由に組み合せて使うことができる.ただし, IntegerListと RealListとを使う場合は,リストの長さを表す特別な引数をCのソースコード中に与えなければならない.
:ArgumentTypes:の一例.
:ArgumentTypes: IntegerList, RealList, Integer
対応するCの関数の宣言はこのようになる.
void f(int *a, long alen, double *b, long blen, int c)
MathLinkによってCプログラムに渡されたリストの最初の要素は, Mathematicaにとって標準的な1番目ではなく,Cの標準である0番目として参照されることに注意する.
さらに,Cの規格に従い, Stringで示される文字列は,ヌルバイト \0で終了する char *で示されるオブジェクトとして渡される.特殊文字の取扱いについては 2.13.5で説明する.

Mathematicaへデータを送信する MathLink関数
mprepや mccが実際に行うことは, MathLinkテンプレートファイルから MathLinkライブラリ関数を明示的に呼び出すCプログラムを生成することである.そのCプログラムのコードを読めば, MathLinkの動作をもっと深く理解することができるだろう. mccを使う場合,-gオプションを忘れないようにする.-gオプションが与えられないと生成されたソースコードは自動的に消去されてしまう.
もし,外部関数が整数か浮動小数点数を1つだけリターンするようなものなら, MathLinkテンプレートファイル中の:ReturnType:に Integerあるいは Realを与えるだけでよい.しかし,Cのメモリアロ ケーション/デアロケーションのしかたが原因して,:ReturnType:に IntegerListや RealListを与えることはできない.そのような構造体をリターンしたいなら, MathLinkライブラリ関数を明示的に呼び出すCプログラムを書く必要がある.この場合, :ReturnType:は Manualを指定する.
整数の引数を1つ取り,結果を明示的な MathLink関数の呼出しでリターンする関数の MathLinkテンプレート.
:Begin:
:Function: bits
:Pattern: bits[i_Integer]
:Arguments: i
:ArgumentTypes: Integer
:ReturnType: Manual
:End:
関数の型は voidと宣言する.
void bits(int i) {
int a[32], k;
Cの配列aに値を設定する.
for(k=0; k<32; k++) 
a[k] = i%2;
i >>= 1;
if (i==0) break;

if (k<32) k++;
配列 a中の k個の要素を Mathematicaに戻す.
MLPutIntegerList(stdlink, a, k);
return ;
}
外部関数 bitsを持つプログラムをインストールする.
In[5]:= Install["bitsprog"]
Out[5]= 
外部関数はビットのリストを返す.
In[6]:= bits[14]
Out[6]= 
Cの配列 int a[n1][n2][n3]は, MLPutIntegerArray()を利用して,深さ3のリストとして Mathematicaに送信することができる.
...
3次元Cの配列を宣言する.
int a[8][16][100];
配列 dimsを用意し, aの 次元を保持するように初期化する.
long dims[] = 8, 16, 100 ;
...
3次元配列を Mathematicaに送信し,深さ3のリストを作成する.
MLPutIntegerArray(stdlink, a, dims, NULL, 3);
...
いかなる Mathematicaの式も, MathLink関数で作ることができる.基本的に,作成したい Mathematica式の FullForm(完全形)に直接相当する MathLink関数を順に呼び出せばよい.
2つの引数の Mathematica関数Plusを設定する.
MLPutFunction(stdlink, "Plus", 2);
最初の引数に 77を与える.
MLPutInteger(stdlink, 77);
2番目の引数にシンボル xを与える.
MLPutSymbol(stdlink, "x");
一般に,まず, MLPutFunction()を呼び,作成したい Mathematica関数の頭部とその関数が引数をいくつ取るかを与える.そのあと,もうひとつ,MathLink関数を呼んで関数の引数を順に設定する. Mathematicaの式の一般的な構造と頭部の概念については2.1で解説している.
要素数が2のリストを生成する.
MLPutFunction(stdlink, "List", 2);
リストの最初の要素は10個の要素を持つCの配列 r.
MLPutIntegerList(stdlink, r, 10);
最初のリストの2番目の要素は要素数が2のリスト.
MLPutFunction(stdlink, "List", 2);
サブリストの最初の要素は浮動小数点数である.
MLPutReal(stdlink, 4.5);
サブリストの2番目の要素は整数である.
MLPutInteger(stdlink, 11);
MLPutIntegerArray()とMLPutRealArray()とを使えば,Cが前もって1次元的に割り付けたメモリ上の配列を送信することができる.しかし,Cプログラムの実行中に作った配列は,ネストされたポインタの集まりであることが普通である.そのような配列を Mathematicaに送信するには, MLPutFunction()を何度か呼び,最後に MLPutIntegerList()を呼べばよいだろう.
...
aを整数のリストのリストのリストとして宣言する.
int ***a;
...
n1要素の Mathematicaリストを作成する.
MLPutFunction(stdlink, "List", n1);
for (i=0; i<n1; i++) {
n2要素のサブリストを作成する.
MLPutFunction(stdlink, "List", n2);
for (j=0; j<n2; j++) {
整数リストを書き出す.
MLPutIntegerList(stdlink, a[i][j], n3);
}
}
...
MathLink関数を利用して作成した式はMathematicaに送信された直後に評価されることに注意する.このことは,例えば, Mathematicaに送信した配列を転置したいとき,配列を表す式を Transposeで囲むだけでよいことを意味する.すなわち,配列を表す式を生成する前に,MLPutFunction(stdlink, "Transpose", 1);を呼び出すだけでよい.
Mathematicaに送信したデータを後処理するというアイデアの使い道はたくさんある.1つの例は,長さを前もって知ることのできないリストを送信する場合である.
次々に要素を加えてMathematicaのリストを作成する.
In[7]:= t = {}; Do[t = Append[t, i^2], {i, 5}]; t
Out[7]= 
要素が次々にネストしたサブリストに 入ったリストを作成する.
In[8]:= t = {}; Do[t = {t, i^2}, {i, 5}]; t
Out[8]= 
Flattenはリストのネストをはずす.
In[9]:= Flatten[t]
Out[9]= 
Sequenceは自動的にネストをはずす.
In[10]:= {Sequence[1, Sequence[4, Sequence[ ]]]}
Out[10]= 
MLPutIntegerList()を呼び出すには,送信したいリストの長さを知る必要がある.しかし,ネストされたSequenceの列を作成することにすれば,リスト全体の長さを送信前に知る必要がなくなる.
結果をListで囲んでおく.
MLPutFunction(stdlink, "List", 1);
while( 条件 ) {
要素を生成
次のレベルのSequenceを作成する.
MLPutFunction(stdlink, "Sequence", 2);
要素を送信する.
MLPutInteger(stdlink, i );
}
最後のSequenceオブジェクトを閉じる.
MLPutFunction(stdlink, "Sequence", 0);

Mathematicaからデータを取得するための基本的な関数
MLPutInteger()で外部プログラムから Mathematicaへデータを送信する機能があるように, MathLinkには Mathematicaのデータを外部プログラムに渡すための MLGetInteger()のような関数も用意されている.
MathLinkテンプレート中の :ArgumentTypes:のリストは Manualで終ることができ,これは他の引数があることを示す.その式はMathLink関数を呼び出して受け取る.
:Begin:
:Function: f
Mathematicaの関数 fは3つの引数を取る.
:Pattern: f[i_Integer, x_Real, y_Real]
3つの引数はすべて外部関数に渡される.
:Arguments: i, x, y
最初の引数だけが外部関数に直接送信される.
:ArgumentTypes: Integer, Manual
:ReturnType: Real
:End:
外部関数は1つの引数しか,明示的に取得しない.
double f(int i) {
変数 x, yを宣言する.
double x, y;
MLGetReal()を呼んでリンクからデータを明示的に取ってくる.
MLGetReal(stdlink, &x);
MLGetReal(stdlink, &y);
return i+x+y;
}
MLGetInteger(link, pi)のようなMathLink関数は標準的なCのライブラリ関数fscanf(fp, "%d", pi)とほとんど同じように動作する.最初の引数はどのリンクからデータを取得するかを示し,最後の引数は受け取ったデータをどのアドレスに格納するかを示している.

MathLink経由で関数を取得する
:Begin:
:Function: f
Mathematicaの関数 fは整数のリストを引数に取る.
:Pattern: f[a: ___Integer ]
リストは外部関数に直接渡される.
:Arguments: a
引数は外部関数がマニュアルで取得する.
:ArgumentTypes: Manual
:ReturnType: Integer
:End:
外部関数は引数を陽に取らない.
int f(void) {
局所変数の宣言.
long n, i;
int a[MAX];
送信された関数がリストであることを チェックし,いくつ要素を持っているかをnに記録する.
MLCheckFunction(stdlink, "List", &n);
リスト中のそれぞれの要素を受信し, a[i]に保存する.
for (i=0; i<n; i++)
MLGetInteger(stdlink, a+i);
...
}
ほとんどの場合, Mathematica側で外部プログラムに送るデータが期待どおりの構造かどうかを確認することができる.MLCheckFunction()の戻り値は一般に,データがMLCheckFunction()に与えた関数名からなる場合にのみ,非ゼロとなる.
ネストされたリストやその他のオブジェクトを受信することは,MLCheckFunction()を適切な順番で呼び出すことで可能である.

数値のリストを取得する
外部関数が Mathematicaからデータを取得するとき,データを保管する場所を確保しなければならない.もし,そのデータが整数1個であったら,MLGetInteger(stdlink, &n)のように,整数が int nの場所を使うように宣言するだけで十分である.
しかし,データが長さの分からない整数リストである場合,外部プログラムが実際に呼び出された時点でそのリストを保存するためのメモリを割り付けなければならない.
MLGetIntegerList(stdlink, &a, &n)は自動的にこのメモリ割付けを実行して, aをその割付けの結果を指すポインタとする.MLGetIntegerList()のような関数で割り付けられ たメモリはすべて特殊な領域に配置される.そのメモリを修正したり,直接解放することはできない.
この外部関数は整数リストを受信する.
int f(void) {
局所変数の宣言.aは整数の配列.
long n;
int *a;
整数リストを受信する.aを受信したデータの場所を指すようにする.
MLGetIntegerList(stdlink, &a, &n);
...
整数リストを保管していたメモリを解放する.
MLDisownIntegerList(stdlink, a, n);
...
}
:ArgumentTypes: IntegerListを指定した場合,MathLinkは外部関数が終了した直後,リスト用に使ったメモリを自動的に解放する.しかし,MLGetIntegerList()で明示的に整数リストを受け取った場合は,そのリストの利用が終ったら忘れずにメモリを解放することが重要である.

数値配列を受信する
MLGetIntegerList()は Mathematicaのリストから1次元の整数配列を抜き出す. MLGetIntegerArray()はあらゆる深さのリストや Mathematica式から整数配列を抜き出す.
構造体にある,レベル iの Mathematica関数の名前は, heads[i]に文字列として保存される.レベルiの構造体のサイズはdims[i]に,全体の深さは dに保存される.
複素数のリストを外部プログラムに渡すとき, MLGetRealArray()は実部と虚部のペアからなる列を含む2次元配列を作るだろう.このとき,heads[0]は"List",heads[1]は"Complex"となるだろう.
Mathematicaの IntegerDigitsや RealDigitsを利用して数を数字のリストに変換すれば,外部プログラムとの間で数の任意精度を簡単に交換できる.

文字列とシンボル名の取得
:ArgumentTypes:に Stringを指定すると, MathLinkは外部関数が終了した直後,文字列用に使っていたメモリを自動的に解放する.その文字列を参照しつづけたい場合には,メモリを割付け,文字列に含まれる文字のそれぞれを明示的にコピーする必要がある.
MLGetString()を利用する場合は,関数が終了しても文字列を保持するメモリは解放されない.そのため,文字列を参照しつづけることが可能である.もし,文字列が必要でなくなったら, MLDisownString()を呼んで文字列に関連したメモリを解放しなければならない.

任意関数の取得と解放
外部プログラムにおいてどの関数型のオブジェクトを取り込むかがはっきりしている場合は, MLCheckFunction()を使えばよいので簡単である.しかし,関数型が判明しないときは, MLGetFunction()を使うしかない.後者を使った場合は,終了時に MLDisownSymbol()を呼び出して関数頭部を保管するのに使ったメモリを解放する必要がある.
|