prolog(3)

『杜氏千年の知恵』なんて本を読んでみた。図書館にはこの他に日本酒の事について 書いた本が数冊あったけど、これが一番文字が大きくて読みやすそうだったから、借りて きた次第。ああ、本の選択基準が変わってきたな。

後ろ書きを見ると、書いた方は、八海酒造で、杜氏を40年間務めた高浜春男氏。 そうか、この方によって、千寿とか万寿が生み出されたんだな。なんたって、杜氏は 酒製造の現場総監督。今風に言うと、製造部長さんだ。

酒蔵のオーナーさんは別に居て、南雲家という由緒ある家の持ち物らしい。そこへ 季節労働者である杜氏とその配下である蔵人が毎年秋になると訪れて、春まで酒を 作るってのが、慣わしだったそうな。

この高浜氏の出は、新潟県の野積と言う辺鄙な所、冬は雪が降る為、仕事が無く、季節労働者と なって、あちこちの蔵へ出かけたとか。杜氏が多かったので、杜氏の里と呼ばれていたらしい。

杜氏は色々な蔵で修行を積んで、酒作りを極めた人。かの人も、灘だとか群馬だとかの 5箇所の蔵で修行を積み、最後は杜氏派閥のドンから、どこの蔵に骨を埋めるかと、3つ蔵の 候補を提示されたとか。迷った末、実家から近い、八海山酒造に行く事を決めた。 オーナーは、酒作りに対して口を挟まない人だったので、思う存分、理想の酒を追求 出来たそうだ。技術者冥利に尽きますなあ。

酒作り用の米で最適なのは、山田錦。それも、岡山の某で採れるやつが最高とか。どこの 蔵も狙うものだから、米の値段が高騰。そんな米をやっとこさ仕入れ、おしげもなく 磨く(精米)。精米度合いによって、大吟醸とか吟醸とかが決まるらしく、法律でその度合いが 決まってるとからしいんだけど、良い酒(端麗辛口)にこだわって、その度合いを変更 してるとか。大吟醸だと、米の65%ぐらいを削ってしまうらしい。余ったぬかは、県内の 何とか製菓へ送られて、米菓子になるんだな。

日本酒は、蒸かした米に、米麹と酒母(乳酸菌)を付加して作るそうだ。蒸かす為の蒸気 発生係りの釜屋さんとか麹を作る麹屋さんとか、もろみを絞って原酒を造る絞り屋 さんとか、こだわりを持った人の連携で作られるそうだ。奥深いなあ。

ワインとかだと、酵母で醗酵で終わりなんだけど、日本酒だと、米と麹の反応で、米を ぶどう糖に変え、それを酒母の力を借りて、アルコールにするそうだ。3回に渡って、これらを 投入して、進行度を変化させるんで、3段仕込みと言うそうな。手がかかるんですなあ。

品評会があって、それに出す為の酒を造るのが大変らしい。品評会用の酒を喧嘩酒と 言って大切にするとか。醗酵したもろみから原酒を絞るのも、最初のうちは荒走りと 言ってまずいらしいので、絞り工程の中間ぐらいのやつを出すらしい。

日本酒は純米酒と醸造用アルコールを添加したものと、大別すると2種になるそうだ。おいらは 醸造アルコールを添加したやつは、まがいものと思っていたけど、杜氏に言わせると 添加して調整したものも非常に良いとか。食品添加物とは全く意味合いが違うので、 ささいな事にこだわるのは愚の骨頂とか。

詳しい事は、アーカイブスをどうぞ。

manjaroの困った点、その1

manjaroLinuxを久しぶりにUpdateしたんだ。そしたらsshで繋がらなくなったぞ。万次郎Linuxは 以前使ってたArchLinuxのじゃじゃ馬ぶりとは違って、安定してたんだけどな。

GUIでログインしたら、NetworkManagerが動いて、ネットに繋ぎましたよって、アナウンスが 出てきた。けど、自棄の10.0.2.15にsshすると、いつまで経っても返答が返ってこない。

こういう時は、ifconfigだなと思って、確認してみると、あらら、IPv4のアドレスが振られて いないじゃないですか。マネージャに嘘つかれたぞ。しょうがないので、NICをdown/upすると いう揺さぶり攻撃したら、やっとアドレスを取得してくれたよ。こういうマネージャは解任 しましょ。

