ピタゴラ三角、完成

あの人が出ていた

inside nprolog

(gdb) p (sizeof(cell))
$1 = 36
(gdb) set print pretty
(gdb) p heap[256]
$7 = {
  val = {
    fltnum = 1.8780421699300832e-315,
    lngnum = 380119967,
    {
      car = {
        intnum = 380119967,
        subr = 0x16a82b9f <b_nano>,
        port = 0x16a82b9f <b_nano>,
        dyna_vec = 0x16a82b9f <b_nano>
      },
      cdr = {
        intnum = 0
      }
    }
  },
  aux = 7,
  var = 0,
  arity = 0,
  tag = 6 '\006',
  flag = FRE,
  name = 0x7963c4c0 "edit",
  option = 0 '\000'
}

ご本尊様のサイズは36Byte(@32Bit)。256番地には、どうやらprologの述語で言うeditが定義されてる。C語の世界ではそれを担当してるのは、 b_nano って関数だ。

vbox$ nm npl | fgrep ' B ' | sort
200034d0 B proof
  :
20003548 B ed_indent
200035c0 B heap
35755fc0 B cell_hash_table
35756180 B variant
37d7bb80 B stack
3814c480 B record_hash_table
 :

何と言ってもheapは巨大だ! データベースだからね。

それじゃ、64Bitのマシンではどうなる? debianで確認。上記のようにnmコマンドを使って、BSSに割り付けられている変数のアドレスを採取する。

0000000002668480 B heap
0000000023c77080 B column

64Bitって、桁数が多く表示されるので、正直嫌い。

sakae@pen:/tmp/nprolog$ irb
irb(main):001:0> (0x23c77080 - 0x2668480) / 10000000
=> 56

後は、rubyとかで、変数のアドレス(16進数に注意)の差を取ってから、配列のエレメント数で割れば良い。32Bitシステムよりゆったり使っている事が分かる。

16進の計算にrubyを使ってしまったければ、オイラーのお勧めはgdbだ。簡単に進数の変換とか、混合演算が出来るので、お勧めである。一家に一台、gdb計算器を、だ。

リベンジのピタゴラ三角

前回失敗した、ピタゴラスの三角形を発見するやつ、再チャレンジする。 写経と言うか手本にするのは、example/fizzbuzz.pl だ。

再帰は、大きな数をだんだんと減算してって、0になったら終わりってパターンしかやってなかったので、fizzbuzzみたいに、0から数え上げて行くってのが新鮮だった。

ある数に達したら終了ってのは、効率悪いんじゃねぇ? いちいtある数をロードしてきて判定しなきゃならん。その点0との比較なら、それなりのマシン語で一発比較出来る。なんとまあ、みみっちい考えだ事。もっとおおらかに行け。

pp(A,B,C) :- A^2 + B^2 =:= C^2,
            write(A), write(', '), write(B), write(', '), write(C), nl.
pp(A,B,C).

pa(A,B,C) :- A =:= B.
pa(A,B,C) :- pp(A,B,C), N is A + 1, pa(N,B,C).

pb(A,B,C) :- B =:= C.
pb(A,B,C) :- pa(A,B,C), N is B + 1, pb(1,N,C).

pc(A,B,C,Max) :- C > Max.
pc(A,B,C,Max) :- pb(1,1,C), N is C + 2, pc(1,1,N,Max).

run(Max) :- pc(1,1,1,Max).

こうして、分解しちゃうのは楽でいいな。paとかのaは、A辺をスキャンするよって意味。だからA辺だけの事を考えるだけで良い。再帰の終了もB辺と等しくなった時って事で、考えるのが不要とも思えるような楽さ。

ユーザーが与える値は、runの引数だ。後は佳きに計らえって事。

ちょいと頭を使った所は、ピタゴラ三角が成立してるか判定してるppって言う述語。成立してたら結果表示。成立してなかったら? 一見無駄にも思える頭部だけの宣言。これを入れておかないと、思うように動かない。どうなるかは、試してミレー。

すみません、嘘書いてました。サッとは行かなかったんです。で、取った方法は、いわゆるビルドアップ方式。最初ppを書いて、それを手動テスト。pp(1,1,1)とかpp(1,2,3)とかpp(3,4,5)で、副作用が発生して、結果を表示するか(あるいは、しないか)確認。

次はpaを書いてpa(1,2,3)とかpa(1,4,5)とか。これがOkならpbを書いて、試してって具合です。いわゆる、急がば回れですね。

ob$ ./npl -c p.pl
N-Prolog Ver 1.42
?- run(5).
3, 4, 5
yes
?- run(20).
3, 4, 5
5, 12, 13
9, 12, 15
8, 15, 17
yes

よっしゃ、次は、

?- run(100).
3, 4, 5
 :
