雪片の観察

snowflake

この間、図書館へ行ったら、SNOWFLAKEって書名の写真集もどきの本が有ったので、借りてきた。フレークと言うと、ツナフレークぐらいしか思いつかない(食い意地の張った)オイラーに取っては珍しい事。

これもそれも、数回前にやったsnowflake.psに魅せられていたから。神の啓示かな。美しい物を探求せよ。美しいのは、綺麗なおねーさん好きですかだけじゃない。雪は美しく、はかない命。美しい人よりも、ずっとずっと短命だ。

この雪の結晶って、水が凍って出来るものではないそうだ。水蒸気から水の状態を一気に飛び越えて氷になった時に出来るとか。凝縮現象と言うそうだ。これとは反対に、個体がいきなり気体になる現象を昇華って言うそうだ。あの、箪笥にゴンでお馴染みのナフタリンがそれだ。

世界で初めて、人工的に雪の結晶を作り出したのは、北海道大学に居た、中谷宇吉郎先生。学校を卒業したはいいけど、就職先がなくて、やむなく北大に残った。雪国ならでは研究をしたいって事で、雪に挑戦したらしい。

雪になるには、その核が必要。色々やって、うさぎの毛が適度な油脂があって好都合だったとか。気温と湿度によって、出来る結晶の形が大きさが劇的に変わるらしい。 だから、雪の形を観察すると、空の上の状態がある程度、推測出来るらしい。

雪は6方向に枝を伸ばしたものが主流。これは、水の分子構造に由来してるらしい。だから、決して4方向とか5方向に延びる雪は出来ないとの事。知りませんでしたよ。

prostsript debug

以前に見つけた雪の結晶を描くpostscriptがどういう具合になってるか、研究してみる。例によって、しばし資料収集。

Postscript (Third Edition) Section 8.2にコマンドの説明がある。

ppostscript book (PDF and ps) 一般向けの解説書。

アドビ ポストスクリプト技術へのおさそい

Debugging PostScript (short debugger) 簡単なdebuggerが紹介されてた。

そして、何と言ってもソースが一番。ghostscriptを展開しておこう。doc/Source.htmに、何処見ろワンワンが説明されてる。

The Ghostscript source code is divided conceptually as follows:

         PostScript interpreter:
         PostScript operators        z*.h and z*.c
         Other interpreter code      i*.h and i*.c
         PostScript code             gs_*.ps
         PDF interpreter:
         PostScript code             pdf_*.ps
         Graphics library:
         Main library code           g*.h and g*.c
         Streams                     s*.h and s*.c
         Device drivers              gdev*.h and gdev*.c
         Platform-specific code      gp*.h and gp*.c

頭がzで始まるファイルは、dupとかaddみたいなのが収録されてるんだな。iから始まるやつはインタープリター系か。ここまではforthと同じ考え方。これらは、展開したソース群でpsiって言うdirの中にまとめて入っている。

ちょっと実験するなら、下記の起動法が良い。

ob$ rlwrap gs -dNODISPLAY

X無しで起動、行編集が出来るように、rlwrapをかませる。勿論、emacsのps-modeを使うのも有り、だけど、おじさん使用(仕様)だからなあ。

これ、最小のdebuggerだそうです。

<<
/break{ /hook /pause load store }
/cont{ /hook {} store }
/doprompt{
    (\nbreak>)print
    flush(%lineedit)(r)file
    cvx {exec}stopped pop }
/pause{ doprompt }
/hook{}
>> begin

use debugger

ちょいと簡易版を使ってみる。

vbox$ rlwrap gs -dNODISPLAY
GPL Ghostscript 9.07 (2013-02-14)
Copyright (C) 2012 Artifex Software, Inc.  All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
<<
/break{ /hook /pause load store }
/cont{ /hook {} store }
/doprompt{
    (\nbreak>)print
    flush(%lineedit)(r)file
    cvx {exec}stopped pop }
/pause{ doprompt }
/hook{}
GS<11>>> begin
GS>3 4 5 pause mul

break>stack
5
4
3
GS<2>cont
GS<2>stack
20
3

起動直後にdebuggerを登録。続いて、3 4 5 を積んでから pause させ、続きは掛け算ってのを投入してみた。

pauseで一度止まってくれた。そこで、stackの状態を確認。それから、継続してあげる。データが2個消費されて、掛け算の結果に置き換わっているはず。最後にstackで結果を確認。確かに、RPNなカリキュレーター動作になってるな。

HPの有名な電卓が有りましたねぇ。高くて買えなかったけど。そこで眼を付けたのはシャープの関数電卓だった。学校では、計算尺何級とかってやってた頃だ。ちょいと思い出したように、カウンターをgsでやってみる。