代わりは居るんかい? ってんで、ネットに広告を出したら、都合が良い事に Network Configuration (日本語) なんてのが引っかかってきた。

[sakae@manjaro ~]$ sudo systemctl enable dhcpcd@enp0s3.service
[sudo] password for sakae:
ln -s '/usr/lib/systemd/system/dhcpcd@.service' '/etc/systemd/system/multi-user.target.wants/dhcpcd@enp0s3.service'

そして、嘘付きマネージャは綺麗さっぱりと disable network.target して、おいたよ。もう来るんじゃないぞ。なんでも、このマネージャは シチエーションに応じて、いろいろとネット接続の面倒を見てくれる優れ者って事なんだけど、 おいらの所では、正直、オーバースペックだったわい。

manjaroの困った点、その2

GUI上でemacsを使うと、マークする時のキーバインド、Ctrl-spaceがよそのアプリに 分捕られてしまい、悲しい事になってたんだ。困ったその1の余勢を駆って、修正してみた。

よそのアプリってのは、名前は知らないけど、アプリを起動するヘルパーの役をするやつ。 Ctrl-spaceのon/offで、アプリの入力画面が出て来る。キーバインドは他のものに変更 出来れば、Ctrl-spaceは、横取りされる事なくemacsに届くだろうという算段。

設定は、きっと何処かに保存されてるはずだと、家捜ししてみたよ。そしたら

 ~/.config/hotkeys/synapse.hotkeys
[hotkey:activate]
Owner=synapse
Signature=<Super>space  ;; org Control

こんな所に仕舞ってあったぞ。キーモデファイヤをCtrlからSuperキーに変更。スーパーキーって、 普通には役立たずの、Windows用キーだから、ちょっぴり活用出来て、プチ嬉しいぞ。

また、いつの間にか、 Manjaro Wikiへようこそ! なんてページが出来てた。充実させて、XPユーザーを呼び込んでください。

制約論理プログラミング

前回に引き続いてprologです。記事を漁っていたら、 制約論理プログラミングで数独を解く なんてのに出会ったぞ。それから、 宣言型プログラミングの可能性と限界 とか、有名なあの方の プログラム不要の「制約プログラミング手習い」 なんてのにも出会った。面白い所では、 core logicで論理プログラミングとかやってみた。 なんて、clojureじゃないですか。まあ、Javaで盛んにprologが実装されてるんで、有っても 不思議じゃないか。

話を戻すと、 制約論理プログラミング ってのがprologと共に成長してきたようだ。これはなんと豊穣な世界だろう! オブジェクト嗜好とは全く違った世界で、面白い。

酷試対策用

折角、制約論理プログラミングなんてのに行き着いたので、これをアマチュア無線の酷試対策に 役立てられないかチャレンジしてみる。

register(V,I,R) :- V = I*R.     % オームの法則
parallel_register(V,I,R1,R2) :- I1+I2=I, register(V,I1,R1), register(V,I2,R2).  % 並列回路
serial_register(V,I,R1,R2)   :- V1+V2=V, register(V1,I,R1), register(V2,I,R2).  % 直列回路

これ、抵抗2本を、並列、もしくは直列接続した網に流れる電流と電源電圧の関係だ。例では、 ?- parallel_register(50,I,10,25). を実行すると I = 7 が結果として返る。なんてのが 載ってる。

これ、10オームと25オームの抵抗を並列接続して、50Vを加えたら何A流れますかって質問だ。 いかにも酷試に出そうだな。

上記をgprologで実行してみると、

/home/sakae/src/pl/r.pl compiled, 4 lines read - 1255 bytes written, 22 ms

(3 ms) yes
| ?- parallel_register(50,I,10,25).

no

ありゃりゃ、そんなの知らないって。これじゃ、酷試が受からないよ。しょうがない。 まずはトレースしてみるか。

| ?- trace.
The debugger will first creep -- showing everything (trace)

yes
{trace}
| ?- parallel_register(50,I,10,25).
      1    1  Call: parallel_register(50,_23,10,25) ?
      2    2  Call: register(50,_57,10) ?
      2    2  Fail: register(50,_57,10) ?
      1    1  Fail: parallel_register(50,_23,10,25) ?

