2015年7月8日水曜日

MinGW gcc とctypesモジュールを用いてpythonからc関数を呼び出してみる 2

前に構造体を渡すような関数までやったけど、構造体の配列を渡すようなこともよくありそう。簡単な例で試してみたのでメモしておきます。
呼び出されるc関数は、以下の通り。

#include <stdio.h>

typedef struct {
 char name[20];
 int age;
} NAMEandAGE;

int sum_ages(int sz, NAMEandAGE *na)
{
 int i;
 int sum = 0;
 
 for (i=0; i<sz; i++){
  printf("%s %d\n", na[i].name, na[i].age);
  sum = sum + na[i].age;
 }
 return sum;
}
文字列と整数のフィールドをもつ、構造体NAMEandAGEの配列を渡して、 要素の値を表示し、ageの和を返す関数です。
配列は、pythonスクリプトで作成するため、その長さが分からないので、 整数szで長さを渡しています。

ソースファイルをstructures.cというファイルに保存して、

gcc -Wall -c structures.c

でコンパイルします。structures.oというオブジェクトファイルができますので、

gcc -shared -o structures.so structures.o

として、structures.soという、共有ライブラリファイルを作成します。
これを呼び出す、pythonスクリプトは、以下の通り。

import ctypes

class NAMEandAGE(ctypes.Structure):
    _fields_ = [('name', ctypes.c_char*20),
                ('age', ctypes.c_int)]

if __name__ == '__main__':
    mydll = ctypes.CDLL("structures.so")

    data = [('name1', 1), ('name2', 2)] 
    nameandages = (NAMEandAGE * len(data))()
    for n, d in zip(nameandages, data):
        n.name = d[0]
        n.age = d[1]

    result = mydll.sum_ages(ctypes.c_int(len(data)), ctypes.pointer(nameandages))
    print result

配列は、(構造体のクラス * 要素数)で構造体配列のクラスを作って、それを用いてインスタンスを生成するようです。 その後、リストdataの値で、各要素のフィールドを設定しました。
関数を呼び出すときは、ctypes.pointer()を使って、構造体配列のポインタを渡せばよいようです。

2015年5月30日土曜日

Zenfone2を手に入れた

IDEOSでは、OCN mobileのデータSIMを利用していたのですが、 3Gでしか利用していなかったので、ちょっともったいない気がしていました。 画面が小さいため、ほぼモバイルルーターとしてしか使用しないという状況。 Zenfone2が発売されたので、乗り換えました。 メモリ2GB、ストレージ32GBのモデルです。 IDEOSでもmicro SIMを使っていたので、SIMを入れ替えるだけですぐに使えました。 データSIMだけど、セルスタンバイ1%だって。問題がない様子。 画面をダブルタップで、スリープのON/OFFができるのはとても便利ですね。

QPythonを入れてみたところ、便利な計算機として、使えそうではあります。 さらにkivyを用いて、PC上でimageViewerを書き、Zenfone2へインストールしてみました。 しかしながら、QPythonではいるkivyのバージョンは1.8で、バージョン1.9からの in-memory loadingを利用したため、QPythonからは実行できませんでした。

Kivyのページを見たところ、AndroidにはKivy Launcherというものがあるらしいので、 そちらで試すことに。 Kivy Launcherは、kivyのバージョンが1.9で、無事に動作しました。 これで、PC上で作成したGUIつきのpythonスクリプトを、Zenfone2でもそのまま動かせそうです。

2015年5月23日土曜日

matplotlibのMultiCursor AutoScale時の挙動修正

matplotlibのMultiCursor()の使い方は、公式のページにありますが、matplotlib 1.3.0あたりからクロスラインカーソルも利用できます。

multi = MultiCursor(fig.canvas, (ax1, ax2), color='r', lw=1, horizOn=True)
のように、horizOnキーワード引数にTrueを渡すと水平線を描画します。

