アプリケーションの構造
TetGenLink はTetGenの関数をWolfram言語で使えるようにするWolframシステムアプリケーションである.これは,TetGenが高速でしかも低メモリで使えるようにするWolfram LibraryLinkを利用する.このセクションでは LibraryLink がどのように使われているかを見てみる.
TetGen自体はC++で書かれており,そのソースコードはTetGenのWebサイト(http://tetgen.org)で入手可能である.Wolfram言語ではバージョン1.4.3を使っている.インターフェースコードはC++とWolfram言語の混合言語で書かれており,TetGenLink に同梱されている.
TetGenLink の場所は常に$TetGenInstallationDirectoryで調べられる.これは次に示す通りである.まず,アプリケーションをロードする.
このバージョンのWolframシステム用の TetGenLink の場所である.
ここでの説明は,Library Linkを使って自分のライブラリをWolfram言語につなぐのに便利かもしれない.
アプリケーションのレイアウト
TetGenLink はWolfram言語コード,ドキュメント,共有ライブラリ,例題データ,C++ソースコードを含むWolframシステムアプリケーションとして開発された.さらにPacletInfo.mファイルがこのアプリケーションの記述子である.概要は次の通りである.
TetGenLink
PacletInfo.m
TetGenLink.m
Kernel
init.m
Documentation
English
guide, reference, and tutorial pages
ExampleData
sample data sets
LibraryResources
Windows
tetgenWolfram.dll
Other SystemIDs
Source
C
C++ source code
http://www.wolfram.co.jp/products/workbenchの特にアプリケーション開発についてのセクションでは,アプリケーションレイアウトに関する詳細や,Wolfram Workbench のドキュメントにどのように組み込めるかを見ることができる.
Wolframシステムは必要に応じてアプリケーションの異なる部分を見付ける.例えば,Wolfram言語ドキュメントセンターが使われると,それはドキュメントを見付けるために接続する.アプリケーションがNeeds["TetGenLink`"]でロードされたときは,init.mファイルをロードする.また,FindLibrary等のLibraryLink の関数は,Wolfram言語が起動している$SystemIDに適切な動的ライブラリを見付ける.
Wolfram言語の実装
TetGenLink がコマンドNeeds["TetGenLink`"]あるいは<<TetGenLink`でロードされると,メインのWolfram言語パッケージTetGenLink.mをロードするinit.mファイルがロードされる.後者は使用法情報,関数のエキスポート,実装を含むWolfram言語パッケージの基本的な構造に従っている.
これがロードされると,以下のようにいくつかのシンボルが定義される.
$TetGenInstallationDirectory = DirectoryName[ $InputFileName]
$TetGenLibrary = FindLibrary[ "tetgenWolfram"]
これらのシンボルの値を見るためには,アプリケーションをロードしなければならない.
次は TetGenLink アプリケーションの場所を示す.これは$InputFileNameで設定される.
次はこのバージョンのWolfram言語に対する TetGenLink の共有ライブラリの場所を示す.FindLibraryが,使用する$SystemID等の問題をどのように解決しているかに注目のこと.
次のセクションではTetGenの共有ライブラリからさまざまな関数をロードする.これにはすべてLibraryFunctionLoadの呼出しが必要である.setPointsFun等,Wolfram言語とライブラリの間でデータの共有が必要になるものもある.これにより時間とメモリが大きく節約できる.ライブラリにデータを渡すことについては,「MTensor入力引数」を参照されたい.
LoadTetGen[] :=
Module[{},
instanceFun = LibraryFunctionLoad[$TetGenLibrary,
"newTetGenInstance", {}, Integer];
deleteFun = LibraryFunctionLoad[$TetGenLibrary,
"deleteTetGenInstance", {Integer}, Integer];
fileOperationFun = LibraryFunctionLoad[$TetGenLibrary,
"fileOperation", LinkObject, LinkObject];
getPointsFun = LibraryFunctionLoad[$TetGenLibrary,
"getPointList", {Integer}, {Real,_}];
setPointsFun = LibraryFunctionLoad[$TetGenLibrary,
"setPointList", {Integer, {Real, 2, "Shared"}}, Integer];
loading more functions...
]
パッケージの本体はライブラリの関数の呼出しに関するものである.例えば,TetGenCreateを実装すると,ライブラリがロードされたことがチェックされる.それからライブラリで定義されているinstanceFunが呼出される.一点注意してほしいのは,関数が実際に使われるまでライブラリはロードされないという点である.パッケージがロードされた時点ではライブラリはロードされていないのである.関数の結果は,そのインスタンスのIDを保持するTetGenExpression式である.
TetGenCreate[] :=
Module[{},
If[ needInitialization, LoadTetGen[]];
TetGenExpression[ instanceFun[]]
]
次でTetGenCreateを起動する.見ての通り,結果はライブラリに保存されているTetGetオブジェクトのインスタンスハンドルである.ライブラリの呼出しはすべてこのハンドルを使い実際のインスタンスを得る.
次はTetGenGetPointsのWolfram言語での実装である.非常に簡単で,TetGenExpressionからidを抽出し,それをTetGenライブラリの関数に渡すというものである.
TetGenGetPoints[ TetGenExpression[ id_]] :=
Module[{},
getPointsFun[ id]
]
C++の実装
C++の実装には2つの部分がある.TetGen自体のソースコードはTetGenのWebサイト(http://tetgen.org)にある.これにより,他のコードで使うことのできるTetGenのバージョンを構築することができる.他のコードは LibraryLink で使えるTetGenの関数のインターフェースを提供する.そのソースコードは TetGenLink に同梱されている.これは$TetGenInstallationDirectoryを使うと TetGenLink の中に見付けることができる.
これを見るためには,まずアプリケーションをロードしなければならない.
TetGenはtetgenioクラスに属するオブジェクトのインスタンスを生成することにより動作する.これはデータのリストやファイルの点や頂点といったデータを加えること,さまざまな四面体分割を実行すること等さまざまな方法を提供する.インターフェースコードの目的は,TetGenオブジェクトの管理およびそのメソッドへのアクセスをサポートすることである.
ソースファイルtetgenInstance.cxxはTetGenオブジェクトのインスタンスを管理する.次でその実装のいくつかを紹介する.これには LibraryLink 用およびTetGen用のさまざまなヘッダファイルが含まれている.これはオブジェクトのインスタンスを含むためにC++の標準テンプレートライブラリのhash_mapを使おうとする.これを使うためにはコードにVisual C++とGCCコンパイラの両方のコードにそれをロードさせなければならない.hash_mapではtetgenioインスタンスを見付けるキーとして整数を使うことができる.
#include "tetgen.h"
#include "mathlink.h"
#include "WolframLibrary.h"
#include "tetgenWolframDLL.h"
#ifdef __GNUC__
#include <ext/hash_map>
namespace stdext
{
using namespace __gnu_cxx;
}
#else
#include <hash_map>
#endif
static stdext::hash_map<mint, tetgenio *> tetgenMap;
static mint tetgenListCnt = 0;
次のセクションにはさまざまなエキスポートと宣言が含まれている.エキスポートされる関数は,LibraryFunctionLoadに見付けられるようにCの名前付け規則を使わなければならず,EXTERN_Cが使われる.また,Windowsではこれらの関数はDLLから置かれる場所へとエキスポートする必要があり,DLLEXPORTが使われる.
EXTERN_C DLLEXPORT mint WolframLibrary_getVersion( ) ;
EXTERN_C DLLEXPORT mint WolframLibrary_initialize(
WolframLibraryData libData);
EXTERN_C DLLEXPORT int newTetGenInstance(
WolframLibraryData libData, mint Argc, MArgument *Args, MArgument res);
EXTERN_C DLLEXPORT int deleteTetGenInstance(
WolframLibraryData libData, mint Argc, MArgument *Args, MArgument res);
getTetGenInstance関数は整数を取り,hash_mapを使って対応するtetgenioインスタンスを返す.この関数はTetGenライブラリ全体を通して使われるが,ライブラリからはエキスポートされない.
tetgenio* getTetGenInstance( mint num)
{
return tetgenMap[num];
}
ここでは,tetgenioコンストラクタを呼び出すことによりTetGenオブジェクトの新しいインスタンスが生成される.このインスタンスはIDとして使用する新しい整数を生成し,hash_mapのインスタンスを保存し,そのIDを返す.これはWolfram言語関数のTetGenCreateで直接呼び出される.
DLLEXPORT int newTetGenInstance(
WolframLibraryData libData, mint Argc, MArgument *Args, MArgument res)
{
mint newID = tetgenListCnt++;
tetgenMap[newID] = new tetgenio();
MArgument_setInteger(res, newID);
return 0;
}
TetGenライブラリの構築
TetGenLink ライブラリは非常に標準的なC++言語で書かれるので,makefileやproject file等の多数の方法で構築することができる.ここでの説明はCCompilerDriverパッケージを使ってWolfram言語から直接構築する方法である.このアプローチの利点の一つは,異なるタイプのマシン上で起動するさまざまなコンパイラで使えるということである.
ビルドについていくつか問題点がある.まず,TetGenソースファイルのうちの一つは異なる設定でコンパイルしなければならないという点である.2つ目は TetGenLink で使われるTetGenソースは関数がメッセージを得るように修正されているという点である.以下の説明は,TetGenのWebサイト(http://tetgen.org)に掲載されている修正されていないTetGenソースを使ったビルドの方法である.使用するバージョンはTetGen 1.4.3である.
ビルドを行うためには,TetGenリソースとインターフェースリソースを1つのディレクトリに置かなければならない.この例では,シンボルsourceDirがソースのディレクトリを与え,outputDirが出力のディレクトリを与える.
ここでCCompilerDriver`パッケージをロードする.
以下は入力ファイルの定義と最適化なしでコンパイルされるファイルの定義を設定する.
ファイルを最適化しない設定はコンパイラにより異なる.次はVisual Studio C++とGCCコンパイラで動作する値を選んでいる.
これでCreateObjectFileの呼出しでオブジェクトファイルが実際に生成できる.
ライブラリ自体はCreateLibraryで構築することができる.NOMESSAGEFUNCTION定義により,修正されていないソースを使ってライブラリを構築することができる.
ソースの修正
tetgen.cxxの修正は非常に簡単である.その目的はTetGenにより生成されたメッセージをWolframシステムで表示することである.メッセージは通常printfで表示されるので,以下に示すように,変更点はテキストを捉えそれをWolframシステムに戻すようにprintfに負荷を掛けることである.
#define printf clientprintf
typedef void (*addMessage)( char*);
static addMessage messageFun = NULL;
void setMessageFunction(addMessage fun)
{
messageFun = fun;
}
static void clientprintf( const char* mess, ...)
{
char messBuff[ 4096];
va_list ap;
va_start(ap, mess);
vsprintf( messBuff, mess, ap);
va_end(ap);
if ( messageFun != NULL) {
messageFun( messBuff);
}
}
このコードを加えたら,ライブラリの構築時にNOMESSAGEFUNCTION定義は削除しなければならない.