9, 40, 41
27, 36, 45
Segmentation fault (core dumped)

欲張り過ぎて臍まげた? 河岸を変えて、gprologで試してみます。

GNU Prolog 1.4.5 (64 bits)
Compiled Sep 30 2020, 15:49:24 with cc
By Daniel Diaz
Copyright (C) 1999-2020 Daniel Diaz
| ?- ['p.pl'].
compiling /tmp/nprolog/p.pl for byte code...
/tmp/nprolog/p.pl:3: warning: singleton variables [A,B,C] for pp/3
/tmp/nprolog/p.pl:5: warning: singleton variables [C] for pa/3
/tmp/nprolog/p.pl:8: warning: singleton variables [A] for pb/3
/tmp/nprolog/p.pl:11: warning: singleton variables [A,B] for pc/4
/tmp/nprolog/p.pl:12: warning: singleton variables [A,B] for pc/4
/tmp/nprolog/p.pl compiled, 14 lines read - 3020 bytes written, 35 ms

yes

盛大に警告が出て来た。ちゃんと与えられた引数を使ってないけど、大丈夫かあ?って奴だ。 haskellだと厳格だから、ちゃんと対処せにゃならんだろうけど、取り合えず先に進む。

| ?- run(100).
3, 4, 5
5, 12, 13
 :
35, 84, 91
57, 76, 95
65, 72, 97

true ?

(10 ms) yes

何の苦もなく、完走してくれた。見つけ出した3つ組も、C語でのスキャンと一致してたよ。 ロジックの間違いは無いと思っていいだろう。まて、セカンドオピニオンで、最近ご無沙汰してるFreeBSDでのswiplでも確認するか。

Welcome to SWI-Prolog (threaded, 64 bits, version 8.2.0)
  :
?- ['p.pl'].
Warning: /tmp/p.pl:3:
Warning:    Singleton variables: [A,B,C]
Warning: /tmp/p.pl:5:
Warning:    Singleton variables: [C]
Warning: /tmp/p.pl:8:
Warning:    Singleton variables: [A]
Warning: /tmp/p.pl:11:
Warning:    Singleton variables: [A,B]
Warning: /tmp/p.pl:12:
Warning:    Singleton variables: [A,B]
true.

やっぱり警告出て来たね。肝心な結果は、gprologと一致した。よかったよかった。

いや、良くない、検視作業が残ってるだろう。

coredumpの結果を検視する

npl.h

#define VERSION     1.42
#define CELLSIZE    10000000  // small
#define HEAPSIZE     7000000  // small
#define FREESIZE         500
#define STACKSIZE    1000000
#define VARIANTSIZE 10000000  // small

makefile (関係しそうな部分のみ)

CC   = gcc
CFLAGS = $(INCS) -Wall  -g3 -O0 -gdwarf-2

さあ、いよいよ検視の開始。OpenBSD 6.8 は、意外に保守的なgdbを使っています。kernel開発者には、これで十分なのでしょう。ってか、そもそもgdbなんて使わない?

ob$ gdb npl npl.core
GNU gdb 6.3
 :
#0  singlep (addr=Cannot access memory at address 0x7f7fffbecfe4
) at data.c:489
489     int singlep(int addr){

ここに到達するまでの軌跡を表示してみます

(gdb) bt
) at data.c:489
#1  0x00000d65ec2c9c5a in anoymousp (addr=7125) at data.c:710
#2  0x00000d65ec2aa7d1 in walpha_conversion (x=7125) at main.c:873
#3  0x00000d65ec2aaac7 in walpha_conversion (x=26903) at main.c:904
#4  0x00000d65ec2aaab6 in walpha_conversion (x=26904) at main.c:904
#5  0x00000d65ec2aaab6 in walpha_conversion (x=26905) at main.c:904
#6  0x00000d65ec2aaa35 in walpha_conversion (x=26906) at main.c:898
#7  0x00000d65ec2aa909 in walpha_conversion (x=26924) at main.c:885
#8  0x00000d65ec2a9665 in prove (goal=7467550, bindings=59587, rest=7466895,
    n=17019) at main.c:470
#9  0x00000d65ec2a928c in prove_all (goals=7467526, bindings=59587, n=17019)
    at main.c:385
#10 0x00000d65ec2a9469 in prove (goal=7467546, bindings=59586, rest=7467526,
    n=17019) at main.c:429
#11 0x00000d65ec2a928c in prove_all (goals=7467529, bindings=59586, n=17019)
    at main.c:385
     :

永遠と続いているって事は、メモリーを使い果たしたんだろうね。