ここで、クロスラインカーソルを表示したいaxes、(ax1, ax2)を渡しているのですが、
最後のax2でないaxes、ここではax1の軸がオートスケールになっていると、あれっ?
と思う挙動をします。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import MultiCursor

t = np.arange(0.0, 2.0, 0.01)
s1 = np.sin(2*np.pi*t)+400
s2 = np.sin(4*np.pi*t)
fig = plt.figure()
ax1 = fig.add_subplot(211)
ax1.plot(t, s1)

ax2 = fig.add_subplot(212, sharex=ax1)
ax2.plot(t, s2)

multi = MultiCursor(fig.canvas, (ax1, ax2), color='r', lw=1, horizOn=True)
plt.show()
ax1に描画する波形を400だけオフセットしただけですが、実行すると、以下のようになります。
y軸のレンジが0からになってしまいます。 オートスケールで期待する結果とは異なります。 matplotlibパッケージのwidgets.pyにある、MultiCursorのクラスを見てみると、 どうやら、カーソル線の初期位置の計算に、渡したaxesの最後のものだけを使っているために 生じるようです。

これをwidgets.pyをいじらずに回避するには、MultiCursorの__init__()で行なわれる カーソル線の生成を、別に行えばよさそうです。 そこで、MultiCursorの__init__()には、vertOn、 horizOnともにFalseを渡します。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import MultiCursor

class MyMultiCursor(MultiCursor):
    def __init__(self, canvas, axes, 
            useblit=True, horizOn=True, vertOn=True, **lineprops): 
        MultiCursor.__init__(self, canvas, axes, 
                useblit=useblit, horizOn=False, vertOn=False, **lineprops)

        xmids = [0.5 * (xmin + xmax) for xmin, xmax in [ax.get_xlim() for ax in axes]] 
        ymids = [0.5 * (ymin + ymax) for ymin, ymax in [ax.get_ylim() for ax in axes]] 

        if self.useblit:
            lineprops['animated'] = True
        if vertOn:
            self.vlines = [ax.axvline(xmids[i], visible=False, **lineprops)
                           for i, ax in enumerate(axes)]
        else:
            self.vlines = []

        if horizOn:
            self.hlines = [ax.axhline(ymids[i], visible=False, **lineprops)
                           for i, ax in enumerate(axes)]
        else:
            self.hlines = []

        self.vertOn, self.horizOn = vertOn, horizOn


t = np.arange(0.0, 2.0, 0.01)
s1 = np.sin(2*np.pi*t)+400
s2 = np.sin(4*np.pi*t)
fig = plt.figure()
ax1 = fig.add_subplot(211)
ax1.plot(t, s1)

ax2 = fig.add_subplot(212, sharex=ax1)
ax2.plot(t, s2)

multi = MyMultiCursor(fig.canvas, (ax1, ax2), color='r', lw=1, horizOn=True)
plt.show()

MultiCursorを継承したMyMultiCursorを利用します。 実行すると、以下のように期待されたグラフが得られました。

2014年7月31日木曜日

Latitude10がWindows8.1の起動に失敗

Windowsの更新を確認したところ、アップデートあったので、 いつもどおりにやったら、エラーが出て起動に失敗しました。 自動修復で、どうにか復帰。巻き戻ったので、またアップデートをかけたら、 また、再起動後にエラーが出てしまった(IRQ_NOT_LESS_OR_EQUAL)。今度は、自動修復にも失敗してしまった。

自動修復が失敗した後、詳細オプションで、選べる中に、「そのまま続ける」 というのがあったので、選んでみたがなにも反応がない。 電源を落として、再び、起動しなおし。

次に、リフレッシュを選んだところ、Windows8になって起動した。
とりあえず、 Windows8でも起動してくれたので、ほっとしたところ。
Windowsの更新をすると、ストアに8.1へのアップデートが現れたので、再び8.1へ。
と思ったら、失敗。
その後、チップセットのドライバをA07にして、 再び挑戦したところ、無事に8.1へとアップデートできた。今度は、8.1で全ての更新を適用しても、起動時にエラーが起こることはなく、使えるように復帰しました。