GS>/cnt 0 def           % init cnt to 0
GS>cnt =                % examine
0
GS>/cnt cnt 1 add def   % incriment cnt
GS>cnt =                % access cnt
1
GS>/cnt cnt 1 add def   % incriment
GS>cnt =                % access cnt
2

一度定義した変数は、ずっと居座るので、上記の操作が出来る。

GS>[1 2 4] stack
--nostringval--
GS<1>pstack
[1 2 4]

簡易debuggerを使ってみたけど、究極はprintデバッグってのは、昔も今も変わらず。確認する対象がスタックしかないので、それを確認する命令をちょいと追加するだけ。2種有るけど、中身が展開されるpstackがお勧め。得意のちゃぶ台返しで、おちを付けます。

環境調査

冒頭で出て来た中谷先生も最初は雪片の観察からのスタートだった。オイラーも真似して、観察修行だ。最終的には、雪片を自在に発生させたい。

雪を描かせるスクリプトは90行ぐらい。関数の名前からすると、newflakが一輪と言うか一片の雪を描くものみたい。残りは、紙のサイズで、何片描くか計算してるっぽい。

環境が広々してれば沢山描ける。狭ければそれなりだろう。そういう所から確かめる。

vbox$ gs -sPAPERSIZE=a2 snowflak.ps   6x4
vbox$ gs -sPAPERSIZE=a3 snowflak.ps   4x2
vbox$ gs -sPAPERSIZE=a4 snowflak.ps   3x2
vbox$ gs -sPAPERSIZE=a5 snowflak.ps   2x1

6x4とかの表現は、縦に6個、横に4個、合計で24個の雪片が描かれるって意味だ。ペーパーサイズオプションを付けないとA4サイズを指定したと見做されて、6個の表示となった。 勿論、パソコン画面の大きさは一定なので、多数を表示させると、個々の雪片は縮小される。

印刷業の方の常識らしいです。

用紙サイズ (単位: point)
    A3: 1190.55 x 841.89
    A4: 841.89 x 595.276
    A5: 595.276 x 419.528 

雪片を何個か描いて、gsのプロンプトに戻った所で、一番簡単そうなxって関数を実行してみる。

GS>x
Error: /undefined in x
Operand stack:

Execution stack:
   %interp_exit   .runexec2 .... %stopped_push   --nostringval--
Dictionary stack:
   --dict:1165/1684(ro)(G)--   --dict:0/20(G)--   --dict:77/200(L)--
Current allocation mode is local
Last OS error: 35
Current file position is 2

xなんて関数は未定義とな。その下に出てるのは、gsの環境か。環境と言っても陽にメモリーが見える訳ではなくて、スタックが3本有るんだな。

Operand Stackは、演算データやプログラム片が載るやつか。Execution Stackは、関数の呼び出し状況を保持だな。Dictionary Stackは、関数とか変数の格納用だな。前前回に調べた限りでは、辞書は複数登録出来るとな。要するにハッシュテーブルを複数持てるって事か。

/x {rand 70 mod abs} def

無いと言われた関数。昔ちょっとかじったforthの知識によれが、乱数を積む、70を積む、余りを求めて絶対値を取れだな。

GS>rand 70 pstack
70
5042100
GS<2>mod pstack
0
GS<1>-12345 70 mod abs stack
25
0

それはいいんだけど、定義されてるはずのxが何故無い? 目を皿にしてコードをながめると、 最初と最後がこうなってた。

/snowflaksave save def          % prevent left over effects
  :
showpage
clear cleardictstack
snowflaksave restore

ページを表示した後、辞書をクリアしてるよ。そしてその環境を一端saveしてから呼び戻してる。綺麗さっぱりにしてる。日本人なら、飛ぶ鳥後を汚さずって表現を思い出すな。オイラーは、現状確認したいんで、これを削除してしまうぞ。

GS>3 {x} repeat stack
1
68
8
GS<3>

今度は、ちゃんとアクセス出来た。GSのプロンプトに出てる<3>は、スタックに3個のデータが有りますよと教えてくれている。

GS>pagewidth ==  %% same as urx
595.0
GS>pageheight == %% same as ury
842.0
GS>boxsize ==
280.666656

コード内で使われている大事な値。これらは、下記が元祖みたいだ。clippathって何だろう?

GS>pathbbox pstack
842.0
595.0
-0.0
0.0

小さくなーれ

元のコードは長いので、小さくする事に注力してみる。これ、昔からのお得意戦法。コードの本質を失わないように、装飾をそぎ落としているうちに、段々と理解が深まってくるからね。

疑問な所はgsのプロンプトから実験してみるなり、debuggerでpauseさせてstackをdumpしてみればよい。確認する対象がstackしか無いと言う潔さがすがすがしいぞ。

