Mathlink on IronPython 1.0

niwaii2006-09-11

先日公開されたIronPython 1.0は、予想以上に優れモノで、.Netランタイム用に作られたモジュール(マネージコード)と全くシームレスに連携できる(ようだ)。対するオリジナルPython(+ctypesパッケージ)による既存のネイティブコードとの連携では、相互のデータタイプを考慮する必要がある。(もっとも新規にオリジナルpython用のdllを作成する場合なら、boost/pythonを利用することで、連携自体は比較的容易。)
 試みに、mathematica 5.1の標準添付アドオンのひとつで、マネージ用のWolfram.NETLink.dllをIronPython上で使い、簡単なmathematicaのfrontendを作ってみた。(以下のスクリプトを適当なファイル名で保存し、DOS窓から"ipy.exe <適当なファイル名>で実行する。)

#!cmd ipy.exe
"""ipMathlink.py ver0.3 : Mathematica Simple Frontend on IronPython1.0"""
import time
import clr
clr.AddReferenceToFile("Wolfram.NETLink.dll")
from Wolfram.NETLink import MathKernel

class  ipMathKernel:
    """ >>> mkl = ipMathKernel() """
    def __init__(self, gWidth=640, gHeight=480):
        self.mkl = MathKernel()
        self.mkl.AutoCloseLink = "True"
        self.mkl.CaptureGraphics = "True"
        self.mkl.CaptureMessages = "True"
        self.mkl.GraphicsFormat = "JPEG"
        self.mkl.GraphicsWidth = gWidth
        self.mkl.GraphicsHeight = gHeight
        self.strOut = []

    def console(self):
        """yet another mathmatica console (with saving graphics)"""
        print "Mathematica Simple Frontend 0.3 on IronPython1.0"
        print "usage: input mathematica-style sentence (use quit to exit)"
        print " ex) Plot3D[Sin[x y],{x,0,2 Pi},{y,0,2 Pi}] "
        count = 0
        while 1:
            print ""
            func = raw_input("In[%s]:= " % str(count))
            if func == "quit": return self.strOut
            self.mkl.Compute(func)
            self.strOut.append(self.mkl.Result)
            print "Out["  + str(count) + "]=", self.mkl.Result
            self._saveGraphics()
            count += 1

    def _saveGraphics(self):
        if len(self.mkl.Messages) > 0:
            print "Messages:"
            for i in self.mkl.Messages: print i
        if len(self.mkl.PrintOutput) > 0:
            print "PrintOutput:"
            for i in self.mkl.PrintOutput: print i
        if self.mkl.Graphics.Length > 0:
            strtime = time.strftime("%y%m%d%H%M%S_", time.localtime())
            for i in range(self.mkl.Graphics.Length):
                imageFileName = strtime + str(i) + ".jpg"
                self.mkl.Graphics[i].Save(imageFileName)

    def runFunc(self, func=""):
        """ >>> result = run("Prime[10^9}") """
        self.mkl.Compute(func)
        self._saveGraphics()
        return self.mkl.Result

    def setVal(self,val_name,data):
        """ >>> setVal("valiable", data) """
        strtmp = str(data)
        if strtmp[0] == "[": strtmp = "{" + strtmp[1:len(strtmp)-1] + "}"
        func = str("%s = " % val_name) + strtmp
        self.mkl.Compute(func)

    def getVal(self,val_name):
        """ >>> valiable = getVal("val") """
        func = str("%s" % val_name)
        self.mkl.Compute(func)
        result = self.mkl.Result
        if result[0] == "{": result = result[1:len(result)-1]
        return result

if __name__ == '__main__':
    mkl = ipMathKernel(640,480)
    results = mkl.console()
    print "results= ",results

 IronPythonとWolfram.NETLink.dllの連携自体の機能として、Mathematica形式の関数式を文字列として入力でき、かつ、Python側に出力データを文字列として取り込むことができる。また、関数式に用いたMathematica側の変数を保持していて、以降の入力に再利用することもできた。
 当初、グラフィックデータが生成される場合に、mspaint.exeなどを内部から起動して表示する予定だったが、IronPython1.0には、オリジナルPythonの関数os.systemが無く(チュートリアルの記述にある方法で、オリジナルのosライブラリを取り込むことは出来るが、os.systemはパージされている)、相当する機能も見当たらなかったため、自動保存に止めた。
 ちなみに、オリジナルPythonでは、mathematicaのネイティブコードとの連携用アドオン(Mathlink)を用いるMLというパッケージがある。だが、MLは、mathematica形式の関数式を直接入力できず変形して入力する必要がある。

「追記」少し改良した。IronPythonについては、使い方が良く分かっていないこともあるが、スレッド周りに改善の余地ありとの印象を持った。