2012年12月25日火曜日

MacMahonのSuperdomino

MacManonの3色四角形Superdominoにはジグソーパズル風の変形がある. つまり黒は黒と接するが, 白は灰と接するのである. それでも解はある.

白と灰の間の線を凹凸にするとジグソーパズルになるのである. そのジグソーdominoを前回のブログの3色四角形superdominoの図に対応させて描くと次のようになる.


The Winning Waysにはそれを利用したJohn Conwayの1968年のクリスマスカードの絵がある. (ウェブを探したらやっとここに見つかった.)

とりあえず制約条件をすこし修正してプログラムを走らせると, 次の解がまず得られた. この境界をジグソーに対応させたのがその下の図だ.


このジグソーパズルが通常のそれと違うのは, 絵にまったく頼らずに完成することである. それも多くの解(余詰め?)が存在する. ユニーク解にするためにはやはり絵を描くことになろう. それでCheshire Catの絵に上の解を重ねてみたのが下の図だ.


1年ほど前, IBMカードを作ったcraftroboを使って切り出せば, ジグソーパズルが作れるはずだが, こんなに優しいジグソーパズルは作るまでもあるまい.

2012年12月23日日曜日

MacMahonのSuperdomino

The Winning WaysにMacManonのSuperdominoという話題がある. 正n角形で辺と中心で出来る二等辺三角形をm色に塗り分けたものである. m色n角形superdominoという.

4色三角形superdominoは24種, 3色四角形superdominoも24種類ある.



5色五角形superdominoは, 各色を1回ずつ使い裏返しは同じとみると12種だ. この12種で辺の両側が同じ色になるように正十二面体に貼ることができるかがクイズである.

五角形は置いておき, 三角形では辺の長さが三角の辺の2倍の正六角形の中に, 周囲を同一色にし, 内側の相対する辺が同じ色になるように詰めるのがクイズ. 四角形では4×6の長方形で同様にするというのがクイズである.

四角形の, 周囲を黒にした解の一例は以下だ.



十年ほど前, 情報処理学会誌にプログラム・プロムナードの連載があり, 私はそこで「計算機用ジグソーパズル」という記事を書いた. それ以外にも探索問題のプログラムはなんども書いたからちょっとやってみたが, 最初の解が出てくるまでは存外大変であった.

プログラムを書くには24個の箱とその辺を下の図のように番号をつける.



制約としては箱0の辺0の色は2, 箱0の辺3の色は2, 箱1の辺0の色は2, 箱2の辺3の色は箱0の辺1の色, ...のようになる.

そして4色四角形のsuperdominoのプールから, 必要に応じて回転しながら, 制約に合うものを探すことになる.

しかし闇雲に初めても最初の解もなかなか現れない. ある週末, プログラムを走らせたまま帰宅したら, 次の週の初め, 夛くの解が出ていたから, 方針としては良かったのだが, これではあまりにも手抜きであったのでなるべく準備をしてからやりなおすことにした.

左上の箱から番号順に詰めていくとすると, まず辺0がg0, 辺3がg3, 辺1と辺2はどうでもよいdomino の集合を*g0??g3とする. ?は0,1,2だから要素が9個の集合が出来る. 箱5,11,17,23用には辺1が2だから要素数3の*g02?g3. 箱18から22用には辺2が2だから要素数3の*g0?2g3. 箱18用には辺1と2が2だから要素数1のg022g3を用意する.

*2222は(((2 2 2 2) (23 0)))
*02?2は(((0 2 0 2) (13 0)) ((0 2 1 2) (15 0)) ((0 2 2 2) (17 0)))
で, 要素は((u r d l) (i j))の形である. u, r, d, lは上右下左の色(0,1,2), iはdomino番号(0〜23), jは90度の回転した数(0〜3)である.

プログラムの中心は次のようだ.
(define (test n ps is js)
 (define (up ps) (caddr (list-ref ps 5)))
 (define (left ps) (cadar ps))
 (define (try *????)
  (for-each (lambda (x)
   (let ((p (car x)) (i (caadr x)) (j (cadadr x)))
    (if (not (member i is))
     (test (+ n 1) (cons p ps) (cons i is) (cons j js)))))
      *????))
 (case n
  ((0) (try *2??2))
  ((1 2 3 4) (case (left ps) ((0) (try *2??0))
                             ((1) (try *2??1))
                             ((2) (try *2??2))))
  ((5) (case (left ps) ((0) (try *22?0))
                       ((1) (try *22?1))
                       ((2) (try *22?2))))
  ((6 12) (case (up ps) ((0) (try *0??2))
                        ((1) (try *1??2))
            ((2) (try *2??2))))

あとは同様なのでしばらく省略.
  ((24) (begin (display ps) (newline) (display is) (newline)
   (display js)) (newline))))