前項に書いた、環境調査ってのは、その一環だった。newflakがインデントも無しに書いてあるんで、少々悩んだんだ。/xxx { … } def って形で関数が定義されてるから、viの%コマンドを使って、{ の相方を探してしまったぞ。関数定義が一望出来る範囲に抑えて欲しい。それと視覚的な要素も大事。

で、63行目あたりから、いわゆるmainが始まってると推測したんだけど、それ以降にも関数定義が表れていてわけわかめ。悩んだのは、

clippath pathbbox /ury exch def /urx exch def /lly exch def /llx exch def

この理屈が分からなかったんよ。で、アドビの資料参照。冒頭のclippathは、無視して鎧恣意。次のpathboxで、4つのデータがスタックに積まれるとな。後は、それらのデータを取り出してきて、それぞれに名前を付けてるって判明。これ、postscriptの定石だな。

それが理解出来たら、後は愚直にコードを読むだけ。ぐちょぐちゃ有る関数は、最小サイズ250以上で、与えられた紙サイズにフィットするboxsizeを決めるためだった。それが決まれば、 指定回数をリピートさせるだけで雪片を書いていける。

次はいよいよ雪片の中。どうしても一望出来るように装飾をそぎ落とした。装飾とオイラーが認定したのは、boxisizeの箱を描いて、内部を色付けしてる所。雪片のバックグラウンドがカラフルになって、美術的には良いだろうけど、学術的には無駄。

後、疑問だったのは、/strokecolor 等の、定義。関数の中に隠れてしまっていたので、コピペして実行、結果を確かめた。配列の中に定義が書いて有ったら、それを実行して、簡約されたら、配列のエレメントに追加されるんだね。これらも、定数のRGBで置き換えてしまったら、更に縮小出来るけど、元作者に敬意を払ってそのままにしてる。

雪片の中では、乱数が多用されている。いや、もっと大事な事は、同一な乱数列を使ってるって事。乱数発生器の初期値はsrandで与える。この種を常に同一になるように定数で与える。

新しい版では、こうなってた(元にしたのは、古い版のやつ)。

% we make the seed deterministic for testing - the orginal code seeded
% the generator with user time.
/lastseed 0 def
/seed {lastseed dup 1 add /lastseed exch def} def

オリジナルだと種にusertimeってのが使われていたんだ。面白そうなので後で見る。

最後は核心部分のarmだ。スマホの元になるCPUの設計図を売ってるあのARMじゃなくて、腕ね。 否、雪片だから、枝って言う方が良いかな。

一本の腕を書いたら、全体をぐるっと60度回転させる。それを6回繰り返せば、りっぱな雪片になる。肝心の腕は、ベジェ曲線を3本組み合わせ、それを5回繰り返している。何で15回一気にやらなかったのだろう? 理由は良く分からん。

ここで、乱数発生器を一度初期化してから原点に移動。同じ事を繰り返している。が、座標xの正負を反転させてる。言わばX軸を中心に対象の曲線を描いているんだな。なんたって、雪片には対称性がありますからね。

後は、色付けですね。ああ、やっと理解出来たぞ。

code

%!PS-Adobe-3.0 EPSF-3.0
%%BoundingBox: 0 0 300 300
%% Elizabeth D. Zwicky, Modfy by sakae

/boxsize 280 def
/x {rand 70 mod abs} def
/y {rand 120 mod abs} def
/newflake
{  /seed usertime def  seed srand
   /strokecolor  [rand 99 mod 100 div               % for Red
                  rand 99 mod 100 div               % for Green
                  100 rand 22 mod sub 100 div] def  % for Blue
   /fillcolor  [rand 99 mod 100 div
                100 rand 22 mod sub 100 div
                rand 99 mod 100 div] def
   /eofillcolor [rand 99 mod 100 div
                 rand 22 mod 100 div
                 100 rand 22 mod sub 100 div] def
   /colorfill {fillcolor aload pop setrgbcolor fill } def
   /colorstroke {strokecolor aload pop setrgbcolor stroke } def
   /eocolorfill {eofillcolor aload pop setrgbcolor eofill } def
   /arm {0 0 moveto
         5 {3 {x y x y x y curveto} repeat} repeat
         seed srand
         0 0 moveto
         5 {3 {x neg y x neg y x neg y curveto} repeat} repeat
         seed srand
        } def

   newpath
   seed srand
   boxsize 2 div boxsize 2 div translate  % Org is center of box

   6 {arm 60 rotate} repeat

   gsave colorfill grestore gsave eocolorfill grestore colorstroke
} def
%%%%%% main %%%%%%%%%%%%%%%%%%
1 setlinewidth clippath
newflake
showpage

こうして、見通しが良くなったら、あちこちを変更して、楽しめばよい。腕が3本の世界初の雪片を作るなんてのも自在に出来るぞ。

それから別の楽しみ方として、関数xとyを少し変形して、腕の太り具合を制御してみるなんてのも、楽しかろう。太り具合を段階的に変えれば、雪片の成長具合を確認出来そう。つい最近、東大の先生が、原子レベルで塩の結晶の成長具合を電子顕微鏡で確認出来たなんてのがニュースになってたけど、あれをあなたのパソコン上でも確認出来るよ。

view ghostscript code

上でusertimeなんて言うのが出てきたんで、これは何? マニュアル見るよりソース見れでやってみます。 psiなdir内で検索。 zmisc.cで見つかった。

/* - usertime <int> */
static int
zusertime(i_ctx_t *i_ctx_p)
{
    gs_context_state_t *current = (gs_context_state_t *)i_ctx_p;
    os_ptr op = osp;
    long secs_ns[2];

    gp_get_usertime(secs_ns);
    if (!current->usertime_inited) {
        current->usertime_inited = true;
        current->usertime_0[0] = secs_ns[0];
        current->usertime_0[1] = secs_ns[1];
    }
    secs_ns[0] -= current->usertime_0[0];
    secs_ns[1] -= current->usertime_0[1];
    push(1);
    make_int(op, secs_ns[0] * 1000 + secs_ns[1] / 1000000);
    return 0;
}

コメントに注目ですよ。わずかにforth流の、スタック変化が記されている。usertimeの前についてる マイナス記号は、スタックからの引数取り込みは無しを告げてる。後ろにある <int> は、結果がintとしてスタックに積まれるって説明だな。

この関数の直ぐ下に、標準じゃないけとと断った上で、

/* <string> getenv <value_string> true */
/* <string> getenv false */

こんなのが出てた。試してみるか。

GS>(HOME) getenv pstack
true
(/home/sakae)
GS<2>clear  (FOOOOO) getenv pstack
false

HOMEって言う環境変数はちゃんと有った。FOOOOOなんてのは、登録されていないよ。ソース見ると、得した気分だわい。

元データは、 gp_get_usertime() で得ている。この関数は、 base/gp_unix.c に置いてあった。

/* Read the current user CPU time (in seconds) */
/* and fraction (in nanoseconds).  */
void
gp_get_usertime(long *pdt)
{
#if use_times_for_usertime
    struct tms tms;
    long ticks;
    const long ticks_per_sec = CLK_TCK;

    times(&tms);
    ticks = tms.tms_utime + tms.tms_stime + tms.tms_cutime + tms.tms_cstime;
    pdt[0] = ticks / ticks_per_sec;
    pdt[1] = (ticks % ticks_per_sec) * (1000000000 / ticks_per_sec);
     :

unix流の、プロセスの消費時間を得ているんですなあ。納得です。

この関数の隣に、

/* ------ Date and time ------ */

/* Read the current time (in seconds since Jan. 1, 1970) */
/* and fraction (in nanoseconds). */
void
gp_get_realtime(long *pdt)
{
    struct timeval tp;
        if (gettimeofday(&tp) == -1) {

    /* tp.tv_sec is #secs since Jan 1, 1970 */
    pdt[0] = tp.tv_sec;

    /* Some Unix systems (e.g., Interactive 3.2 r3.0) return garbage */
    /* in tp.tv_usec.  Try to filter out the worst of it here. */
    pdt[1] = tp.tv_usec >= 0 && tp.tv_usec < 1000000 ? tp.tv_usec * 1000 : 0;

こんなコードも発見した。日時も取れるのか。

で、zmisc.cをぶらぶらしてたら、ファイルの最後の方に

const op_def zmisc_a_op_defs[] =
{
    {"1bind", zbind},
    {"1getenv", zgetenv},
    {"0.defaultpapersize", zdefaultpapersize},
    {"2.makeoperator", zmakeoperator},
    {"0.oserrno", zoserrno},
    {"1.oserrorstring", zoserrorstring},
    {"0realtime", zrealtime},
       :

こんなのが有った。一見、postscript語とCの関数の対応表って分かるんだけど、冒頭に付いている数字は何? 想像を膨らませると、実行に必要な引数の個数だな。

だったら、このパターンで検索して見ろワンワン。

sakae@pen:/tmp/ghostscript-9.53.3/psi$ grep '"[0-9]\.*[a-z]' *.c
interp.c:    {"2add", zadd},
interp.c:    {"2def", zdef},
interp.c:    {"1dup", zdup},
interp.c:    {"2exch", zexch},
zalg.c:    {"2.sort", zsort},
zarith.c:    {"1abs", zabs},
zarith.c:    {"2add", zadd},
zarith.c:    {"2.bitadd", zbitadd},
          :

全部で、600個を超えてた。随分な数だ箏。

etc