(gdb) info registers
rax            0x0      0
rbx            0x0      0
rcx            0xd65ee904900    14731445291264
rdx            0x348b8  215224
rsi            0xd65ee904900    14731445291264
rdi            0x1bd5   7125
rbp            0x7f7fffbed020   0x7f7fffbed020
rsp            0x7f7fffbed000   0x7f7fffbed000
r8             0x101010101010101        72340172838076673
r9             0x8080808080808080       -9187201950435737472
r10            0x0      0
r11            0x99c3c9a1f2f3aa71       -7366822868045026703
r12            0x71f221 7467553
r13            0xd6823d3a9c0    14740928833984
r14            0x7f7ffffeba98   140187732458136
r15            0x3      3
rip            0xd65ec2c924a    0xd65ec2c924a <singlep>
eflags         0x10297  66199
cs             0x2b     43
ss             0x23     35
ds             0x23     35
es             0x23     35
fs             0x23     35
gs             0x23     35

こんな物出してどうする?いや、ripにそれらしいのが出てるな。

debian:nprolog$ ./npl
N-Prolog Ver 1.46
?- ['p.pl'].
yes
?- run(100).
  :
9, 40, 41
27, 36, 45
Resource error

なお、debian(32Bit)で、gitしてきたものをそのまま実行したら、ちゃんと止まってくれた。 この違いは何よ?

思うに、gcあたりに何か隠れていそうだな。制限をきつくしてストレス試験ってのは、良い試験と思うぞ。

医学界でも、ストレス試験を行う場合がある。昔、女房が、心臓ドキドキするって主訴で医者にかかった。心電図とか取っても異常なし。で、医者の取った方法は?

携帯用の心拍計をつけて、数日生活してみてくださいってやつ。診察台に横たわっての計測じゃ、良い状態での検査にしかならない。普通の生活中で現象が出現するか確認したい。真っ当な方法だと思うぞ。

後は、半導体とかの加速試験ね。定格を外れる状態で試験して、ウィークポイントを炙り出す。 普通の状態じゃ、中々症状出ないからね。

それにしても、100x99x98回の宝探しで、根を上げるのは、メモリー食べ過ぎ症候群?(と、外野席が、騒いでます)

そんな事より、お前はどうだ。偉そうにしてんじゃねぇ! 反省しろ。

敗軍の将、多くを語る

前回書いた、ピタゴラ三角の探索は失敗作だった。失敗分析をしておく。

C語のループをそのままprologで実現しようとして無理した。一つの述語で、何から何まで実現しようってのは、愚の骨頂。

その無理の主因は、三角を見つけた後、failさせて、次を探させた事。これは100歩譲って良しとしても、バックトラックを招いてしまった。

awk(おおく)じゃ無かったけど、上記に尽きるな。

失敗は成功の基。大いに失敗して、そこから学ぼう。それが強くなる秘訣。

inc/dec

nprologのマニュアルを見ていたら、便利な述語を発見。nprolog独自のインプリメントだから swiplとかとは互換性が無いからその積りで。

?- In=3, inc(In,Out).
In = 3
Out = 4 .
yes

これの何が嬉しいかと言うと

?- listing
 :
pa(A,B,C) :-
    pp(A,B,C),
    inc(A,N),
    pa(N,B,C).
pb(A,B,C) :-
    B=:=C.
pb(A,B,C) :-
    pa(A,B,C),
    N is B+1,
    pb(1,N,C).

pbの所で数値をインクリメントするのに、isを使ってる。はなはだしく違和感があるぞ(オイラーの感想)。それで、paの所ではincを使ってみた(周囲に溶け込んでいて違和感、全く無し!)。

さりげなくlistingなんてのを使ってみた。これ、言うなれば(宣言とルール)データベースのダンプね。lispとかだと、こうはいかないので、なかなか楽しい機能だ。

このincとdecぐらいなら、簡単にswiplに移植出来るよ、単にマクロだろ。本当か?

?- dec(5,Q).   % (1)
Q = 4 .
yes
?- dec(Q,7).   % (2)
Q = 8 .
yes
?- dec(8,7).   % (3)
yes
?- dec(8,6).   % (4)
no
?- dec(X,Y).   % (5)
Instantation error dec  #[X,Y]

お前が想定したのは、用例(1)だろう。(2)は、想像通り何をマイナス1したら、7になりますかだ。(3)は、8をマイナス1したら7、は正しいですか? へいその通り。(4)は、間違った演算はnoって答えてる。そして最後は、そんなの神でも分からないんで、エラーだよと、回答拒否。

これだけの機能をきちんと実装しないと、prologの述語としては失格だ。移植なんて大変だぞ。 やるなら、心してかかれ。 上の例だと、端子が2つなんで、組み合わせは4種だけど、端子数が増えると、大変な事は、保障しますよ。