no

その前に、スクリプトの意味する所を押さえておくか。オームの法則は中学生も知っている 社会常識だな。次の並列の所に出てくる、I1+I2=I ってのは、キルヒホッフの第一法則(俗に言う、電流則)だな。 回路の接点に流れ込んだ電流の総和と流れ出た電流の総和は等しいってやつ。I1は抵抗R1に 流れる電流、I2は抵抗R2に流れる電流。Iは電源からそれぞれの抵抗に流れる電流って事だ。 わざわざ法則と謳う程、大仰なものじゃないな。

直列の方は、それぞれの抵抗端の電圧を加えたものは電源電圧に等しい。これはキルヒホッフの 第二法則(電圧則とも言う)。これも、常識の範疇だな。

抵抗に加わる電圧が50Vで抵抗値が10オームの時の電流は? って聞かれて、そんなの知るか! って回答を拒否されたんで、全体的に知らないぞって事になってるんだな。ひょっとして、prologって 式の変形を出来ないの?

| ?- register(V,3,5).
V = 3*5
yes

| ?- register(20,I,5).
no

どうもそうらしい。それと、算術演算は不得意みたいね。段々、性格が分かってきたよ。 それじゃ、こうしてやる。

| ?- [user].
compiling user for byte code...
register(V,I,R) :- I = V/R.

user compiled, 2 lines read - 269 bytes written, 74358 ms
warning: user:1: redefining procedure register/3
         /home/sakae/src/pl/r.pl:1: previous definition
yes
| ?- register(20,I,5).
I = 20/5
yes

これで旨く行った。でも、warningで注意されてるように、後での追加はまずいのね。 述語がコード中で離れ離れになってると、

?- Warning: /home/sakae/src/pl/r.pl:4:
        Clauses of register/3 are not together in the source-file

こんな注意を喰らう事もあるよ。

| ?- [user].
compiling user for byte code...
reg(V,I,R) :- V = I*R.
reg(V,I,R) :- I = V/R.
reg(V,I,R) :- R = V/I.

user compiled, 4 lines read - 537 bytes written, 108704 ms
yes
| ?- listing.
reg(A, B, C) :-
        A = B * C.
reg(A, B, C) :-
        B = A / C.
reg(A, B, C) :-
        C = A / B.

代数式の結果を得るには、=をisに変えればいいんだな。

| ?- reg(20,2,R).

R = 20/2

yes
| ?- R is 20/2.

R = 10.0

それじゃ、例題をやる。

?- parallel_register(50,I,10,25).
I = 50/10+50/25

今度は、応用。

?- parallel_register(50,7,10,R).
false.

?- trace.
true.

[trace]  ?- parallel_register(50,7,10,R).
   Call: (6) parallel_register(50, 7, 10, _G300) ?
   Call: (7) 7=_G367+_G368 ?
   Fail: (7) 7=_G367+_G368 ?
   Fail: (6) parallel_register(50, 7, 10, _G300) ?
false.

ふむふむ、何と何を足したら7になる? こういう問いの答えは沢山有りすぎて、答えられないのか。 何とか遷都な。(おっ、神のお告げだ。東京は地震に見舞われるから、早く那須へでも遷都ナスべき。 東京五輪より、これ重要。日本沈没の憂き目に会いますぜ!)

この失敗した式も、registerと同様な問題を孕んでいるな。ここはもう、キルヒの法則って 事でくくり出して、可搬にしとくか。ついでに述語名も少し整理する。

ohm(V,I,R) :- V = I*R.
ohm(V,I,R) :- I = V/R.
ohm(V,I,R) :- R = V/I.
k(IV,IVa,IVb) :- IV = IVa+IVb; IVa = IV - IVb; IVb = IV - IVa.
para(V,I,R1,R2) :- ohm(V,I1,R1), ohm(V,I2,R2), k(I,I1,I2).
seri(V,I,R1,R2) :- ohm(V1,I,R1), ohm(V2,I,R2), k(V,V1,V2).

キルヒホッフの法則は、電流でも電圧でも成り立つので、それ風の変数名にしといた。本来なら ohmみたいにすっきりと分けて定義するべきなんだろうけど、セミコロンのor機能を使ってみた。 こういう書き方はprologでは推奨されないのかな?