testの引数はpsがそれまでのdominoの列, isはi, jsはjの列である. (test 0 '() '() '())で起動する.

プログラムをしばらく走らせると解が次々と現れる. とても全解探索する気分にならないが, Martin GardnerのNew Revised Edition Mathematical Diversionsの193ページに解の総数は12261と書いてあった.

1964年の初め, Stanford大学の計算センターで, Gary Feldmanが全解を求めるプログラムを書いた. Algolで書いたプログラムはB5000計算機で40時間かかったそうだ. 結果は8ページの"Documentation of the MacMahon Squares Problem," a Stanford Artificial Intelligence Project Memo No. 12で, 1964年1月16日に計算センターから刊行された.

なおこの話題によると2009年11月21日29日のブログ 彩色立方体もMacMahonの考えたものらしい.

2012年12月14日金曜日

生後n日目

最近 還暦, 古希, 喜寿, 傘寿などの祝事で兄弟があつまる事が少くない. そういう折に私が「生れて何万何千日たった」と発言したので, まわりが似たような計算をしたがった.

私自身はJulian Date(DJ)が計算できる例の個人用電卓を携帯しているので, 「今日の日付を入力, JDに変換, 生れた日付を入力, JDに変換, 引き算」で簡単に得られる. もっともこれでは生れた日が第0日になり, 生れた日を第1日にする常識的な勘定と違うが, 満n日といえば良いかもしれない.

しかし一方, 「ちょうどn日になるのはいつかなぁ」にはJDだけでは片付かない. そこで誕生日とnを入力すると, 生後n日目になる日付を計算するウェブアプリを作ってみようと考えた.

アルゴリズムは私のブログ, 2011年5月23日のunix timeにあるのを使えばよい. 現在生きている人が対象なら, Julian Calendarは考えなくてよい. Gregorian Calendarが昔まで続いていたとするFixed Date(Rata Die)というのがあるから, 年月日からFixed Dateへの変換とその逆変換があれば, 出来たようなものである.

私はhtmlのウェブページは何回も書いたことがあるが, 計算するにはJavaScriptでも使うことになるであろう. O'reillyのJavaScriptの本は持っている. その例をみながらやって見ることにした.

<html>
<head>
<title>nth day after birth</title>
<script language="JavaScript">

この辺まではお作法のうちだ. JavaScriptでは整数の除算が見当たらなかったので, 整数除算の関数を作る.
function quotient(a,b){
var r=a%b;
var q=(a-r)/b;
return q;}

つぎは私のカレンダーのプログラムによく登場する前月までの日数の和のリストで, 平年用と閏年用を用意する.
var mon0=[0,31,59,90,120,151,181,212,243,273,304,334];
var mon1=[0,31,60,91,121,152,182,213,244,274,305,335];

次は年月日からFixed Dateを計算する関数rdだ.
function rd(y,m,d){
var a=(y-1)*365;
var b=quotient(y+3,4);
var c=quotient(y,400)-quotient(y,100);
var f=(((y%100)==0)?((y%400)==0):((y%4)==0))?mon1[m-1]:
  mon0[m-1];
return a+b+c+f+d-1;}

ご覧のように簡単だ.

反対にFixed Dateから年月日を求める関数grだ. ブログにあったアルゴリズムはfloor関数を使うが, JavaScriptにはないらしいので, 1で整数除算をすることにした.
function gr(rd){
var d0=rd+366-1;
var y400=quotient(d0,146097);
var d1=d0%146097;
var y100=quotient(d1/36524.25,1);
var d2=d1-146097+quotient(36524.25*(4-y100),1);
var y4=(y100==0)?quotient(d2,1461):
  (24-quotient((36523-d2)/1460.96,1));
var d3=(y100==0)?(d2%1461):(d2-quotient(y4*1460.96,1));
var y1=((y100>0)&&(y4==0))?quotient(d3,365):
  quotient(d3/365.25,1);
var d4=((y100>0)&&(y4==0))?d3%365:
  (d3-1461+quotient(365.25*(4-y1),1));
var y=y400*400+y100*100+y4*4+y1;
var leap=((y%100)==0)?((y%400)==0):((y%4)==0);
var e=leap?mon1:mon0;
var mon=0;
for(i=0;e[i]<=d4;i++){mon=mon+1;}
var d=d4-e[mon-1]+1;
document.write(y.toString(10)+" "+mon.toString(10)+" "
  +d.toString(10));
document.write("<br>");}

これでテストしてみるとうまく行く. 結果は関数の中でdocument.writeする.

ところでy,m,d,nの入力はどうするか. 読み込んだものは文字列らしい. 解説書で使えそうなものを探すと, データ間はカンマで区切り, カンマの位置をindexOfで探し, substringを取れば, それぞれのデータが得られることが分った. またそれだけだと文字列であるが, 0を引くと整数になるという奥の手も知った. こうして書いたのが下の<body>部分である.
</script>
</head>
<body>
<script language="JavaScript">
document.write("<br>");
var s,i,y,m,d,n;
s = prompt("Type in your birthyear,birthmonth,birthday and n
  as `2000,1,1,1000' without space", "");
i = s.indexOf(',');
y = s.substring(0,i);
s = s.substring(i+1,s.length);
i = s.indexOf(',');
m = s.substring(0,i);
s = s.substring(i+1,s.length);
i = s.indexOf(',');
d = s.substring(0,i);
n = s.substring(i+1,s.length);
document.write("y = " + y);document.write("<br>");
document.write("m = " + m);document.write("<br>");
document.write("d = " + d);document.write("<br>");
document.write("n = " + n);document.write("<br>");
y=y-0;
m=m-0;
d=d-0;
n=n-0;
var r=rd(y,m,d);
document.write("Your "+ n.toString(10) +"th day is ");
gr(r+n);document.write("<br>");
</script>
</body>
</html>

さて上のプログラムを呼出すと下のような画面が現れる.





例えばAlan Turingは1912年6月23日生まれ. その滿1万5千日目をみるには

1912,6,23,15000

と入力すると
y = 1912
m = 6
d = 23
n = 15000
Your 15000th day is 1953 7 18

と得られる. Turingは1954年6月7日に他界したから, 1万5千日とすこししか生きていなかったわけである.






2012年11月5日月曜日

Piの1000桁

IEEE Annals of the History of Computing July-September 2012に, 1949年のLabor Dayの休暇にENIACで円周率を2035計算したという記事があった.

円周率(Ludolph's number)といえばWilliam Shanksが1873年に707桁計算し, 自分の墓に刻んたという話が有名であったが, その値が528桁目から下は違っていて, それを見付けたのはENIACの計算によるのかと思っていたが, ウェブで調べると見つけたのは同時代だが, 電動計算機だったらしい.

今から半世紀前, 日本でもあちこちの大学や研究所で計算機が作られはじめ, デモ用に自然対数の底e(Napier's number)を1000桁計算するプログラムが作られた. 私たちもパラメトロン計算機PC-1でプログラムを作ったのは 2012年5月9日のこのブログに書いた通りだ.

ENIACの記事によれば, Machinの式で計算しそうだ. つまり

π/4 = 4 tan-1(1/5)- tan-1(1/239)

tan-1(1/5)=1/5-1/(3·53)+1/(5·55)-1/(7·57)+...

tan-1(1/239)=1/239-1/(3·2393)+1/(5·2395)-1/(7·2397)+...

見て分るようにこの式はeの式

e=1+1/1!+1/2!+1/3!+...

より面倒なので, πに手を出す人はあまりいなかった. しかしtan-1は交代級数なので, 計算は楽な筈である. 1000桁の計算ならtan-1(1/5)は1/(2n+1·52n+1)が1/101000より小さくなる辺りまで計算すればよい.

手始めに100桁でやってみる.
(define c (expt 10 100))
(do ((n 1 (+ n 2))) ((< (/ c n (expt 5 n)) 1) n)) => 141
(do ((n 1 (+ n 2))) ((< (/ c n (expt 239 n)) 1) n)) => 43
これで必要な項数は判ったので, 105桁のbignumで計算する. aは tan-1(1/5), bはtan-1(1/239)で, 最後にaの16倍からbの4倍を引き, 最後の5桁を捨てる. opはループで毎回0と1に切り替わりそれに従って次の項を足したり引いたりする.
(define c (expt 10 105))
(define op 0) (define a 0)
 (do ((n 1 (+ n 2))) ((= n 143))
  (set! a ((if (= op 0) + -) a
   (quotient (quotient c n) (expt 5 n))))
  (set! op (- 1 op)))
(define op 0) (define b 0)
(do ((n 1 (+ n 2))) ((= n 45))
 (set! b ((if (= op 0) + -) b
  (quotient (quotient c n) (expt 239 n))))
 (set! op (- 1 op)))
(quotient (- (* a 16) (* b 4)) 100000)
で結果は
314159265358979323846264338327950288419716939937510
 58209749445923078164062862089986280348253421170679
πの100桁, 1000桁の値はすぐに見付かる. 私は城, 牧之内「計算機械」にあったのを知っているので書棚から取り出し同じであることを確認した.

1000桁も同様に計算したが, 書くまでもあるまい.

2012年10月14日日曜日

多面体描画道楽

このタイトルのブログを最後にアップロードしたのは今年の9月9日で, 大二十面体の6枚の面に色づけした.

その頃から試みたかったのは, ある方向からの光線で陰影をつけることであった.

その前に見える面のすべてに色づけした図を描いてみるとこのようになった. 色の選び方には自信がないが.



これが出来たので, いよいよ陰影の図に取りかかる. 光の来る方向と各面の法線とのスカラー積を計算し, +1は光線に向いているから白に, -1は黒にすればよいのではと考えて次のような図が出来た.



しかし灰色が似ていてやはり面の識別は難しいというのが感想である. 枠となる正二十面体の頂点0,2,3のところだけ取り出したのがこの図である. 緑の正五角形の中が凹み, そこに5/2角錐が立っているのが見てとれればよいが, これもなかなか難しい.



何回か前のブログに書いたように, やはり難解な星形多面体である.

2012年10月8日月曜日

EDSACのプログラム技法

私が2000年ころ書いたEDSAC用Eratosthenesの篩の説明 少し長いが興味と元気があればフォローして欲しい.

まずプログラムの先頭はこうなっている.



これはすべて定数か作業場所である.

以下のが本体.



本体のプログラムは先頭の T 96 K G K で以下のプログラムを96番地から格納する. 最後のE 96 K P F で96番地から実行を開始する. 以下相対番地を前回のように ' で示す.

0'の T F でアキュムレータをクリア. S 850 Dで850,851の P D, P F つまり長語の1を引き35ビットオール1を作る. T 992 Dで 992,993番地へ入れる. 2'番地をアキュムレータに置き, A 849 Fでアドレス部に2を足して2'へ戻す. 848の T 1024 D を引き, 負なら0'へ. このループで長語の992から1022まで16長語をオール1にして篩を用意する.

8'からのループは A 1022 Dでオール1にし, 850 D の1を引き, 111...10(負のストロブ)を作って長語852に入れる. アキュムレータはクリアされていてそれに 850 D の1を足し, 000...01 (正のストロブ)を作って長語922に入れる. これを左1ビットシフトし, 000...10 にして 850 D へ戻す.

16'から22'までは11'と13'の格納命令を2ずつ増やし, 結局長語852-920には負のストロブ, 922-990には正のストロブを用意する.

23', 24', 25'でFIGS, CR, LFを出力. 26', 27', 28'で2を作り, 29'で0番地へ格納して30', 31'のWheelerリンケージで出力ルーチン P6 へサブルーチンジャンプして最初の素数2を印字する. 33'からがいよいよ篩だ.

以下の表で左端の852の列は長語の番地 次の0の列はストロブや篩の長語の番号 その右の二進表示が長語の内容である.
 852  0 11...110 負ストロブ
 854  1 11...101
 856  2 11...011
...
 920 34 01...111

 922  0 00...001 正ストロブ
 924  1 00...010
 926  2 00...100
...
 990 34 10...000

            9753←対応する奇数の素数
 992  0 11...111 篩
 994  1 11...111
 996  2 11...111
...
1022 15 11...111 
0番の篩の長語の各ビットはその上に書いてあるように右から3,5,7,9,...に対応する.従って最後の15番の篩の左端のビットは1121に対応する. EDSACのメモリー容量からすれば篩はもっと大きく出来るが, 篩の様子を水銀タンクで眺められるように, ちょうど1つのタンクの大きさにした.

篩は正のストロブを順に使い, 篩の右から順のビットとandをとって, 素数か合成数かをみる. 合成数なら次のストロブで次の篩のビットを調べに進む. 素数ならその素数をp とすると, 左の図のようにpビットごとにpの倍数があるから, 負のストロブを使っていま調べたビットからpビットおきに篩のビットを0に変えていく. 手順としてはこれだけだが, ストロブが下まで来たときに, 篩を次の長語にする; ストロブを先頭に戻す作業が必要である.

33'で正のストロブを乗数レジスタに置き34'で篩とandをとる. 844Dの長語の1を引き負なら合成数だったので75'へ. そうでないなら37'から素数出力; 841に素数の4倍があるので42'で右2ビットシフトし, 44',45'でP6へいく.

46'からは素数の倍数の篩を0にする, つまり篩う仕事を開始.

以下抜き書きしたプログラムは左から命令のある相対番地, 命令, 番地の示す場所の内容, つまりオペランド, 演算結果のアキュムレータ, 格納命令の場合は格納番地と内容を示す.

      命令     オペランド  アキュムレータ
 47   A  34 @   C 992 D      C 992 D 
 48   U  71 @                              71 C 992 D
 49   S 843 F   L     F      T 992 D
 50   T  72 @                              72 T 992 D
 51   A  33 @   H 922 D      H 922 D
 52   S 842 F   P  70 F      H 852 D
 53   A 841 F   P   6 F      H 858 D       
 54   U  70 @                              70 H 998 D
 55   S 840 F   H 992 D      P   6 F 
 56   G  69 @
47'で今素数を調べた篩を取り出し71'へ入れ, ファンクション部をTにした命令を72'へ入れる. 51'は素数を調べた正ストロブを乗数レジスタに入れる命令で, 篩を0にするには対応する負ストロブが必要である. それは7番地若い方にあるからまず70を引き, 素数のビット数だけ先から篩うので841番地にある素数の4倍の値を足し, 消すストロブの 番地を作る. 55'は負ストロブの範囲を超えたかのチェック.

範囲を超えていなければ69'へ来る. 以下のプログラムの70',71',72'は先ほどのプログラムが設定したものだ.
 69   T     F
 70   H 998 D
 71   C 992 D
 72   T 992 D
 73   A  70 @   H 998 D      H 998 D
 74   G  53 @
 53   A 841 F   P   6 F      H1004 D
 54   U  70 @                        70 H1004 D
73'からは篩をクリアする命令を更新する. H 998 Dをもって53'へ. そこで再び素数の数だけストロボのアドレスを増やす. こうしている内に負ストロブの範囲を飛び出し57'へ進む. 58'で篩の番地を取り出し, アドレスを2増やし71'へ戻す. 72'のT命令も増やす. しかし篩の方が範囲を超える心配があるので, 63'でT1024 Dを引き, 篩の範囲が終わっていなければストロブの番地をもとへ戻す. それが66'からでストロブを取り出し, 番地から70を引いて1周戻し, 54'へいって次のサイクルに入る

篩の最後まで来たら, 合成数の時と同じく75'へ行く.

75'からは次の奇数の素数テストになる. 76'からは素数の4倍をもっていたカウンタ841を4増やす. 79'からは正ストロプを乗数レジスタに置く33'の命令を2増やし, 82'で正ストロブの範囲をチェックし, 範囲内なら32'へ行って左隣のビットをテストする. 正ストロブが範囲を超えているなら, 84'で正ストロブを先頭に戻し, 86'から篩の長語を次にすすめ, 89'で篩も範囲を超えたなら91'でプログラムを終止する.

初期の機械語命令の時代はこういうプログラムは普通であった.

このプログラムが出来た時, EDSACのシミュレータを公開している英国Warick大学のMartin Campbell-Kelly君に送ったらシミュレータのホームページにWadaSieveとして公開してくれた.

彼は"The program is beautifully written and very fast. A little master work."とコメントしている.

2012年9月28日金曜日

EDSACのプログラム技法

EDSACのプログラムの読み書きに重要なのが文字コードである. コードの知識なしでは仕事にならない. それ以前の計算機での入力法はよくわからないが, EDSACではプログラムを紙テープにパンチし, それをイニシアルオーダーで読み込むことが例の本で公開されたので, イニシアルオーダーを解読するためにも文字コードに関心をもつことになった.

EDSACは大学で作った計算機なので, 入出力は市販の機器を用いることになる. 当時はもちろんテレタイプは存在していたから, 当然それらを使うわけだ.

その頃 欧米で使われていたテレタイプは, 英大文字と数字と若干の記号のもので, 5単位テープが標準であった. このコードはInternational Telegraphic Alphabet No.2 (ITA2)という. (規格はここのFreely available itemsのEnglishから得られる) この種のテレタイプは昔は国際電電にいくと見られたがとおの昔に姿を消した.

テレタイプとしてはこのような形であった.



なかなか可愛らしくて1台ほしい. 数年前にアメリカのある空港で耳の不自由な人のための似たようなキーボードを見たことがあった.

コード表は次のとおり.



Figure shift(FIGS)を打つとその後は右の数字と記号を送受信することになる. Letter shift(LTRS)で英大文字になる.

このコードの特徴は, 使用頻度の高い文字は1が, つまりテープの孔が少ないことだ. Wikipediaでletter frequencyを見て, 頻度の順に孔の数をならべると
e t a i n o s r l d h c u m f p y g w v b k x j q z
1 1 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 4 3 4 4 3 4 2
孔が少ないとテープが丈夫なのか, ごみを減らそうとしたのか知らないが, Morseコードと同様な精神で出来ている. ITA2の特殊記号も欧文Morseコードと同じである.

さてEDSACではこういう機器を使おうとしたが, 数字のコードが
P 0 01101
Q 1 11101
W 2 11001
E 3 10000
R 4 01010
T 5 00001
Y 6 10101
U 7 11100
I 8 01100
O 9 00011
と数値に関係がない. そこで最上段の文字のコードを00000から01001に変更した. ただそうするとP 0のコードはまったく孔が開かず, ブランクテープと同じになってしまうので, テープでは一番左の位置はコードが0の時に孔が開くようにした.

変更は最小にしたらしいが, T→F→Y→T, O→D→W→O, R→X→U→S→R, I→A→I, P→B→E→P, Z→Q→Zと交換した. ITA2と同じなのはH, N, M, L, G, C, V, J, Kである.

これがEDSACの文字コードだ.



左の方がPerforator, テープ鑽孔機で, そのすぐ右がTeleprinter, 出力用タイプライターである. 図形文字でない機能鍵のFigure shiftもプログラムでは文字として使いたいというので, 鑽孔機にはπの文字がついているという具合である.

EDSACの最初の本のコードでは, 鑽孔機の記号の位置はタイプライターのそれとずいぶん違っていた. その辺は上の表では省略した.

EDSACのプログラムでは命令を A 3 F のように書くが, 数字の前にFigure shiftを打つかというとそうではない. 命令の最初は文字と思い, そのあとPQWERTYUIOJが続くあいだは数字として扱う. Jは10として使え, そう使うプログラムを存在する.

一方数値だけの疑命令でも, 先頭にはPを書かなければならない. 命令はコード表のFからVまでの文字で終わる. π, S, Z, Kには別の機能があった.

EDSACはこのようにコードを変更したが, 我々のパラメトロン計算機では, コードは市販の機器のままで, あとはプログラムで挑戦するという方法をとった. 日本国内では6単位が標準だったので文字数も多く, プログラムが見やすいシステムが作れた. その話はまたいつか.

2012年9月22日土曜日

EDSACのプログラム技法

英国ケンブリッジ大学のEDSAC(Electronic Delay Storage Automatic Calculator)が稼働し始めたのは1949年5月6日であった.

EDSACのメンバーはプログラムの解説書(Maurice V. Wilkes, David J. Wheeler, Stanley Gill: The Preparation of Programs for an Electronic Digital Computer)を早々に出版した. 世界で最初のプログラムの本であり, 多くの人がそれでプログラミングの楽しさを知った. 私もその1人である.

遠い昔のテクニックはどんどん忘れ去られる. 最近EDSACのプログラムを眺める機会があったので, その頃のプログラムを解説したくなった.

当時のメモリーは水銀遅延線かブラウン管であった. EDSACはそのDelay Storageから分かるように, 水銀遅延線の記憶装置を持つ. つまり水銀タンクの一方からPiezo効果を使い音波を送り, 反対側で受けた音波を逆Piezo効果で電 気信号にもどす. その遅れを記憶装置として利用する. (水銀遅延線の写真はここに)

EDSACのアーキテクチャは簡単である. レジスタとしては71ビットのアキュムレータと35ビットの乗算レジスタ. 記憶装置は17ビットの短語が1024語. 2n番地と2n+1番地の短語を2語つなぐと35ビットの長語になる. 不思議な計算だがその秘密は, 水銀遅延線での短語は18ビット分あり, 語と語の切替えに1ビットの時間がかかるので, 長語は36-1ビット, 短語は18-1ビット, アキュムレータは72-1ビットということだ.



命令語は左端の5ビットが英文字1字で表すファンクション部, 次の11ビットがアドレス部, 最後の1ビットがL/S(long or short)部で, 0だとアドレス部の短語, 1だと長語を示す.

数値は2の補数表示で, -1≤x<1の範囲の値を持つ. 左端のビットが1なら負数である. 具体的には左端の方だけを示すと, 0.012は0.25, 0.12は0.5, 0.112は0.75, 1.002は-1, 1.12は-0.5, 1.012は-0.75である.

EDSACの命令は A n F のように書く. 先頭の英字がファンクションで, つづいて十進でアドレスを書き(0なら何も書かない), 最後のコードレターという英字を書く. コードレターはアドレスの終りを示す他, FはL/Sビットが0, Dは1を示す.

A n はn番地の内容をアキュムレータに足す; S n は引く. T n はアキュムレータをn番地に格納してアキュムレータをクリア. U n は格納するがクリアせず. E n Fはアキュムレータが正ならn 番地へジャンプ, G n F は負ならジャンプで, 無条件ジャンプはなかった.

乗算は乗数を H 命令で乗算レジスタに置き, V n はn番地と乗算レジスタを掛けて積をアキュムレータに足す; N n は積を引く.

EDSACに除算命令はない.

基本的な説明は以上で終る. サブルーチンジャンプの説明も要ろう. n番地からm番地にあるサブルーチンを呼ぶには, 引数を指定された場所に入れ(T 命令で入れるからアキュムレータはクリアされている)n番地に A n F の命令を置く. n+1番地に E m F を置く. 英文字Aは左端のビットが1なので, アキュムレータは負, 従って E 命令でm番地へジャンプする.

m番地のサブルーチンに来たときは, アキュムレータに A n F があるから, これに U 2 F を足す. EDSACの文字コードでは A+U=E だから和は E n+2 F の命令になり, これをサブルーチンの最後に格納する. サブルーチンからはこの命令を実行してn+2番地へ戻る. これをWheelerリンケージという.

次は二進法の小数を十進に変換して出力する方法.

0.00012は十進では0.0625である. これを10倍する. 二進の方は0.10102, 十進の方は0.625 この時の整数部が元の小数の1桁目だが, 今は0だから0を出力.

また10倍する. 二進は110.01002, 十進は6.25. 整数部は6だから6を出力. 整数部を捨ててまた10倍する. 二進は10.12, 十進は2.5. そこで2を出力. さらに10倍して5を出力. という次第で0625が出来る.

EDSACのプリントルーチンP6は次のように書いてある.



左の0から31は相対行番号である. 欄外の24→9のようなのは, 24番地から9番地へジャンプして來るの意味. 25行目の(E F)のかっこは, この命令は変更されること. その下の横線は, 無条件ジャンプを示す. 29番地から31番地の左の2本の縦線は, 偽命令, つまり命令の形はしているが実行されず, 定数であることを示す.

最初の G K はKで終っているから, 制御指令で, 次の命令が格納される番地を相対番地の原点に設定する. つまり命令がθ(短語)やπ(長語)で終っている時は, A 3 F 命令の場所を0 とする. 以下では相対番地を'で示す.

0',1'番地はWheelerリンケージである. 帰り命令は25'番地に格納される. 2'番地は29'番地の偽命令を乗算レジスタに置く. Jは01010, 995は二進では11111000112で, 最後がFつまり0だから, 命令の形としては
01010 01111100011 0
であり, 数値的には
(/ #b01010011111000110 65536.0) => .655364990234375
だから, 216/105を上に丸めたものである.

0番地には短語の範囲に5桁の整数があり, それを5桁で出力するのだが, 最大の99999と最小の1とを見ると,
1 ]=> (* (/ 99999 65536) .655364990234375)

;Value: .9999976144172251

1 ]=> (* (/ 1 65536) .655364990234375)

;Value: 1.00000761449337e-5
だから, 純小数にして5桁出力する方針である.

4'番地の T 4 D でこうして出来た長語を4,5番地に置く. 5',6'番地は3'番地の命令V F を0番地に入れる. Vは11111なので, コメントのように-1/16を置くことになる. 0番地の負は, 出力の先頭の0をスペースにする目印である. 7'番地は乗算レジスタに10/16を置き, 10倍の準備をする. 8'番地は6'番地の命令を0から引き, Tが00101, 5なので1番地のカウンタを-5/16にする. 10'番地は4,5の長語に10/16を掛ける, すなわち10倍して小数点を4ビット右へ. つまり整数部が先頭の5ビットに収まる.

11'番地ではアキュムレータを残したまま積を戻し, 12'番地の A F で6'番地で入れた0番地の-1/16を足す, つまり整数部が0だったら-1/16になり, 26'番地へ進む. 26'番地で先程引いた1/16を足し, O 31 θ で空白を出力, 20'へ戻る.

整数部が0でなければ, 13'番地のジャンプは起きず, 14'番地の T F でアキュムレータをクリアし, 15'番地の T F で0番地を0にする.

16'番地の O 5 F は, 4番地の長語の先頭の5ビットが数字のコードなので, それを出力する.

17'番地で4番地の長語を取り出し, F 4 F では O 5 F で出力レジスタにいれた数字のコードを4番地の先頭の5ビットに取り出す. 残りのビットは0. それを19'番地で引き, 整数部をなくしてから, L 4 F で左へ4ビットシフト, 4番地へ戻す.

EDSACのシフトは難しい. 命令の下位から見て最初の1のビットが現れるまでシフトする. だから L D は1ビット, L 1 F は2ビット, L 2 F は3ビット, L 4 F は4ビットシフトになる. それより上に1のビットがあっても影響しない.

22'番地からは5桁出力したかのチェック. 最初に入れた1番地の-5/16から3'番地の-1/16を引く. 初めの4回は負なので9'番地へ戻る. 5回済むと25'から主ルーチンに戻る.

思わず長い説明になったが, EDSACの頃のプログラムはこういう感じであった.

このプログラムの問題点は0を出力すると, 5個の空白になることだ. 当時はメモリーが少く, プログラムを短かくするのが重要であった.

EDSACについては, シミュレータのTutorial Guideを参照されたし. その23ページにP6の記述がある.

2012年9月9日日曜日

多面体描画道楽

このタイトルの前回のブログ(2012年7月21日), 大二十面体の絵はもっともらしいと思ったが, 実は違っていた. 最後の方の図の三角形ABCの面は, 芯だと考えた正十二面体の面であって, 星形の面が20あるのはいいのだが, 十二面体の12は余計であった. というより, その図でBはもっと深い位置にあるべきであった.

間違いに気づいたのは, 面を色分けにしようとして, 面の数が多すぎると分かったからだが, 正しいB点の位置の計算に手間取っていた. その計算もやっと計算が終わったら, 次は隠面消去のアルゴリズムに手間取った. 秋風も吹き始めた今頃になって, どうやら正しい図を描くことが出来た.

まず前回の失敗の図だ. 頂点0の正5/2角錐が立つ正5角形は存在しない.



それに対して, 正しい図は次のとおり.



20枚の面のうち, 頂点0に関わって, しかも見える6枚の面に色をつけたのが次の図だ.



先ほどの正5/2角錐は, 底面が平面ではなくなったが, 一応角錐ということにすると, その底は逆に窪んだ5角錐になり, 見ている範囲でも3色別々の3枚の平面で出来ていることが分かる.

結構面倒な図形であった.

2012年8月29日水曜日

トポロジカルソート

TAOCPの第1巻にトポロジカルソートのアルゴリズムがある(Algorithm 2.2.3-T). リストの使い方の例である.

例に使う順序には, TAOCPのをそのまま使わせてもらおう. ただ, あの本でのデータは1から始まり, 0は別の機能を持っているが, 私は0から始めるのが好きなので, そういうふうに変更した.

8<1, 2<6, 6<4, 4<7, 7<5, 3<5, 0<2, 6<3, 8<4, 1<7のようなデータがあるとする. 8は1に先行し, 2は6に先行し,...と読む. この関係をグラフにすると, 上の図の上のようになる.

トポロジカルソートはそれを上の図の下のように, 前後関係を保ったまま, 1列に並べるものである.

Algorithm 2.2.3-Tでは, まず要素の個数(上の例では0から8の9個)を受け取り, 下の図のAのようなデータ構造を用意する. 左端の番号は元のデータに出てくる数である.

countはある要素に先行する要素の数を保持する. topはこの要素に後続する要素のリストを保持する. countは最初は0, topは最初はnilである. 8<1を読み込むと, 要素1には先行者8があったので, 1のcountを1増やし, 1を要素8のtopのリストにつなげる. したがってデータ構造はBのようになる.

2<6を読み込むとCになる. 関係をすべて読んだのがDだ.

8<1と8<4を読んだわけだが, 4が1の前にあるのはリスト処理が楽だからで, topのリストの中での順は重要ではない.

count=0の要素0と8は先頭になる資格がある. 従ってソートしたリストに書き出すことが出来る. そしてそれらがソートリストに並んだら, そのtopリストの各要素は邪魔者が1つなくなったので, countを1減らす. その結果0になったら, ソートリストにならぶ権利が得られる. これがトポロジカルソートの基本だ.

TAOCPのアルゴリズムでは, 入力が終わるとcountをしらべ, 権利をもつもののキュー(FIFO)を構成する. count=0になったもののcountの欄を流用する. 以下の図では, キューの関係は赤字で示す. キューを作るには, 先頭(Front)から末尾(Rear)へ至るリストを使う. キューの最後にはnilを入れておく. キューに要素を挿入するのは末尾の側で, 削除するのは先頭の側である. その場所を覚えるためにの変数fとrを用意し, キューが空の時は, f=r=nilとする(E).

要素0のcountが0なので, Fに示すように, 1要素だけのキューが出来, fもrも0を指し, 0のcountがnilになる(F).

さらにcountが0のものを探すと, 要素8が見つかり, Gのようになる. つまりfは0を指し, 0のcountは8になり, 8のcountはnil, rは8となる.

0の探索が終了すると, トポロジカルソートの出力が始まる. その前に変数Nを要素数nにしておき, 出力のたびにNを1減らし, 最後にすべて出力したかをN=0で確認する. まずfの値0を出力, N=N-1とする. 0のtopからリストを辿り, それらに先行していたものがなくなった処理をする. つまり要素2のcountを1減らす. その結果countが0になったら, この要素も先行するものがなくなり, 出力できるようになったわけで, この要素をcountのキューにつなぐ(H). 0のcountは8のままだが, これはゴミである. Algorithm 2.2.3-Tでは, topのリストはそのままにしているが, 演習問題2.2.3-23にあるように, トポロジカルソートが出来なかったときの原因探索用に, 済んだリストはnilにしておくのがよい.

fにfのcountを代入してfを進め, 次の要素の出力に取りかかる. 図Iは要素8も出力したところ, Jは2も出力したところである. キューの先頭をfで辿り出力を続ける. fがnilになったら終わりで, Nは0になったはずである. 0にならなければ, 元のデータがおかしかった(ループがあった)ので, topのリストを出力してみると, 原因がわかる.
,br> 私がSchemeで書いたプログラムは以下のようだ. 変数Nの代わりにnnにしてある.
(define (algorithm223t n ls)
 (let* ((count (make-vector n 0)) (top (make-vector n '()))
        (r '()) (f '()) (nn n))
  (define (getrel ls)   ;データを読み込む 
   (if (not (null? ls))
    (let ((j (caar ls)) (k (cadar ls)))
     (vector-set! count k (+ (vector-ref count k) 1))
     (vector-set! top j (cons k (vector-ref top j)))
     (getrel (cdr ls)))))
  (define (outloop f)   ;出力のループ
   (define (eraserel p) ;topのリストを解消する
    (if (not (null? p))
     (let ((c (- (vector-ref count (car p)) 1)))
      (if (> c 0) (vector-set! count (car p) c)
       (begin (vector-set! count r (car p)) (set! r (car p))
              (vector-set! count r '())))
      (eraserel (cdr p)))
     (begin (vector-set! top f '())
      (outloop (vector-ref count f)))))
   (if (not (null? f))
    (begin (display f) (display " ") (set! nn (- nn 1))
     (eraserel (vector-ref top f)))))
  (getrel ls)
  (do ((k 0 (+ k 1))) ((= k n)) ;count=0のキューを作る
   (if (= (vector-ref count k) 0)
    (begin (if (null? r) (set! f k) (vector-set! count r k))
     (set! r k) (vector-set! count r '()))))
  (outloop f)   ;出力開始
  (if (= nn 0) 'ok (list 'nn nn))))

(algorithm223t 9
'((8 1) (2 6) (6 4) (4 7) (7 5) (3 5) (0 2) (6 3) (8 4) (1 7)))
このプログラムを走らせると 0 8 2 1 6 3 4 7 5 が出力される.

このプログラムに出力命令を追加し, データ入力が終った時のcountとtopの値は #(8 1 1 1 2 2 1 2 ())#((2) (7) (6) (5) (7) () (3 4) (5) (4 1))である.

またデータの最後に(3 0)を追加すると, ループが出来, トポロジカルソートが出来ない. それをやってみると8 1でソートの出力は終り, 残ったtopとして#((2) () (6) (0 5) (7) () (3 4) (5) ())が得られる. これからだとループの発見は面倒だが, 演習問題23のように拡張すると, どこが悪いかが判明する

2012年7月22日日曜日

Life Game

広い空間と長い時間を使えば, Life Gameで計算機がシミュレート出来るという話は聞くが, そう簡単には納得できない.

Winning Waysを読んでいたら, 記憶装置の実現法が書いてあった. こういう方式らしい.

A, B, C, などが記憶場所で, その上方にブロックを置き, そのブロックまでの距離を記憶している値とする. 従って書き込むにはその数値の分だけブロックを遠ざける手続きを繰り返す. また読み出すにはブロックを近づけ, 0になるまでの回数で知る.

近づけたり塔ざけたりには, いつものようにグライダーを使う. 上の図の縦方向は, 実はグライダーの飛ぶ斜め方向である.

ブロックを近づける方法はこうだ.



左上からA, B2機のグライダーがブロックに近づく. この図をクロック0としよう. 目的はブロックを左上へ動かすことである.

その後, クロック10になると, ブロックに衝突したAがブロックを少し移動して下の図になる.



ブロックの元の位置は赤い枠で示したとおりで, 左へ2, 上へ1移動した. 左と上の距離が違うので, もう一度Bをブロックに衝突させる. その結果が次の図だ. (クロック18)



ブロックの位置は, 上へも左へも3移動した.

一方, ブロックを遠ざけるのは簡単ではない.



上の図(クロック0)では, グライダーは左下から右上へ向かっている. 従ってブロックを右上に動かしたい.

グライダーは10機描いてあるが, 2つの編隊になっている. 第1編隊はA, B, C, D, Eで, 最初にブロックに近づくAのミッションは, ブロックに衝突し, 蜂の巣(beehive)4個からなる養蜂場(bee farm)の形にすることだ.

下がクロック24で, Aの衝突の結果, 養蜂場になったところである.



そこへ近づくB, C, Dの3機のミッションは, それぞれ下, 右, 左の蜂の巣と消滅衝突をすることである. 最後のEが近づき, 上に残った蜂の巣に衝突し, ブロックにする.

クロック77になると, 衝突が治まり, ブロックになるが, 赤い枠の元の位置からは左へ1, 上へ2移動した.



すぐ近くに第2編隊のF, G, H, I, Jが接近している. これらは前の編隊とは左下から右上に掛けての45度の線について対称である. Fが養蜂場を作り, G, H, Iが余分の蜂の巣を片付け, 最後にJがブロックに戻す. (164クロック)



これでブロックは右上に(1,1)だけ移動した. しかし先に書いた近づける方は(3,3)だけ移動したので, 遠ざける方は同じことをあと2回やらなければならない. つまり30機をもって押し返すという筋書きである.

ところで, 遠ざける方の最初の図だが, Winning Ways第4巻953ページにある図は, シミュレートしてみると, うまく行かないのである. いろいろテストしてみて, CとEを元の図より1ピクセル下へ, HとJは1ピクセル左へ移動してある. これならうまくいくことがシミュレーションで確認してある.

2012年7月21日土曜日

多面体描画道楽

星形正多面体描画はまだ「大二十面体」が残っていた. (「大二十面体」からは子供の頃読んだ「怪人二十面相」を連想してしまう. でも内容はもう覚えていない.)

一松先生に記述はこうだ「正二十面体の各面に対し, 各辺に隣る三角形の頂点3個を結ぶ正三角形20個を面にとります.」 相変わらず簡潔だ. 面は20, 辺は3×20÷2=30, 頂点は3×20÷5=12. 後に判明するように, 頂点で面は星形(5/2角形)で交わるから, Schläfliの記号は{3,5/2}. いやはや記述とは反対に大変な難物であった.

前回のブログにあった枠になる正二十面体の図をもう一度示すと. つまり頂点0,1,2の面に対しては, それに隣る三角形の頂点5,10,3の三角形で, こういう面だけで出来る立体はそう容易には想像出来ない. 数学者の想像力にはいつもながら脱帽だ. かなりの日時をかけ, 苦心惨憺, 紆余曲折, といいつつも十分楽しんだ末, 出来た図はこれだ. 自画自賛すれば実にもっともらしい. たしかに頂点5,10,3を結ぶ三角形は見える. 下は左が正二十面体の面, 右がそれに対応する大きい三角形である.
[2 1 0]   [10 5 3]
[3 2 0]   [11 1 4]
[4 3 0]   [7 2 5]
[5 4 0]   [8 3 1]
[1 5 0]   [9 4 2]
[2 10 1]  [11 9 0]
[11 10 2] [6 1 3]
[3 11 2]  [7 10 0]
[7 11 3]  [6 2 4]
[4 7 3]   [8 11 0]
[8 7 4]   [6 3 5]
[5 8 4]   [9 7 0]
[9 8 5]   [6 4 1]
[1 9 5]   [10 8 0]
[10 9 1]  [6 5 2]
[7 8 6]   [4 9 11]
[8 9 6]   [5 10 7]
[9 10 6]  [1 11 8]
[10 11 6] [2 7 9]
[11 7 6]  [3 8 10]
ためつすがめつ眺めているうちに, これは下の図のオレンジ色の正五角形の上に, 背の高い星形が乗っている; その正五角形が芯になる正十二面体であるらしいと分かって来た. しかし頂点3に関わる部分だけで三角形は15もある. 都合180個の三角形の座標を計算しなければならぬ. 当然その計算の部分はSchemeで別にやることにした.

星形の底面の凸の点は, 例えばAは頂点3と5の線上にあるから, 後で計算出来るとして, 凹の点Bはどうするか. 頂点3からの三角形の辺は, 8,5,1,10,6に向かい, それらの頂点は8,1,6,5,10の順に星形(5/2角形)につながっている.

ABの線は頂点5, 10の線と平行であり, BCの線は頂点8, 1の線と平行なので, その交点の座標を知る必要がある. このような交点を5個計算し, 3と5の位置を内分してAが得られたように, 3と交点を同じ比で内分するとBが得られるわけだ. この比は1:(1+√5)/2なので, 黄金分割であった.

180個のすべての三角形の頂点の座標が得られてから, それらの各点の視線方向の距離を計算し, 遠い物から順に描くと最初の図が出来た.

一応目的は達成したが, こういう水の溜まる立体を白地で示すと, どこが凸でどこが凹かが分かりにくい. 次は面を濃淡に塗り分けてみたい.

2012年7月10日火曜日

多面体描画道楽

星形正多面体の次の対象は, 「大十二面体」である. 前回は「星形大十二面体」であった. 「大十二面体」と「星形大十二面体」があって, 相並んでいるのは, ノーメンクレイチャとしてはいかがかと思うが, 一松先生の本がそうなので仕方がない. 「星形大十二面体」と「なんとか大十二面体」といいたいところだ. その(星形でない)大十二面体の, 「正多面体を解く」の記述はこうだ. 「正二十面体の頂点に隣る5個の頂点のなす正五角形をそのまま面に採る.」 この上なく簡潔至極の表現だ. 上が正二十面体である. その頂点0に隣る5個の頂点のなす正五角形は, 下の図にオレンジ色で示す面1,2,3,4,5になる. これを「0に隣る面」ということにしよう. (その辺には小さく0と付記した.) さらに頂点23に隣る頂点の面を作ると のように2に隣る面0,1,10,11,3と, 3に隣る面0,2,11,7,4が得られる, ところで0に隣る面と2に隣る面は13で交わるからその交線は1から3へ引いた線である. 同様に0に隣る面と3に隣る面は24で交わるからその交線は2から4へ引いた線である. さらに2に隣る面と3に隣る面の交線は0から11へ至る. 従ってもとの正二十面体の面の1つの三角形0,2,3の中に3つの交線が現れる. その交点を下の図のようにPとする. 同様に三角形0,3,4の中に3つの交線が現れる. その交点を下の図のようにQとする. この2点の座標が決まると, 大十二面体を描くことが出来る. それがこれだ. この正多面体はPQの所が凹んでいて, 水が溜まる形である. 右下の頂点3を中心とした昔の陸軍の徽章のような星形がみられる. 3の星の1つの枝は0の星の枝でもあるから, この枝は2等辺三角形である. ところでPQの座標の計算法だが, 正二十面体をx軸方向から見た図を描くと になる. この図で1, 10短い方の辺の長さを2とすると, 図の 0, 6の長さは1+√5. Oから0までは(1+√5)/2. 0, 3も1なので, これらからP, Qのz座標(3+√5)/2, (√5-1)/2が得られる. これらを解くには (a+b√5)×(c+d√5)のような計算が頻発するから, この積がe+f√5であるとして, a,b,c,dからeとfを求めるプログラム
(define (mult a b c d)
 (list (+ (* a c) (* 5 b d)) (+ (* a d) (* b c))))
を用意すると仕事がはかどった.

2012年7月8日日曜日

多面体描画道楽

一松先生の「正多面体を解く」の星形正多面体に最初に登場するのは, 星形大十二面体である. その記述はこうだ. 「正十二面体の各面の頂点に隣る5個の頂点は同一平面上にあって, 大きな正五角形をなします. これを星形正五角形の形に結びますと, 全体として星形正五角形すなわち正5/2角形が, 各頂点に3個ずつ会する星形正多面体ができます.」

「隣り」を動詞に使うのは数学者だけだが, 意味は十分通じる. そこで描いてみると



正5/2角形を1個描いただけだとこうだが, 12個全部を描くと何も分からない.



この図をプリンタで出力し, 鉛筆であちこちなぞっていると, どうも正五角形の中央を凹ませ, 正十二面体の各頂点を頂点とする三角錐があるように思えてきた.

その凹みの座標を調べるため, 前回のようにzx断面を考えると,  0, 1, 2, 3, 4の面の凹みは3から8の線と4から10の線の交点にあるようだ.



この図で凹みの中心はP, そのz座標は(√5-1)/2である.

ここまで分かると, 後は隠面消去のために面の前後関係の調整だけで, 下のような図が出来た.



この星形正多面体の枠と芯は次のようだ. 外側のオレンジ色の正十二面体が枠で, 内側の紫の正二十面体が芯である. その芯の20個の各面の上に正三角錐が乗っている.

2012年7月7日土曜日

多面体描画道楽

オフィスの近くの神保町. ある日の昼に, 理系の本の多い明倫館書店の前を通り,  そういえば先日購入したのは何だっけ?

その1ヶ月ほど前に, 一松信先生の「正多面体を解く」を買ったのだが紙袋にいれたままオフィスに忘れていた.

この書の最後の方に星形正多面体の話題があった. そういえば以前正多面体や準正多面体をいろいろ描いたが, 星形は手つかずであったので早速その部分を読む.  申し訳ないが一松先生の本の図はどうもいまいち分かりにくいのである. やはりこの際, 自分で描いてみるか.



一松先生の受け売りだが, 2次元の星形正多角形(上の図の左下)の書き方には2通りある.  右上のように「芯」の正多角形の各辺を延長すると左下が出来る. あるいは右下の「枠」となる正多角形の頂点をいくつか置きに結ぶ.

星形正多角形は, 普通の正多角形とは違って見えるが, すべての辺の長さが等しく, すべての頂点の角度も等しいから, やはり正多角形である. この図の星形正多角形は正5/2角形というらしい.

立体の手始めは星形小十二面体である. これは正十二面体を芯とし, その各面を延長する.

2010年6月1日の私のブログの



正十二面体を芯とし, そのプログラムの修正て始めよう.

この正十二面体の正面左上を向いている面(頂点が0, 1, 2, 3, 4の面)に隣接する5枚の面を延長すると点20で合わさった五角錐になり



が出来る. この操作を他の11枚の面についても行うと



が描ける.

五角錐の頂点の座標は下の図のように得られる.



最初の正十二面体で立体を貫くの赤, 緑, 青はx軸, y軸, z軸である. 上の図は立体のzx面の断面である. Aは01の, Bは313の, Cは817の中点で, Oは立体の中心だ.

20は辺3, 13の延長線上にあり,  立体の辺(稜)の長さの半分を1とすると, OA=OB=OC=(3+√5)/2からx座標が2+√5である.

さて, 星形小十二面体を眺めると, 面0, 1, 2, 3, 4を延長した面28, 21, 29, 26, 24は正5/2角形である. こういう形が12枚あるわけだ. 芯の正十二面体の12の面に五角錐を載せたから, 頂点も12ある. 辺は各頂点から5本出ているが, 相棒の頂点と共有だから都合30本になる.

例のEulerの公式は 面+頂点=辺+2だが, 12+12≠30+2で公式が成り立たないという. 「正多面体を解く」にはちゃんと説明があるが, 受け売りになるので省略.

さてもう1回似たような図を示す.



このオレンジ色の破線は星形正多面体の枠の正二十面体である. 平面だと, 頂点をいくつか置きに辿るといえるが, 立体の場合はどういえばいいだろうか.

一方, 星形小十二面体の隠面消去だが, 五角錐を構成する三角形を遠方にある物から並べ, 各三角形を描画する際, まず内側を白で塗りつぶしてから黒で輪郭を描いた.

始めの正十二面体では, 各面の法線方向を計算し, それと視線の角度を計算して面の見える/見えぬを決めている.