オイラーは元ハード屋。かの昔のICは、端子数ってか足の数をケチる為、入出力共通って構成が多かった。IOコモンってやつね。prologにもそういう考え方が有ってもよさそう。 述語を呼ぶ時は、引数に入力データをセットして渡す。述語が実行されたら、結果は引数に格納されてる。どう、端子数が減って扱いが楽になると思うんだけど、どうよ。

そんなに、入出力端子を設けるって、何か利点はあるの? prologは論理を扱うのが主体。 yes/no でもいいし、true/false でもいいし、白/黒でもいいんだけど、ようするにどっちかってのが非常に大事。

問い合わせをした結果をprologシステムが容易に知る必要がある。問い合わせの述語の結果を返値として、いの一番に採用したら、述語(ようする関数)の計算結果を返す端子が別に必要。 それで、出力端子を引数に任せてしまった訳だ。

C語とかではどうなっているか、ぷち確認。代表例で、sqrt

       double sqrt(double x);

ERRORS
       Domain error: x less than -0
              errno is set  to  EDOM.   An  invalid  floating-point  exception
              (FE_INVALID) is raised.

平方根を計算したい時は、引数xにその値を入れてから、sqrtを呼び出す。sqrtの返り値は、計算された値になる。ここまでは良い。じゃ、引数がマイナスだったら? 計算不能。それをどうやって伝える? システムが用意したerrorって言うグローバル変数に入れて返す。

だから、使う時は、エラーになってないか確認して、問題なかったら計算結果を採用する。ちょいと面倒な訳だ。

prologは、述語を連鎖させて、結果がyesの場合は、どんどんと次の述語を実行する機構になってる。

head :- func1, func2, ... funcN .

これを、 scheme風に書くと

(define (head)
    (and (func1) (func2) ... (funcN)))

(注: 両者共、引数は省略した)

prologで、関数の後ろに付いている、カンマは、and結合の意味だ。勿論or結合が欲しい場合もあるだろう。その場合はカンマに変わってセミコロンを使う。じゃ、否定は? もう忘れた(御免)。

FreeBSDにも移植してテストする

FreeBSDは入れてるけど、正直余り使っていない。この際試してみるか。余りいじっていないので、gccの準備はしてない。

[sakae@fb /tmp/nprolog]$ cc -v
FreeBSD clang version 10.0.1 (git@github.com:llvm/llvm-project.git llvmorg-10.0.1-0-gef32c611aa2)
Target: x86_64-unknown-freebsd12.2
Thread model: posix
InstalledDir: /usr/bin
[sakae@fb /tmp/nprolog]$ vi builtin.c  main.c

毎回、リナ用の stdio_ext.h を消すの面倒だな。リナ用の囲いを付けてください。 後は、自己責任にntp.hのFLUSHの所を修正。最後にmakefileで、ccにするのと -ldlの削除か。

cc   -Wall -O3 -c main.c -o main.o
main.c:1195:13: error: expected expression
            else if(c == 'b')
            ^
1 error generated.
 *** Error code 1

新種のエラーだな。

1192                #elif __OpenBSD__ || __FreeBSD__
1193                printf("\a");
1194                #endif
1195            else if(c == 'b')

c == 'a'の所の文が満たされないのでエラってた。FreeBSDも仲間に入れてあげたよ。

[sakae@fb /tmp/nprolog]$ ./npl -c p.pl
N-Prolog Ver 1.46
?- run(100).
3, 4, 5
 :
27, 36, 45
Resource error

メモリーを半分に絞っても、CFLAGSを同じに設定しても、coreは吐かなかった。OS固有の問題なのだろうか? 

偶然かも知れないけど、25,36,45の直角三角形を見つけた後、core、あるいはResource errorになってるな。何か匂うぞ、クンクン。

獲物を追って

[sakae@fb /tmp/nprolog]$ gdb npl
  :
(gdb) b main.c:419
Breakpoint 1 at 0x20c914: file main.c, line 419.
(gdb) r -c p.pl
Starting program: /tmp/nprolog/npl -c p.pl
N-Prolog Ver 1.46
?- run(100).
  :
27, 36, 45

Breakpoint 1, prove (goal=7494324, bindings=63024, rest=7494373, n=18001)
    at main.c:419
419             error(RESOURCE_ERR,"",NIL);
(gdb) bt
  :
#1122 0x000000000020cd78 in prove (goal=7484278, bindings=61743, rest=7484055,
    n=17635) at main.c:505
#1123 0x000000000020bd91 in prove_all (goals=7484254, bindings=61743, n=17635)
    at main.c:396
   :

coreとResource Errorは紙一重の違いに思えてきた。スポットで追っても、全体像が見えていないので、意味ないな。後は作者様にバトンタッチ。nは評価の回数?かな。18000回回っても、結果が出ないとSTOPを掛けてるように見受けられるけど。 lispで、evalとapplyを行ったり来たりしながら収束させるけど、それと同じ?


This year's Index

Home