なにがよくて、なにが悪かったのか、はっきりせずにちょっと心配ですが、以前のように動作している様子。リフレッシュ後、元の状態に戻すのには時間がかかるので、もうやりたくないですね。



2014年7月26日土曜日

Windowsのgnuplotで、plot sin(x)をしてみた

Javaは知らないけど、便利なライブラリがあるようなので、 Clojureを触ってみようということでやってみました。

数値を得たら、プロットしてみるということはしばしばあります。 pythonだとmatplotlibを使ったりしますが、 gnuplotを呼び出すことができれば、 グラフ描画をgnuplotにまかせることもできそうです。

gnuplotは、gp463-win32-setup.exeを使ってインストールしたも のを利用しています。(ver.4.6.5でも大丈夫です) コマンドラインでgnuplotが実行できるように、インストールされたgnuplot.exeがあるディレクトリへあらかじめPathを通しておきます。

Clojureは、1.6.0をダウンロードしました。 Getting Startedにあるように、

java -cp clojure-1.6.0.jar clojure.main

で、REPLを起動して利用しています。

以下が、そのスクリプトですが、 プロセスを開始するgpstart、gnuplotのコマンドを送るgpdo、 終了するgpstopという関数を作っています。 sin(x)をプロットするのは、demo関数で行います。
(defrecord GnuplotProc [proc out in])

