2010年7月29日木曜日

再帰曲線

ドラゴン曲線に関する最初のブログ(2009年6月19日)に書いたことだが,...

左折を+, 右折を-と表示すると
+ + - + + - - + + + - - + - -
になる. 記号は15個あるが, 中央の + が最初の折り目で, その両側はもともと重なっていたのを開いたから, 並び順が中央を中心にして対象で, しかも+と-が入れ替わっている.

これを X2 + Y2 と書くと,
X2 = + + - + + - -
Y2 = + + - - + - -
である. するとこの両枝は同じなので,
X2 = X1 + Y1
Y2 = X1 - Y1
X1 = + + -
Y1 = + - -
最後は
X0 = +
Y0 = -
この+と-のパターンはなんだろうと思っていたが, はたと思いついたのは, Gray codeであった.

下の図は通常の二進法とGray codeの対応を示す. Gray codeの右の+と-は, Gray codeの1の数が, すぐ上のcodeのそれと較べて, 増えたか減ったかを示す. これが上のパターンと同じなのである.




なぜかというと, 枠で囲った部分がX2とY2で, その間が, 一番左の桁が1になったことで + になっているのである.

この図からははみ出すが, 16番のところは右から5ビット目が1になり, +となり, 24番のところは右から4ビット目が0になり, -になるのである.

一方, 通常の二進法の右の小さい丸は, 右から1ビット目と2ビット目, 2ビット目と3ビット目のように, 連続して2つの1が続く状態が現れた場所を示す.

1が連続すると, Gray codeの作り方から分かるように, 排他的論理和をとるので, 1が減るのである.

従って, -が現れるのは, 4n+3か, 8n+6か, 16n+12か, ...である.

これだけ分かるとドラゴン曲線のプログラムは書ける.


