eazy-gnuplot ふりかえり


Lispからgnuplotを使ってグラフを描画するための 拙作ライブラリ、 eazy-gnuplot を紹介します。これはべつに新しいものではなく、2014年ごろに書いたライブラリです。しかし、今も継続してアップデートしていますし、かつ自分も使いやすいと思っているので、論文を書くときには重宝しています。

Motivation

計算機実験を行った時、必要になるのはその結果の可視化です。いくら大量にログファイルがあろうが、それを可視化したり表にしたりしない限り、論文にはなりません。沢山の人からの需要があるので、およそ全てのスクリプティング言語にはプロッティング用のライブラリがあります。Lispにも同様のものがあってしかるべきです。

ここで翻ってLisp のグラフ描画環境を振り返ってみましょう。Lisp は、GUIが弱いのと同様に、可視化ライブラリもあまり良い物がありません。現在quicklispに登録されているグラフ描画パッケージは7つですが、eazy-gnuplot以外のライブラリは控えめに言っても ぱっとしません

まず、非gnuplot系のライブラリは除外されました。gnuplotは大抵の大学で最初に教わるプロッティング用のプログラムです。多機能で、ディストリ標準のパッケージマネジャからすぐに入り、汎く普及しています。それに比べいわば 古い/どマイナー (lisperが何を言ってんだか…) な物、たとえば plplot, Grace などをベースとしたものは、機能が少ない、例がない、stackoverflowでみつからない、などと大変です。実際、stackoverflowで検索した所、gnuplotに関する質問が9000件もあるのに比べ、plplotは66件、Graceは0件でした。また、言うまでもなく、直にpngに書き込むようなlisp純正のライブラリ(clotなど)は、機能の面でgnuplotとは比べ物になりません。

では, gnuplot への接続ライブラリはというと、そもそも数が少ない。当時メンテされていたgnuplotライブラリは、 vgplot と clnuplot だけでした。Clikiを見ると過去にはもっと 沢山のgnuplot用ライブラリがあったようですが、どうやら誰もこれらをquicklispに登録しようとはしていないようです。

また、これらのライブラリは、 完全 でありませんでした。ライブラリが完全であるとは、 gnuplotにある全ての機能が問題なく使える という意味です。たとえばvgplotのマニュアル を見てみても、gnuplotにある膨大な全機能のうち、カバーされているのはほんの少しだということがわかります。これでは、「論文の長さを6ページに収めたいからaxisを1mm短くして・・・」みたいな、 かゆいところに手が届く網羅性 が足りていないのです。

gnuplot 固有の問題

ここで, lisp からのラッパを書く上で、ほかのプログラム と gnuplotでの違いをおさえておきましょう。ほかと比べた gnuplot の特徴 として、 超多機能統一感のなさ という点があげられます。いわば、プログラム全体が Huge Hack であると言えるでしょう。

多機能といえばたとえばこんな例。かゆいところに手は届くのですが、あれもこれもと追加していった感がバリバリです。幾つかの機能には相互排他的なものもあり、お世辞にも綺麗に整理されているとはいいがたい。。。

set label {<tag>} {"<label text>"} {at <position>}
          {left | center | right}
          {norotate | rotate {by <degrees>}}
          {font "<name>{,<size>}"}
          {noenhanced}
          {front | back}
          {textcolor <colorspec>}
          {point <pointstyle> | nopoint}
          {offset <offset>}
          {boxed}
          {hypertext}

統一感の無さといえば、たとえば次のように ペアのようなものを指定するための記法が統一されていなかったり、

using 3:2                  (使う列の指定)
xrange [10:100]            (軸の範囲)
x, y    (座標指定)