(defn gpstart
  "Start gnuplot process"
  []
  (let [proc (.start (doto (ProcessBuilder. '("gnuplot" "--persist"))
                       (.redirectErrorStream true)))
        out (clojure.java.io/writer (.getOutputStream proc))
        in (clojure.java.io/reader (.getInputStream proc))]
    (GnuplotProc. proc out in)))

(defn gpstop
  "Stop gnuplot process"
  [proc]
  (.destroy (get proc :proc)))

(defn gpdo
  "send command string to gnuplot process"
  [proc s]
  (let [w (get proc :out)]
       (.write w (str s))
       (.newLine w)
       (.flush w)))

(defn demo
  "Demo function"
  []
  (def p (gpstart))
  (gpdo p "set xlabel \"x\"")
  (gpdo p "set ylabel \"y\"")
  (gpdo p "plot sin(x)")
  (read)
  (gpstop p))

(demo)
clispと、pythonでも書いてみました。
(defun gpstart 
  ;Start gnuplot process
  ()
  (make-pipe-io-stream "gnuplot --persist" :buffered t))

(defun gpstop
  ;Stop gnuplot process
  (proc)
  (let ()
    (gpdo proc "quit")
    (close proc)))

(defun gpdo
  ;send command string to gnuplot process
  (proc s)
  (let ()
    (format proc "~A~%" s)
    (force-output proc)))

(defun demo
  ;demo function
  ()
  (let ((p (gpstart)))
    (gpdo p "set xlabel \"x\"")
    (gpdo p "set ylabel \"y\"")
    (gpdo p "plot sin(x)")
    (read)
    (gpstop p)))

(demo)

Pythonで書いたスクリプトは以下の通り。
import subprocess

def gpstart():
    """ Start gnuplot process """
    proc = subprocess.Popen(["gnuplot", "--persist"], 
                            stdin = subprocess.PIPE,
                            stdout = subprocess.PIPE,
                            stderr = subprocess.PIPE)
    return proc

def gpstop(proc):
    """ Stop gnuplot process """
    gpdo(proc, "quit")
    proc.kill()

def gpdo(proc, s):
    """ send command string to gnuplot process """
    proc.stdin.write(s)
    proc.stdin.write("\n")
    proc.stdin.flush()

def demo():
    """ Demo function """
    p = gpstart()
    gpdo(p, 'set xlabel "x"')
    gpdo(p, 'set ylabel "y"')
    gpdo(p, 'plot sin(x)')
    raw_input()
    gpstop(p)

if __name__ == '__main__':
    demo()
この程度なら、行数はほぼ一緒ですね。
clispやClojureを使ってもgnuplotを操作できそうなので、ひと安心です。

Rubyでもやってみました。
def gpstart
    # Start gnuplot process
    IO.popen("gnuplot --persist", "r+")
end

def gpstop(io)
    # Stop gnuplot process
    io.close
end

def gpdo(io, s)
    # send command string to gnuplot process
    io.puts(s)
end

def demo
    io = gpstart
    gpdo(io, "set xlabel \"x\"")
    gpdo(io, "set ylabel \"y\"")
    gpdo(io, "plot sin(x)")
    gets
    gpstop(io)
end

if __FILE__ == $0
    demo
end
シンプルですね。
Windows環境でirbを利用する人は少ないのかな。 ActiveScriptRuby 2.1.2-p95を利用してみたのですが、 irbのインタラクティブな環境で、Backspaceがちゃんと動作しませんでした。 検索してみたら、1.9.2での同様な症状と対処法がこちらにありました。 irbに--noreadlineオプションをつけて起動すると、正常にBackspaceできるようになりました。


2016/07/24追記
同じものをC言語で。Windows Vista 32bit MinGWのgcc-4.9.3で確認。

#include <stdio.h>

FILE *gpstart(void);
void gpstop(FILE *p);
void gpdo(FILE *p, char *s);

FILE *gpstart(void) {
    /* start gnuplot process */
    return popen("gnuplot.exe --persist", "w");
}

void gpstop(FILE *p) {
    /* stop gnuplot process */
    gpdo(p, "quit");
    pclose(p);
}

void gpdo(FILE *p, char *s) {
    /* send command string to gnuplot process */
    fprintf(p, s);
    fprintf(p, "\n");
}

void demo(void) {
    /* demo function */
    FILE *p;
    p = gpstart();
    gpdo(p, "set xlabel \"x\"");
    gpdo(p, "set ylabel \"y\"");
    gpdo(p, "plot sin(x)");
    gpstop(p);
}

void main(void) {
    demo();
}


2014年7月19日土曜日

クイックっぽいソート

Web上をいろいろ見ていたら、Hskellの5行のクイックソートというのがあるみたいで、Pythonでも書いてみました。 Quickソートが何をやっているのかを書き記したようなものみたいになっています。

def qlikesort(data):
    if len(data) < 1: return data
    smaller = [x for x in data[1:] if x < data[0]]
    larger = [x for x in data[1:] if x >= data[0]]
    return qlikesort(smaller) +[data[0]]+ qlikesort(larger)

pythonでもそんなに長くならないですね。
CLISPでも動くように、一対一で書き直してみると、

(defun qlike-sort (s)
  (if (null s) 
      nil 
      (let ((smaller (remove-if (lambda (x) (>= x (car s))) (cdr s)))
            (larger  (remove-if (lambda (x) (<  x (car s))) (cdr s))))
           (concatenate 'list (qlike-sort smaller) (list (car s)) (qlike-sort larger)))))

当たり前ですが、あまり変わらないですね。

Graffiti入力っぽいキーボードを修正した

DELLのLatitude10でしばらく使っていたGraffiti入力っぽいソフトウェアキーボードに、ショートカット機能を追加してみた。ショートカットストロークのあとに、アルファベットのストロークで、コマンドを実行するものだ。ショートカットで起動させるコマンドは、テキストファイルに書き込んでおくようにした。python+wxPythonで書かれたスクリプトを修正するのは、動作を確認しながら、少しずつ手を加えていき、30分ほどで修正が完了した。

Windowsタブレットは、あたりまえだけどWindows上で作成したスクリプトがそのまま動作するので、自作のスクリプトを動作させる環境としては、とてもよいものだと思う。操作方法も特別なことはないし。最近、8インチのWindowsタブレットが気になるところですが、Latitude10が調子よく動作しているので、しばらくはないかな。