(define (test n p)
(cond ((= (modulo n 4) 3) #f)
((= (modulo n 8) 6) #f)
((= (modulo n 16) 12) #f)
((= (modulo n 32) 24) #f)
((= (modulo n 64) 48) #f)
((= (modulo n 128) 96) #f)
((= (modulo n 256) 192) #f)
((= (modulo n 512) 384) #f)
(else #t)))

山場はこのtestだ. こういつまでも書くわけにはいかないから, nがどんなに大きくてもいいように, 下のプログラムでは書き直してある.


(define (moveto x y)
(display (number->string x)) (display " ")
(display (number->string y)) (display " moveto\n"))

(define (rlineto x y)
(display (number->string x)) (display " ")
(display (number->string y)) (display " rlineto\n"))

(define limit 64)

(define (test n p)
(cond ((> p (* 2 n)) #t)
((= (modulo n p) (* 3 (/ p 4))) #f)
(else (test n (* p 2)))))

(define (foo n dx dy)
(rlineto dx dy)
(if (< n limit)
(if (test n 4) (foo (+ n 1) (- dy) dx)
(foo (+ n 1) dy (- dx)))))
(moveto 0 0)
(foo 1 20 0)

2010年7月24日土曜日

再帰曲線

このブログにドラゴン曲線のことを書いたのは, 1年くらい前のことである.

ウェブでドラゴン曲線の画像を眺めていたら, 4つのドラゴン曲線を組合わせて, 平面を埋めている絵があった. 早速描いてみることにした. 次がその戦果である.


もちろんドラゴン曲線は角で直角に曲がるのだが, 通過の仕方が分かるように, 四角の部屋を丸く掃く居候モードで描いてある.

問題は, それぞれの色のドラゴンが重ならないかということだ.

ちゃんと証明するのは面倒だが, ドラゴン曲線をフラクタルで描くステップを考えると, 当たり前のような気がする.

フラクタルでの描き方は次のようだ.



まず0次の線を赤のように引く. 左から右に向って引いている積りだ.
次に1次の線を青のように, 赤の線分を右に三角形に膨らませるように引く.
2次の線は, 橙のように, 青の線分を最初は右に, 次は左に膨らませる.
3次の線は, 緑のように, 橙の線分を右, 左, 右, 左と膨らませる.
これを適当な次数まで繰り返す.

描画アルゴリズムとしては, n次のフラクタルを描きたいというサブルーチンで始める. 次数が0なら始点と終点を引く. そうでないなら, n-1のフラクタルのサブルーチンを呼ぶのである.


このようにして描いた, 0次, 2次, 4次, 6次は次のとおりである. 8次の図はこのブログ先頭のものだ.






重ならない理由がなんとなく納得出来たであろうか.

ついでに10次と12次の図も示すと次のとおり.


2010年7月10日土曜日

ビットの反転と置換

Hacker's DelightにもTAOCPにもSchroeppelのビット反転法の話が登場する. もとはHAKMEMの167項の一部にある話である.

Hacker's Delightでは(102ページ), 64ビットレジスタで7ビットxの反転を((x*0x40100401)&0x442211008)%255 と書いてあり, TAOCP(V4F1,26ページ),0<=x<2gの反転をt←((ax mod 2n)&b, y←((ct)mod 2n)>>(n-g) ただしn=g2, 0≤x<2g, a=(2n+g-1)/(2g+1-1), b=2g-1(2n-1)/(2g-1), c=(2n-g-1)/(2g-1-1) とある.

まずDelightの方.

((x*A)&B)%255
ただし A=0x40100401, B=0x442211008 と書き直す.

このAとBを二進にする.
A=100 0000 0001 0000 0000 0100 0000 0001
つまり4つの1の間に0が9個ある.
B=100 0100 0010 0010 0001 0001 0000 0000 1000

従って 7ビットgfedcbaにAを掛けると

gfedcba
1000000000100000000010000000001 A
gfedcba000gfedcba000gfedcba000gfedcba A*x
10001000010001000010001000000001000 Bでマスクすると
e a f b g c d これらが残る
43210 6543210 6543210 6543210 6543210 8ビット内の位置
11111111 28-1で割った剰余
abcdefg 反転出来た.

ビットに右から番号をつけるとaの位置は0, bは1, ..., gは6.
Aを掛けるとaの位置は0,10,20,30になり, 8の剰余は0,2,4,6になる. Bで6のaを取る. bの位置の8の剰余は1,3,5,7で5のものを取る. そういう仕掛けである.

TAOCPの方は, 定数に(2p-1)/(2q-1)が多いが, これはqビットごとの1のあるパターンを作る常套手段である.


g=3とすると, n=9. 従って

a=100010001
b=100100100
c=10101

x=rqp
これにa=100010001を掛けると
rqp0rqp0rqp
2nでmodをとると
p0rqp0rqp
b=100100100
でマスクする
p00q00r00
c=10101を掛けると
p00q00r00
p00q00r00
p00q00r00
p0pqpqrqr0r00
2nでmodをとると
pqrqr0r00
n-gビット右シフトすると
pqr
と反転出来る.


これは, 反転すべきパターンを位相をずらして複製し, 必要な部分を取り出し, Delightのように2n-1で割った剰余で揃えるか, TAOCPのように(割り算は出来ない)cをかけて混ぜ合わせ, 途中に出来た部分を取り出すかである.


TAOCPのやり方で, もっと一般的な置換が出来ないか考えたのが, 今回のブログのテーマである. 例として64ビットレジスタで8ビットのパターンを任意に置換する.

x=76543210 (ビットの名前)
とする.
置換は (0,1,2,3,4,5,6,7)->(3,2,4,1,6,0,5,7)
つまり75061423にしたい.


次のようにする.
a=0x8040201008040201
b=0xbfdfeff7fbfdfeff
y=(xa % 2^64) & b
c=0x0101010101010101
d=0x4020100804020100
z= ((xc % 2^64) >> 1) & d
m=0x14012000000a4080
(((y|z & m) * c) & (2^64-1)) >> 56

Schemeでは
(define (genperm x m) ;m permutation mask
(let* ((a #x8040201008040201) (b #xbfdfeff7fbfdfeff)
(c #x0101010101010101) (d #x4020100804020100)
(y (band (modulo (* x a) (expt 2 64)) b))
(z (band (>> (* x c) 1) d)))
(>> (band (* (band (bor y z) m) c) (- (expt 2 64) 1)) 56)))
(define m #x14012000000a4080)
実行してみると
(genperm #b11110000 m) => #b11010100
(genperm #b11001100 m) => #b10010011
(genperm #b10101010 m) => #b11001001

yはこうなる.

y= mask
76543210 ff 64ビットの内 最右の8ビット
6543210x fe
543210x7 fd
43210x76 fb
3210x765 f7
210x7654 ef
10x76543 df
0x765432 bf 最左の8ビット x: ドントケアビット

xc>>1 はこうなる.

07654321
07654321
07654321
07654321
07654321
07654321
07654321
?7654321 ?: 右シフトで左から挿入されたビット

z= ((xc mod 2^64) >>1) & d

z= mask
xxxxxxxx 00
xxxxxxx1 01
xxxxxx2x 02
xxxxx3xx 04
xxxx4xxx 08
xxx5xxxx 10
xx6xxxxx 20
x7xxxxxx 40

y|z は各列に0から7を1つずつ含む.

y|z & 0x14012000000a4080 <= 置換用マスク

76543210 7 80
65432101 5 40
54321027 1 2 0a
43210376 00
32104765 00
21057654 0 20
10676543 3 01
07765432 6 4 14
75061423

0x0101010101010101を掛ける.

7
75
75 1 2
75 1 2
75 1 2
750 1 2
75061 23
75061423

modulo 2^64 >> 56 => 75061423

完成!

置換用マスクの作り方.

00 09 18 27 36 45 54 63
08 01 10 19 28 37 46 55
56 17 02 11 20 29 38 47
48 57 26 03 12 21 30 39
40 49 58 35 04 13 22 31
32 41 50 59 44 05 14 23
24 33 42 51 60 53 06 15
16 25 34 43 52 61 62 07

y|zの行列を眺めながら作った上の表は, 0を0へ移動するには0にマスクを置く. 0を1へ移動するには9にマスクを置く. ... 0を7へ移動するには63にマスクを置く. ...7を7へ移動するには7にマスクを置く. のように読む

この表は pをqに移動するマスク位置を計算するプログラムでも計算出来る.

(define (pq p q)
(cond ((< p (+ q 1)) (+ (* -8 p) (* 9 q)))
((= p (+ q 1)) (+ (* 8 p) q))
((> p (+ q 1)) (+ 72 (* -8 p) (* 9 q)))))

上の例 0->5 1->3 2->1 3->0 4->2 5->6 6->4 7->7

(number->string
(apply +
(map (lambda (p q) (expt 2 (pq p q)))
;from pos to pos
'(0 1 2 3 4 5 6 7) '(5 3 1 0 2 6 4 7))
) 16)
=> "14012000000A4080"

2010年7月1日木曜日

再帰曲線

第1回のArtificial LifeのProceedings(1987年)を見ていたら, 下のようなフラクタルの絵があった.



当然どう描くのか不思議であるが, Wikipediaに85度というヒントがあったので, 描いてみたら, 何とも簡単であった.



をまず描くのである. この4本の線をまたフラクタルにすると,





と細かくなり, 何段目かで最初の図になる.



原点からx軸に沿い, 長さdの線を描くとする. その線を途中で折曲げて上のように描きたいから, まずd1を計算する. d1=d/(2+2*cos 85)なのは容易に分かる. 従ってdの線を描く代りに, x軸に沿い, 長さd1の線を描き, 原点を(d1,0)へ移し, そこで85度回転する. また新しいx軸に沿い,長さd1の線を描き, 原点を(d1,0)へ移し, 今度は-170度回転する. 後は図に従い, 同様にやる.

長さdの線を描く代りに, 短い線を何本か描くので, 再帰呼出しになっている. 当然どこかで止めなければならない. nなるパラメータを1つ用意し, 下請けを呼ぶ時, nを1引く. nが0で呼ばれたら, dの直線を直接引く.

PostScriptのプログラムは以下の通り.

/draw {4 dict begin
/n exch def /d exch def %パラメータを取り込む
n 0 gt % n>0なら
{/d1 d 85 cos 1 add 2 mul div def /n1 n 1 sub def
gsave %環境を待避
d1 n1 draw
d1 0 translate
85 rotate
d1 n1 draw
d1 0 translate
-170 rotate
d1 n1 draw
d1 0 translate
85 rotate
d1 n1 draw
grestore} %環境を回復
{0 0 moveto d 0 lineto stroke} ifelse end} def % n=0なら直接描く

40 40 translate
400 6 draw %起動


最終の姿は, 予想もしないものだ.

2010年6月13日日曜日

Ulam数

TAOCP V4F1にUlam数の話題があった(演習問題7.1.3--141).

なんとか数にはFibonacci数, Mersenne数, Goedel数, Meertens数, Erdos数など, 数々あるが, Ulam数はあまりポピュラーではないようで, 私は始めて聞いた. A New Kind of Scienceにはちゃんとあった.

こういう数列である.
<U1, U2,...>=
<1,2,3,4,6,8,11,13,16,18,26,28,36,38,47,48,53,57,62,69,72,77,82,87,97,99,...>
で, U1=1, U2=2, Un(n≥3)は, 0<j<k<n(jとkは違う)のUj+Ukの一通りの和で >Un-1の最小のものという定義である.

U3は, >U2だからまず3が候補で, 3=U1+U2が1通りなので, 3である.

U4は, >U3だからまず4が候補で, 同じU2を使う2+2は許されないが, 4=U1+U3が1通りなので, 4である.

U5は, >U4だからまず5が候補だが, 5は1+4と2+3と2通りで出来るので失格. 6は2+4しかないのでU5=6となる.

U6は, 6の次の7が候補だが, 7は1+6と3+4と2通りで出来るので失格し, 3+5=8になる.

こういう計算をするプログラムをSchemeで正直に書いて実行してみると, Fibonacci数以上に再帰が起き, U10くらいで辛抱出来なくなる.

上の数列は, SICPにあるメモ化に書き直して計算したものである. もちろんメモ化で断然速くなる.

演習問題の趣旨は, これをビット計算で高速化せよというものだ. もともとはBITの論文にあるらしいが, 解答を見ると以下のようになっている.

今回のブログは, このアルゴリズムの謎解きである.

aとbの2つのビット列を使う. nとm=Unまで計算が進んだとき, 0≤s<2mのsについて, aのs番目のビットの意味は, [s=1であるかs=2であるかsは<mの異なるUlam数の1通りの和かである]. bのs番目のビットの意味は, [sは<mの異なるUlam数の2通り以上の和である]である. ただし[述語]は述語が真の時1, 偽の時0とする(Iverson記法).

従ってn=2, m=U2=2の時, a=0111(左から順に, s=0だから上の述語に合わず0; s=1だから1; s=2だから1, s=3はU1+U2の1通りだから1). b=0000(どのsも2通り以上の和にならない)

さてn←n+1の時のアルゴリズムは次のようだ.



実際aとbは次のようになる.

n=3, mはam+1から右へ探して最初の1の添字, つまり0111の1番右のa3から探し始めると, いきなり1だからm=3. これがU(n=)3=3である. Un-1=2, Un=3だから, アルゴリズムの但し書き(where以下)に従い, a4...a5は0, またb4...b5は0だから, a, bは4ビットから6ビットになり, a=011100, b=000000にする. aを0から2までと3から5までに分け, bは3から5を使う.

アルゴリズムでは代入の左辺が(am...a2m-1,bm...b2m-1)のように書いてあるが, これは右辺を計算してからam...a2m-1とbm...b2m-1に代入したいという気持である. つまり

新しいaの3から5は, (100⊕011)&¬000=111.
新しいbの3から5は, (100&011)|000=000.

従ってa=011111, b=000000になる.
次はn=4, m=(a4から右へ探すので)4. 但し書きによりa=01111100, b=00000000になり, aの4から7は (1100⊕0111)&¬0000=1011. bの4から7は(1100&0111)|0000=0100. 従ってa=01111011, b=00000100.

では, これはなにをやったか.

a=011100の時, U1,U2,U3は1通りの和で作れるということであった. aの3から5は100だったが, まずU3の1はこれが1通りの和であることを示す. それにaの0から2の011をxorするのは, 左からの位置sはm+sの和で作れることを示す. 3については3+0はない; 4については3+1がある; 5については3+2があるということだ. すでに1通りで出来るものは, xorでその条件がリセットされてしまう.

一方, bm+sはリセットされる時はasとam+sが共に1なので1になり, つまり2通りで和が作れることを記憶する.

またaに戻り, その後, bs+mの否定とandをとるのは, 一度リセットされたら, あとは別の和で作れることが分かっても, 1に戻さないためである.

またbの方は, 前の値とorをとるから, 一旦2通り以上となれば, ずーっとこのビットは設定されたままになる.

かような次第で, このアルゴリズムを1回使うと, 次のUlam数が1つ得られる.

MIT Schemeにはbit-stringというデータ型があり, and, or, xor, not, stringappend, substringがとれ, またビットを探す関数もあるから, このアルゴリズムを書くのにお誂え向きである. ただしビットの番号は右端が0なので, 上の説明とは添字の向きが反対になる.

3から始め, c個のUlam数を計算するプログラムは以下の通り.

(define a (unsigned-integer->bit-string 4 14))
(define b (unsigned-integer->bit-string 4 0))
(define n 2) (define m 2)

(define (ulam c)
(if (> c 0) (let* ((m1) (z) (a1))
(set! n (+ n 1))
(set! m1 (bit-substring-find-next-set-bit
a (+ m 1) (* m 2)))
(display (list n m1)) (newline) ;nとmのリストを出力する.
(set! z (make-bit-string (* (- m1 m) 2) #f))
(set! a (bit-string-append a z))
(set! b (bit-string-append b z))
(set! m m1)
(set! a1 (bit-string-and
(bit-string-xor (bit-substring a 0 m)
(bit-substring a m (* m 2)))
(bit-string-not (bit-substring b m (* m 2)))))
(bit-substring-move-right! (bit-string-or
(bit-string-and (bit-substring a 0 m)
(bit-substring a m (* m 2)))
(bit-substring b m (* m 2))) 0 m b m)
(bit-substring-move-right! a1 0 m a m)
(ulam (- c 1))) 'ok))

実行結果は

(ulam 10)
(3 3)
(4 4)
(5 6)
(6 8)
(7 11)
(8 13)
(9 16)
(10 18)
(11 26)
(12 28)
=> ok

面白いよね.

2010年6月9日水曜日

楕円反射

前回のブログで, 共焦点二次曲線のことに触れた. 共焦点二次曲線もつい描いてみたくなる優美な図である.

教養学部の学生の時, 最初の製図の課題が二次曲線を描くことであった. この時は曲線群ではなく, 1本の楕円と1本の双曲線だったと思う. 1本とはいえ, 烏口で墨入れして描くのだから大変であった.

楕円を描こうと思うと, 長軸 a と短軸 b を決める, つまり楕円の外側の寸法を押さえることから始めるのが普通であろう. 例えばa = 200, b = 150; アスペクト比4:3の楕円を描くことを考えてみる.

xを-200から適当な増分のステップで200まで増やし, xに対応するyを計算して次のx,yまで線を引く, でまぁ出来るわけだが, x=200, -200の辺りは勾配が大きく, xの増分を小さくしなければならなす, x=0の辺りは勾配は小さく, 増分は大きくしたい. 増分を途中で変えるくらいなら, x=200の辺りは, yを独立変数にした方が楽である.

切り替えはx^2/a^2=1/2, y^2/b^2=1/2の付近でやるのがよいかも知れぬ. かくして1象限分を描くPostScriptのプログラムは

/a 200 def /b 150 def /d 5 def %dは増分
/soly {1 dict begin /x exch def %xからyを解く
1 x x mul a a mul div sub sqrt b mul end} def
/solx {1 dict begin /y exch def %yからxを解く
1 y y mul b b mul div sub sqrt a mul end} def
/rd {d div round d mul} def %増分の倍数で丸める
/x1 {a a mul 2 div sqrt rd} def %切り替え点
/y1 {b b mul 2 div sqrt rd} def %切り替え点

/quadrant {0 b moveto %1象限分を描く
d d x1{/x exch def x x soly lineto} for
y1 d neg 0{/y exch def y solx y lineto} for
stroke} def

これを4回呼び出す.

quadrant 1 -1 scale %第1象限 x軸に対称にする
quadrant -1 1 scale %第4象限 y軸に対称にする
quadrant 1 -1 scale %第3象限 x軸に対称にする
quadrant -1 1 scale %第2象限 y軸に対称にする


そのようにした描いたのが上の図だ. 第1象限の赤い格子は, xを変数で描いた部分と, yを変数で描いた部分を示す. x=140, y=105 辺りで切り替わっている.


しかし, 媒介変数を使った楕円の表現の方が, 一気に書けて嬉しい. 0≤t<2πについて, (a*cos(t),b*sin(t)) の点を次々に結ぶ. 以下の共焦点の二次曲線は媒介変数法で描いてある.

さて, 共焦点の楕円を書くには, まずfを決め, 次に離心率eをパラメータとして決め, それらからaとbを求めることになる.

a=f/e, b=√(a^2-f^2)として, 媒介変数法で描画するわけだ.

双曲線は, やはり媒介変数による表現がある. 楕円は通常のcosとsinであったのに, それがcoshとsinhになる. つまりhyperbolic cosineとhyperbolic sineになるわけで, hyperbolic(双曲線の)という修飾があるのは, これで分かる.

さようなわけで,
sinh x=(e^x-e^(-x))/2,
cosh x=(e^x+e^(-x))/2
を定義しなければならない.

こうして描いた共焦点二次曲線の絵が上の図である. 楕円の方は, eを0.5から0.9まで0.05ステップで変え, 双曲線の方はeを1.1から1.5まで0.05ステップで変えて描いてみた. a=f/eは曲線がx軸と交差する点(中央からの距離)なので, 楕円では, 0.5が円に近い(焦点から遠い)方, 双曲線では, 1.1が放物線に近い(焦点に近い)方である.

2010年6月8日火曜日

楕円反射

楕円の内側が鏡だとして, 鏡の中の, 焦点以外の1点からの光が楕円の壁で反射し, その光がまた壁の別の場所で反射し, それを逐次繰り返すとどうなるか. これは高橋秀俊:「数理と現象」の92ページと113ページに書いてある話だ. そこを読むと, これは森口研でXYプロッタを使って描いてみて, 実験したというので, その図が同書に載っている.

森口研にいた私だが, XYプロッタのグループとは別であった私は, 今頃になって, その絵をどのようにして描くかということに興味を持った. 今ならXYプロッタでなく, PostScriptを使うことになる.

結論からいうと, 思っていたより簡単であった. 今回はそれを報告しよう.



この図は長軸の左端Aからある勾配で右方向へ出発した光がBで楕円に当り, 反射した光が次にCで楕円に当り, ... を10回程度したところである.

楕円上の点(x0, y0)から勾配 n/mで楕円の内側に出発した直線が, 反対側で楕円に突き当たる点の座標(x1,y1)を知りたい.

それには,
x2/a2+y2/b2=1 (楕円)
(x-x0)/m = (y-y0)/n (直線)
を解く.

具体的には, a=175, b=150の楕円で, x0=-175, y0=0からm=3,n=1で出発してみる.

まずWolframAlphaで
solve (x+175)/3=y, x^2/175^2+y^2/150^2=1
とやってみると,
Results:
x=-175, y=0
x=48215/373 = 129.021 y=37800/373 = 101.340
が得られたから, Bの座標(x1,y1)は129.021と101.340である.

私がPostScriptで書いたプログラムでやってみると, 当然xの解は2つ得られるが,x0でない方がx1で, それに対してyを計算するとyも上下2つ得れ, そこで, 直線の式に載っている方を採用する. その結果, これでも129.021439 101.340492 が得られて安心する.

次に反射する角度を得なければならない. まず基本知識として, 焦点から出発した光は, もう1つの焦点へ集まるということから, Bと焦点F0, F1を結ぶ線は, 入射角と反射角が等しいという関係にある. この証明は以下のようにやる. (texで書いたものを張り付けた.)



2本のピンに絡めた輪と鉛筆を使って楕円を描く手法を考えてみても, 上の性質は確からしい.



従ってABF0の角をBF1に足せば, Cへの方向が求まる. つまり
θA=tan-1(y1-y0)/(x1-x0).
θ0=tan-1y1/(x1+f).
θ1=tan-1y1/(x1-f).
θC=(θ0A)+θ1.

それを新しい勾配m,nとし, x1, y1を改めてx0,y0として, いまの手順を繰り返すと下のような図が得られる.



短軸の下の方から, かなり上の方向を狙って出発すると, こういう図も得られる. いずれにしろ, 包絡線は, 楕円か双曲線になるが, それもF0, F1を焦点とする, いわゆる共焦点円錐曲線(Confocal Conics)になる.



このようにして出来た図はきれいに見えるが, 領域を万遍なく埋めて, 最後をぴたりと出発点で止めるには, 試行錯誤が必要であった. 簡単な場合を描いてみると





のようになる. なんかLissajous曲線の楕円版を描いている気分であった. 上の2つのきれいな絵は, 出発角度と繰返し回数を何回も調整して得られたものである. XYプロッタなら, こういう実験に時間が掛ったであろうが, PostScriptでは瞬時に結果が得られ, 楽々に実験を進めることが出来た.