Gnuplot v5 から突然for文やif文が使えるようになったことなどがあげられます。(http://d.hatena.ne.jp/kenbeese/20121126/1353902734 より)

do for [dir in dirnames] {
  file = dir . "/dat"
  p file t file
  }

この点を抑えたうえで、(Clikiにもあるような) 既存のライブラリがおかしているのは、「全機能のうちの一部を手でマップする」戦略をとっている点です。 死ぬほど沢山ある機能に対し、一つ一つ関数/構造体/引数に対応させていく ためには、気の遠くなるほどの時間がかかるでしょう。 仮にその一部をマクロで抽象化してまとめられたとしても、以上のように各々の機能が沢山のオプションを持っているので、効率よくマップすることができません。

いったいぜんたい、マニュアルだけで254ページもあるgnuplotの全機能をlispからマップするだなんてことが、一介のLisp Hackerに可能なのでしょうか・・・。

出来る 出来るのだ

こういう観点から作ったgnuplot ライブラリ、それが eazy-gnuplot です。SQLやhtmlなどある程度形式的な文法がきちっと定義されている言語と違い、gnuplotのスクリプト言語はぐちゃぐちゃです。そのような言語に対応するためには、 こちらもDirty に行く必要があります。

以下に、eazy-gnuplotのサンプルコードを示します。おおよそgnuplotのスクリプトにほぼ対応しているのがわかるでしょうか。

(with-plots (*standard-output* :debug t)
   (gp-setup :xlabel "x-label"
             :ylabel "y-label"
             :output #p"sample.png"
             :terminal :png
             :key '(:bottom :right :font "Times New Roman, 6")
             :pointsize "0.4px"
             :yrange :|[0:1]|)
   (format t "~%unset key")
   (plot "sin(x)" :title "super sin curve!")
   (plot (lambda ()
           (format t "~&0 0")
           (format t "~&1 1"))
         :using '(1 2)
         :title "1"
         :with '(:linespoint))
   (plot (lambda ()
           (format t "~&0 1")
           (format t "~&1 0"))
         :using '(1 2)
         :title "2"
         :with '(:lines)))

このコードは、以下のようなgnuplotコードを生成してgnuplotインタプリタに入力し、グラフを描画します。おおよそ元のコードに対応していることがわかります。

set xlabel "x-label"
set ylabel "y-label"
set output "sample.png"
set terminal png
set key bottom right font "Times New Roman, 6"
set pointsize "0.4px"
set yrange [0:1]
plot sin(x) title "super sin curve!", '-' using 1:2 title "1" with linespoint, '-' using 1:2 title "2" with lines
0 0
1 1
end
0 1
1 0
end

大切なのは拡張性です。結局の所テキストに書き出すわけですから、gnuplotに出来ることならなんでもできるというのが言えるでしょう。たとえば yrange の指定。 これは元のlisp では :|[0:1]| と書かれており、これが出力結果では [0:1] になっています。eazy-gnuplot では、オプションとして与えられるオブジェクトは gp-quote という関数を経てプリントされます。この関数は、

動作
symbol 全文字がuppercaseの場合, lowercase でプリント
それ以外はそのままプリント
string/pathname "クオート"付きでプリント
cons 再帰的にgp-quote
ほか print関数でプリント

というルールでオブジェクトをプリントします。殆どの場合 普通のsymbol と string でgnuplotコマンドを記述することが出来ますが、lisp側の都合でそれが困難な場合には || でシンボルにしてしまえば無理矢理でも出力してしまうことが出来るのです。

これが役立つ他の例は、例えば以下のような例。

:key `(samplen 0.8 |Left|)

set key コマンドは、leftLeft の2つのオプションがあり、大文字と小文字で意味が変わります。あまりにも自明ですが、|Left| のようにクオートしてしまえば、このイレギュラーなケースにも対応できます。自明に柔軟なので、将来gnuplotのバージョンが上がった時でも、何の問題もなく新しい機能の恩恵に預ることが出来るでしょう。

最頻出機能に対するいくつかの関数

eazy-gnuplot の全ての機能は、結局の所このような文字列出力に帰されます。しかし、たとえどんなコマンドでも出力できるとはいえ、いくつかの最もよく使う機能には専用の関数を用意してあります。

(plot what &keys &allow-other-keys) 関数がそれです。キーワードには上のgp-setupと同様にオプションの列を記述します。 what の型に応じて、以下のように動作が変わります。

動作
string 関数として (e.g. "sin(x)")
pathname ファイル入力として (e.g. #p"data.csv")
関数 実行結果を inline data として

同様に splot と fit を用意してあります。

まだeazy-gnuplot側で対応していない機能に対しても、結局の所 with-plots で指定したストリームに文字列を書きだせば目的を果たすことが出来ます。例えば replot ですが、

(with-plots (s)
  ...
  (format s "~&replot")
  ...)

とすることで使えます。しかし、もうすこし綺麗に書きたい場合もあるかもしれません。その場合には、何でも使える gp コマンドがあります。これは引数にgp-quote を適用して出力するだけの便利なコマンドです。たとえば以下のように用います。

(gp :set :key ...)
(gp :replot)
(gp :unset :key)
...

Lessons Learned

eazy-gnuplot を作るにあたって良かったのは、「あんまり作りこまない」という決断ができた点です。将来のライブラリ構築に際して参考になれば幸いです。

eazy-gnuplot は clml の作者 Mike Maul さんにいたく気に入ってもらい、パッチを幾つかうけたほか、このようなすばらしいcookbookをいただきました。今見た所、いくつか古いapi (func-plot の機能は plot にすでに取り込まれています。) が残っていますが、この例の多さからも、eazy-gnuplotの柔軟性がわかっていただけるでしょうか。

ほぼ gnuplot 準拠なので、あらたに覚えることが少ないという点も良い点です。オプションを調べる際には今までどおりgnuplotのマニュアルを読めばよろしい。

最後に、わたしがこれで作った幾つかのグラフの例を示して、アドベントカレンダーを終えたいと思います。

研究室サーバのジョブ数とコア温度の監視。

とある探索問題でのsearch depth とノード数をアルゴリズムごとに比較した図。

とある探索問題集で全体のノード数と探索の最終段階のノード数を比べた図。(ほとんどのノードが最終段階で訪問されている。)