?- para(50,7,10,R).
R = 50/ (7-50/10)

?- R is 50/ (7-50/10).
R = 25.

ちゃんと動いてる。さらなる例題。

?- para(V,7,10,25).
V = (7-V/25)*10 ;
V = (7-V/10)*25 ;
false.

両辺に変数が有るよ。後は、自分でこの方程式を解けばいいんだな。自動的には解いてくれないのだろうか? なお、この結果は、swiplのものです。gprologでやると、

| ?- para(V,7,10,25).

cannot display cyclic term for V ? a

cannot display cyclic term for V

no

yapでやp するかって質問も有ったので、勿論yes.

   ?- para(V,7,10,25).
V = (7- ** *10/25)*10 ? ;
V = (7- ** *25/10)*25 ? ;
no
   ?- V is  (7- ** *10/25)*10.
     ERROR!!
     TYPE ERROR- atom ** for arithmetic expression: expected evaluable term, got **

AZPrologでやってみると

| ?-para(V,7,10,25).
V       = (7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(
7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-
(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7-(7
-(7-(7-(7-(7-(7-(7-(7-(7-~/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*
10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/
25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)
*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10
/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25
)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*1
0/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/2
5)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10/25)*10
yes

なんだか、凄い答えが返ってきましたよ。努力賞ですかね(お前が偉そうに言うなっつうの)。

そんじゃ、更なる難題を。

?- seri(50,10,7,R).
R = (50-10*7)/10

?- R is (50-10*7)/10.
R = -2.

今度は、抵抗の直列回路、未知の抵抗と7オームの抵抗に50Vを加えたら10A流れたとさ。 電気回路をやった人なら、一瞬のうちに暗算して、回路抵抗は50/10で5オームと割り出して いる事でしょう。それなのに7オームが繋がってるって?

prologに聞いてみると、涼しい顔して答えを返してきました。答えはマイナス2オームですって。 こりゃ、超伝導を通り越して、江崎ダイオードならぬ、江崎抵抗の出現ですよ。今年のノーベル賞 間違い無し。早くネーチャー誌に投稿しとけ!!

さて、こういうトンデモはどうやって防止するか? リミッターを入れて、VもIもRも正数だけね ってのは、すぐに思いつくけど、具体的にどうする?

lm(Cv) :- Cv >= 0.

こんなのを定義しといて

?- lm(seri(50,10,7,R)).
ERROR: >=/2: Arguments are not sufficiently instantiated

も、エラーになるよ。ひょっとして解決不能? まてまて、そういう使い方ってLisp的な 使い方じゃろ。prologの流儀に反するから文句を言ってきたんじゃ。

?- seri(50,10,7,R), lm(R).
false.

?- seri(50,10,7,R), lm(R), Z is R.
false.

?- seri(50,10,2,R), lm(R), Z is R.
R = (50-10*2)/10,
Z = 3

Rって変数を渡さないといかんのね。そして、その結果が正だったら、式を is で計算させれば いいんか。

次は、面倒な操作を、マシンにやらせる方法。計算機なんだから加減乗除ぐらいはやって欲しいぞと。

?- X = 3, \+ number(X), V is X.
false.

?- X = 3*5, \+ number(X), V is X.
X = 3*5,
V = 15.

ちょいと実験した。式をどうやって見つけるか? 式は数値と違うって事だな。atomとも 違うみたいなので、数値の否定を取ってやればいいんか。

ohm(V,I,R) :- V = I*R; I = V/R; R = V/I.

k(I,I1,I2) :- I = I1+I2; I1 = I-I2; I2 = I-I1.

para(V,I,R1,R2) :- var(V), ohm(V, I, R1*R2/(R1+R2)).
para(V,I,R1,R2) :- ohm(V,I1,R1), ohm(V,I2,R2), k(I,I1,I2).

seri(V,I,R1,R2) :- var(I), ohm(V,I,R1+R2).
seri(V,I,R1,R2) :- ohm(V1,I,R1), ohm(V2,I,R2), k(V,V1,V2).

p(V,I,R1,R2) :- para(V,I,R1,R2), \+ number(V),  X is V,  write(X).
p(V,I,R1,R2) :- para(V,I,R1,R2), \+ number(I),  X is I,  write(X).
p(V,I,R1,R2) :- para(V,I,R1,R2), \+ number(R1), X is R1, write(X).
p(V,I,R1,R2) :- para(V,I,R1,R2), \+ number(R2), X is R2, write(X).

s(V,I,R1,R2) :- seri(V,I,R1,R2), \+ number(V),  X is V,  write(X).
s(V,I,R1,R2) :- seri(V,I,R1,R2), \+ number(I),  X is I,  write(X).
s(V,I,R1,R2) :- seri(V,I,R1,R2), \+ number(R1), X is R1, write(X).
s(V,I,R1,R2) :- seri(V,I,R1,R2), \+ number(R2), X is R2, write(X).

paraやseriをラッパーで覆っています。面倒なので、正数のチェックは省略。 それから、ラッパーの代わりに、一気にformat文で表示しちゃおうかと思ったんだけど、 どうもアプリ依存症みたいなんで、止めた。

それから、質問によっては、サイクリックな答えが返ってくる可能性があるんで、ちょっと 電子工学の技を使って知識を埋め込んでみた。ええ、合成抵抗の計算ね。var/1 ってのは、変数が拘束されてない時に、 yesになる述語。これで、場合分けしてるよ。

?- p(V,7,10,25).
50.0
V = 7* (10*25/ (10+25)) .

?- p(50,I,10,25).
7
I = 50/10+50/25 .

?- p(50,7,R,10).
25
R = 50/ (7-50/10) .

どうやら、旨く行ったわい。最後に、prologがどんな風に考えたか調べておく。

[trace]  ?- p(V,7,10,25).
   Call: (6) p(_G3647, 7, 10, 25) ? creep
   Call: (7) para(_G3647, 7, 10, 25) ? creep
   Call: (8) var(_G3647) ? creep
   Exit: (8) var(_G3647) ? creep
   Call: (8) ohm(_G3647, 7, 10*25/ (10+25)) ? creep
   Call: (9) _G3647=7* (10*25/ (10+25)) ? creep
   Exit: (9) 7* (10*25/ (10+25))=7* (10*25/ (10+25)) ? creep
   Exit: (8) ohm(7* (10*25/ (10+25)), 7, 10*25/ (10+25)) ? creep
   Exit: (7) para(7* (10*25/ (10+25)), 7, 10, 25) ? creep
   Call: (7) number(7* (10*25/ (10+25))) ? creep
   Fail: (7) number(7* (10*25/ (10+25))) ? creep
   Redo: (6) p(7* (10*25/ (10+25)), 7, 10, 25) ? creep
   Call: (7) _G3736 is 7* (10*25/ (10+25)) ? creep
   Exit: (7) 50.0 is 7* (10*25/ (10+25)) ? creep
   Call: (7) write(50.0) ? creep
50.0
   Exit: (7) write(50.0) ? creep
   Exit: (6) p(7* (10*25/ (10+25)), 7, 10, 25) ? creep
V = 7* (10*25/ (10+25)) .

そうそう、prologって、ゴールをandしながら進んで行くのが好まれるみたいで、orは 余り使わないとか。参考に覚えておこう。

上記のコードは、swipl、gprolog、AZProlog、yap、PrologCafeで動く事を確認した。 中でもyapのwriteの動きが一番まともだったよ。他のやつは、何と言うんですかね、nl 付き のように見えるんだけど。。。 下記、yapのセッションです。

% Restoring file /usr/lib/Yap/startup.yss
YAP 6.2.2 (i686-linux): Sun Aug  4 11:38:59 UTC 2013
MYDDAS version MYDDAS-0.9.1
   ?-  % reconsulting /home/sakae/src/pl/ohm.pl...
 % reconsulted /home/sakae/src/pl/ohm.pl in module user, 1 msec 8664 bytes
yes
   ?- s(V,3,4,5).
27V = 3*4+3*5 ?
yes
   ?- s(30,I,4,5).
3.33333333333333I = 30/(4+5) ?
yes
   ?- s(30,5,2,R).
4.0R = (30-5*2)/5 ?
yes

改行が入っていないんで、等式っぽく見えるぞ。