1
大学数学基礎解説
文献あり

命題論理における真偽判定問題の計算可能性について

1321
0

はじめに

はじめまして、mineelと申します。私は理論計算機科学等を普段勉強している学部生です。

今回はタイトルの通り命題論理における真偽判定問題の計算可能性について書きました。
こういった記事を投稿するのは初めてで、至らない部分は多いとは思いますが多めにお願いします。

記事のレベルは各分野の教科書の10ページ目くらいの内容を書いた程度のレベルです。なので面倒な部分や説明が下手な部分はあれど、難しい点は特にないので誰でも読める内容だとは思います。(ただし長いです)

ただ、今回は数学的な解説記事というよりはやってみた系の記事の方が近く、今回は個人的な興味についての自己紹介のような記事になればいいなと思っています。
よろしくお願いします。

この記事では0は自然数に含めるとします。
また、~番目や~項目などと言うときは必ず0番目や0項目から始まっていることもご注意ください。

※この記事はスマホからだと死ぬほど見辛いのでPCから見るのを推奨します。
スマホから見るなら横にすればまだ少し見やすいです。

真偽判定問題とは

論理式の定義

真偽判定問題の前に軽く命題論理の用語の説明から入ります。
いくつか独特な用語や記法を採用しているため、既に命題論理によくなじんでいる方も一応目は通しておおくといいかもしれません。
まずは論理式というものを定義していきます。
命題変項をp0,p1,...とします。(加算無限個)
命題変項とは、これ以上分解のできない論理式の単位のようなものだと考えていただいて大丈夫です。
また、論理式を表す記号としてA,B,C...を使います。

論理式

論理式を以下で定義する
(p0), (p1),...は論理式である
Aが論理式のとき(¬A)は論理式である
A,Bが論理式のとき(AB)は論理式である

論理式

(p4), ((p2)(¬(p2))), (¬(((p1)((p1)(p1)))) は論理式である
p4, ((p1)(p2)), ¬(p1(p1p1)), ((p)(q)) は論理式でない

(¬A)は「Aでない」、(AB)は「AならばB」のイメージです。ただ実際は論理式の形の定義を与えただけでまだ意味は考えてないです。
括弧がキショすぎますが、後々括弧がある方が扱いやすいのでここでは括弧付き論理式のみ考えます
また、定義からわかるようには使わない主義で行きます。
(AB)とかは(¬(A(¬B)))で意味的には同じになるので別にはなくても表現力は変わらないです。

論理式の意味付け

また論理式の意味付けを恣意的に行っていきます。
命題変項ごとに真偽を与える関数をここでは意味付け関数と呼ぶこととします。
任意に与えられた意味付け関数に対し、その定義域を拡張して論理式に意味を与えていきます。

ここでは、命題変項全体の集合をV, 論理式全体の集合をProとすることにします。
また、TV={,}とし、TV内の演算子として否定¬:TVTV及び選言(=または):TV2TVを通常の意味で使うことにします。

論理式の意味

意味付け関数f:VTVに対し、拡張意味付け関数F:ProTVを以下のように定義する
A(pi)のとき F(A)=f(pi)
A(¬B)のとき F(A)=¬F(B)
A(BC)のとき F(A)=¬F(B)  F(C)

この定義によって、各命題変項に対して真or偽を定めてやることで、各論理式に対しても真or偽を定めることができます。

論理式の意味

意味付け関数fを命題変項p0,p1に対して
f(p0)=, f(p1)=
として定義する。その時の論理式((p0)(¬(p1)))の意味を求める
  = F( ((p0)(¬(p1))) ) 
  = ¬F( (p0) )  F( (¬(p1)) )
  = ¬f(p0)  ¬F( (p1) )
  = ¬f(p0)  ¬f(p1)
  = ¬  ¬
  =   
  = 
により、上の意味付け関数fにおいては論理式((p0)(¬(p1)))の意味は偽であるということがわかる

真偽判定問題

以上の概念を用いて命題論理の真偽判定問題を定義します。

真偽判定問題

論理式A及び意味付け関数fを入力としたとき、「Afにおいて真か偽か」を出力とする問題を(命題論理の)真偽判定問題という

先ほどの例におけるf及び論理式((p0)(¬(p1)))を入力としたとき出力は偽であるというようになります。

意味付け関数fの定義域を命題変項全体の集合Vとしてしまうとfが有限集合でなくなってしまうため、真偽判定問題において与えられる意味付け関数fの定義域はA内の命題変項全体の集合Var(A)とすることとします
また、真偽判定問題においては形が同じ論理式は同一視し、論理式に現れる命題変項をp0,p1,...,p|Var(A)|1とします

ここからこの問題がコンピュータによって計算可能であるのかを考えていきます。

計算可能性

真偽判定問題の計算可能性

問題が計算可能であるとは、その問題を解くアルゴリズムがあるということです。
真偽判定問題はどうでしょうか?

先ほどの例でも実演した通り、¬を定義に沿って(pi)という形が出てくるまで分解していき、最後はTV内における¬の定義に従って真偽を求めていけばそれで行けそうですね。ナイスアルゴリズムです!
それではお疲れさまでした!

 
 
 
 
は???
そんな雑な説明が認められるのでしょうか?そもそもアルゴリズムって何ですか?

ここから「アルゴリズムがある」という曖昧な言葉をしっかり数学的に定義していきます。

原始再帰的関数・再帰的関数

計算可能性を定義するためにはいくつかの方法がありますが、ここでは再帰的関数及びNプログラムという2つの計算モデルを提示します。まずは(原始)再帰的関数から見ていきます。
(原始)再帰的関数とは数学的に定義された自然数上の関数です。以下のように定義しますが、定義は長いのでイメージさえ分かれば大丈夫です。

(原始)再帰的関数

原始再帰的関数を以下により再帰的に定義する
(1)初期関数
以下の3種類の初期関数は原始再帰的関数である
①零関数zero:N0N ただし zero()=0
②後者関数suc:NN ただし suc(x)=x+1
③射影関数pin:NnN ただし pin(x0,x1,...,xn1)=xi, 0in1

(2)関数合成
g:NmNgj:NnN (j=0,1,...,m1)が原始再帰的関数のとき
 f(x)=g(g0(x), g1(x), ..., gm1(x))
で定義される関数f:NnN は原始再帰的関数である
※ただし実際は合成の際の引数の個数は統一されてなくても良いことが示される

(3)原始帰納法
g:NnNh:Nn+2Nが原始再帰的関数のとき
 f(x,0)=g(x)
 f(x,t+1)=h(x,t,f(x,t)) 
で定義される関数f:Nn+1Nは原始再帰的関数である

また、以上の3条件における「原始再帰的関数」という言葉を「再帰的関数」に置き換えて、さらに以下の条件も加えたものを再帰的関数という

(4)最小解関数
原始再帰的関数g:Nn+1Nに対し
 f(x)={g(x,y)=0yz<y0g(x,z)  
で定義される関数f:NnNは再帰的関数である。このfを最小解関数と呼びμy(g(x,y)=0)と表す

(原始)再帰的関数

add(x,t)=x+tは原始再帰的関数である
(3)において、
 add(x,0)=p01(x)
 add(x,t+1)=suc(p23(x,t,add(x,t)))(関数合成)
とすればよい

pred(x)={x1(x1)0(x=0)は原始再帰的関数である
(3)において、
 pred(0)=zero()
 pred(t+1)=p02(t,pred(t))
とすればよい

あまり計算論の話を深堀するつもりはないので定義の紹介にとどめておきます。イメージとしてはさまざまな関数を繰り返しによって定義しようという感じです。

一応プログラムとの対応関係のイメージとして、最小解関数がプログラムで言うwhile文、すなわち、繰り返し回数が事前にわからないループに対応します。なので無限ループに陥り計算が止まらない可能性があるというわけです。
そういうわけで定義からも分かるように最小解関数のみが未定義を引き起こす要因となり、原始再帰的関数と違い再帰的関数は入力によっては未定義(=無限ループ)となる可能性を持っています。
 
 
ここでようやく計算可能性の定義に移れます。

計算可能関数

自然数上の関数f:NnNが計算可能であるとは、fが再帰的関数であるということである

先ほど再帰的関数のイメージはさまざまな関数を繰り返しを用いて定義することと話したことと併せて考えると、計算不可能な関数とはとても繰り返しじゃ定義できないような関数というイメージとなります。
(※本来計算可能性という概念は計算モデルに依存しない普遍的な概念であるはずなので様々に同値な定義がありますが、数学的に定義が理解しやすい再帰的関数による定義をここでは採用させていただきます) 

ここで着目してほしい点が、原則として計算可能性というのはあくまで自然数上の関数にのみ定義される概念であるため、先ほどの真偽判定問題の計算可能性を判断するにはそもそも入力の論理式及び意味付け関数を自然数へとコーディングする必要があるという点です。

(※補足:と私はずっと思い込んでいたのですが、チューリング機械による計算可能性の定義が「コンピュータによって計算可能」本来の姿であり、文字列の集合上の関数に対しても計算可能性は定義されます。本当に申し訳ございませんでした)

ただ、その話に入る前にもう少し計算モデルの話を進めます。
再帰的関数の概念は計算可能関数の数学的な性質を扱うにはとても便利な概念なのですが、もう少しプログラムチックにして直感的に扱いやすい形で計算可能関数を表現してみることにします。

Nプログラム

Nプログラムとは原始再帰的関数を用いた数学的にはラベル付き有向グラフとして定義されるプログラムです。
記事の一番下の具体的なNプログラムを先に見ておけば雰囲気をつかみやすいかもしれません。
より詳しいことは参考文献の『計算論』(高橋)をご覧ください。Nプログラムの概念はこちらの本から拝借させていただきました。

Nプログラム

以下のノード及びラベル付き枝の組み合わせでできるラベル付き有向グラフをNプログラムという。また、Nプログラムが表す自然数上の関数も自然に定義する。
(1)入力命令及び出力命令
input(x)
output(y)

(2)代入命令(fは原始再帰的関数)
x:=f(x)

(3)判定命令(f,gは原始再帰的関数)
f(x)=g(y)?truetruetruefalse

すなわち、Nプログラムは変数に式の値を代入する代入命令及び二つの式の値を比較する判定命令によって構成されるプログラムのようなものとなります。
また、定義からわかる通り、Nプログラムも再帰的関数と同様に自然数の組を受け取り自然数を返す自然数上の関数となっています。
そして最も重要な点が、Nプログラムで書ける関数はすべて再帰的関数であり、再帰的関数はすべてNプログラムで書ける関数であるという点です。(ここでは証明は省きます)
これにより、再帰的関数の代わりに直感的に扱いやすいNプログラムを用いて計算可能性について考察することができるということとなります。

さて、ここから先ほどの真偽判定問題の計算可能性をちゃんと証明していきます。

真偽判定問題の計算可能性の証明(本題)

コーディング

ここからが本題となります。改めてやるべきことをまとめます。
①計算可能性は自然数上の関数にのみ定義されるので、真偽判定問題の入力である論理式及び意味付け関数を自然数へとコーディングする
②コーディングされた自然数を受け取って、正しく0または1を返す関数をNプログラムで書く
という2つのことができれば真偽判定問題の計算可能性が証明できます。

ますコーディングの話から進めていくことにします。
論理式に使われる各記号に対して自然数を割り当てるコーディング関数αを定義します。すなわち、αによって各論理式は自然数列へと変換されます。

コーディング関数

各記号に対してコーディング関数αを以下のように定義する
α(()=3, α())=5, α(¬)=7, α()=11, α(pi)=13+2i

また、αを拡張して論理式に対しても定義する
A(pi)のとき α(A)=3, 13+2i, 5
A(¬B)のとき α(A)=3, 7, α(B), 5
A(BC)のとき α(A)=3, α(B), 11, α(C), 5
ただし、出力は自然数列となっている

コーディング

α( ((p2)(¬(p2))) )=3, 3, 17, 5, 11, 3, 7, 3, 17, 5, 5, 5

これにより論理式を自然数列へと変換することができました。目標にはかなり近づいてきましたが、まだ論理式の長さによって変換後の数列の長さが異なっており扱いにくいです。
そこで、自然数列をさらに一つの自然数にまとめるゲーデル関数というものを考えます。

ゲーデル関数

以下を満たす原始再帰的関数G:NmNをゲーデル関数という
(性質)
0im1となる各iに対して
 Gi(G(x0,x1,...,xm1))=xi
を満たす原始再帰的関数Gi:NNが存在する

すなわち、ゲーデル関数とは自然数列を一つの自然数へとまとめることができ、かつ一意に元の自然数列を復元できる関数ということになります。
また、各Giをゲーデル関数Gの逆関数と呼びます。

このゲーデル関数及び先ほどのコーディング関数を組み合わせることで、各論理式を一意に復元できる形で一つの自然数へとコーディングできるようになります。各論理式に対応する自然数をその論理式のゲーデル数と呼ぶことにします。
これで論理式に対する操作や言明をその論理式ゲーデル数への操作や言明、すなわち記号ばかりだったのを、とうとうただの自然数に関する話へとすり替えることができました。いよいよ数学っぽくなってきました。

ところでゲーデル関数は性質だけ示してその存在はまだ示していません。そこで具体的な構成によって存在を示してみます。
ただ、ゲーデル関数の構成方法は一通りでなく、おのおの好きなように実装してもらって構わないです。
ここでは構成方法の一例をあげ、そのゲーデル関数を用いて話を進めていきます。

ゲーデル関数の受肉

次の関数G:NmNはゲーデル関数である
 G(x0,x1,...,xm1)=2x03x1 ... pr(m1)xm1
ただし、pr(x)x番目の素数を返す関数である

Gがゲーデル関数の性質を満たすことの証明は長くなるのでイメージだけ書きます。

証明のイメージ)
まず、xが素数の時1を返す関数prime(x)x未満の数y,zのすべての組み合わせに関してx=yzか調べればいいので事前に上限がわかる繰り返しで表現できるため原始再帰的です。
pr(x)primeを用いて、pr(x1)から始めてpr(x1)+1は素数か、pr(x1)+2は素数かと順に調べていけばわかるため原始再帰的です。繰り返し回数の上限は「nより大きい最小の素数はn!+1以下」という事実よりわかります。
また、pwr(x,y)としてxの素因数分解におけるpr(y)のべきを返す関数(x=0なら0を返す)とすれば、Gi(x)=pwr(x,i)により逆関数を定義できます。(素因数分解の一意性より一意に定まる)
これは、xpr(y)で割り切れるか、xpr(y)2で割り切れるか、と順に調べていき割り切れなくなるタイミングを調べればわかるので原始再帰的です。繰り返し回数の上限はx回とすれば大丈夫です。
ついでに「割り切れるか」というのも証明は省きますがちゃんと原始再帰的です。

また、意味付け関数もコーディングする必要がありますが、f(α)は第i項目が以下のようになる0,1からなる自然数列を返すようにコーディング関数の定義域を拡張して、それに対して上記のゲーデル関数を用いればコーディングが完了します。
 (α(f))i={1(f(pi))0(f(pi))
ただし、0i|dom(f)|1です。

真偽判定問題においては形が同じ論理式は同一視し、論理式に現れる命題変項をp0,p1,...,p|dom(f)|1に限定していました

以上により、①のステップは完了しました。
ここから②のステップへと移っていきます。

Nプログラムの構成

コーディングにより真偽判定問題を自然数上の関数として定式化することができました。
すなわち、真偽判定問題は、xNを論理式のコード、yNを意味付け関数のコードとして、
 P(x,y)={1(xy)0(xy)
という関数P:N2Nによって定式化することができたということになります。

故に、これから示すべきことは関数Pが計算可能関数であるということになります。
当然、Pが再帰的関数であることを直接示せればよいのですが、再帰的関数の概念は直感的に扱いにくいので、同等な概念であるNプログラムを用いることによってPが計算可能であることを示していきたいと思います。

証明用のNプログラムの構成の方針は以下の通りです。簡単に言えば論理式の構文木に沿って左から順にみていき、真理値を更新していく感じです。

ざっくり手順
①与えられた論理式の形を判断する
すなわち、(¬B)なのか(BC)なのか(pi)を判断する
②論理式の形をリストに記録する
③論理式の形に添って部分論理式を取得する
④取得した論理式について①~③を論理式の形が(pi)になるまで繰り返す
f(pi)の値に沿って真理値を更新する
⑥論理式の形を記録したリストを後ろからさかのぼり、形に添って真理値を更新していく
⑦リストの初めまでさかのぼったら、その時の真理値を出力する

順に説明していきます。

手順① 与えられた論理式の形を判断する

本来ならそこそこ面倒な問題だと思われますが、論理式にキショい括弧を付け続けた真価がここでついに発揮されます。
コーディング関数の定義を見直してみましょう。

コーディング関数

各記号に対してコーディング関数αを以下のように定義する
α(()=3, α())=5, α(¬)=7, α()=11, α(pi)=13+2i

また、αを拡張して論理式に対しても定義する
A(pi)のとき α(A)=3, 13+2i, 5
A(¬B)のとき α(A)=3, 7, α(B), 5
A(BC)のとき α(A)=3, α(B), 11, α(C), 5
ただし、出力は自然数列となっている

よく見ると自然数列へと変換した後の、第1項目に特徴が表れていませんか?
(ただし数列は第0項目がスタートとしている)
A(¬B)のときは7となっています。
A(BC)のときは分かりにくいですが、Bがどんな形でもα(B)の第0項目は定義から必ず3となっていることがわかります。そしてそれ以外のときはA(pi)となります。
すなわち、A(BC)のときは第1項目は3となっていることになります。

つまり、各論理式のコードの第1項目(=G1(x))の値を調べればすぐに論理式の形が判断できるわけです。括弧を省略しなくてほんとよかったですね!

ところで、なぜ論理式の形を知る必要があるのかということなのですが、これは論理式には合成性という、その部分論理式について調べれば全体がわかるという性質があるためになります。
故に、形を判断した後はその部分論理式を取得しなければなりません。
ただ、その前に手順②に移ります。

手順② 論理式の形をリストに記録する

記録する意義、記録をどう使うかについては後に改めて説明します。

論理式の形を記録するというのは、論理式を部分論理式に分解していくときに各部分論理式の形をメモしていくというようなイメージです。
ここではその記録の方法について説明をします。

まず、そもそもNプログラムにおいてはリストなど定義されておりません。
ではリストをどう扱うのか。
ここでは、1以上の自然数そのものをリストとして見るということをしてみることにします。
具体的には、自然数を素因数分解したときのべきの部分に着目し、2のべきが3ならリストの第0項目は33のべきが0ならリストの第1項目は0として見るといった具合です。例を見てみましょう。

リスト

30184=2373111より
  30184=[3,0,0,3,1,0,0,...]

1=[0,0,...]

例からも分かる通り、自然数は無限リストとなっており上限を気にしなくてもよいのはとても便利です。ただし、格納できるのは0以上の自然数のみとなっております。

また、要素の取得、挿入あたりの操作も一緒に原始再帰的関数で実装します。(削除は特に使わないので今回は無視します)

まず、取得について。
リストLNに対してget(L,i)というLi番目の要素を取得する原始再帰的関数を考えます。
これは、get(L,i)=pwr(L,i)とすることで問題なく定義できます。
pwr(L,i)の定義はLの素因数分解においてi番目の素数のべきを返すというものでした)

続いてリストへの挿入について。
リストLNに対してins(L,i,j)というLの第i項目にjNを挿入する原始再帰的関数を考えます。
これは、Li番目の要素get(L,i)を取得して、i番目の素数pr(i)j乗をかけて、pr(i)get(L,i)乗で割ることで挿入ができます。具体的には、
 ins(L,i,j)=div( exp(pr(i),get(L,i)), mult(L,exp(pr(i),j)) )
によって問題なく定義できます。
証明は省きますが、mult(x,y)=xyexp(x,y)=xydiv(x,y)=yx(割り切れないときは0を返す)はすべて原始再帰的であり、その合成なのでins(L,i,j)も原始再帰的です。
流石に見づらいのでもう少しわかりやすく書くと
 ins(L,i,j)=(Lpr(i)j)pr(i)get(L,i)
となります。

以上によりリストに対する要素の取得、要素の挿入が定義できました。
 
 
話を戻します。論理式の形をリストに記録するという話でしたが、各形式の論理式に対して具体的にどんな自然数を記録すればよいのでしょうか。

論理式が否定形だったらα(¬)=7より7を記録すれば、記録を見返したときに否定形だったことがすぐ分かるので良さそうです。
含意形((BC)の形)だったらどうでしょう。一見α()=11なので11を記録すればよいのでは?となりますが、実は問題があります。

今回の証明の方針では論理式の構文木を"左から"見ていくという方針をとっており、含意形の論理式に対して手順③においては左の部分論理式しか取得しません。
その後に、手順⑥においてさかのぼっていく際に含意形の記録を見つけたら右の部分論理式について見ていくという方針にしています。
故に、含意形の論理式を記録する際には右側の部分論理式の情報も同時に記録する必要があるわけです。
これがこの手順で論理式を記録する必要性が出てくる理由の1つとなります。

そのために、含意形の記録だとわかり、なおかつすぐに右側の部分論理式の情報も得られるような記録といったら、右側の部分論理式のコードをそのまま記録するのが一番良いでしょう。実は記録用リストに記録する情報は否定形か含意形かの2種類の情報しか記録しません。
なので、記録が7と等しいか等しくないかさえ分かれば十分なので、含意形の記録の際に右側の部分論理式のコードを記録すれば問題ないというわけです。(なので正直否定形の方も7を記録することに本質的な意義はありません)

ところで、論理式が(pi)の形だったらどうするんだ、となりますが、そこは構文解析の終着点であり、そこまで着いたら記録をさかのぼるステップに入るので特に記録は必要ないです。

ちなみに、まだこの段階は論理式の構文を解析してるだけの段階なので、肝心の論理式の真理値についてはまだ何も触れていません。
手順⑤以降で真理値の更新をしていきます。

手順③ 論理式の形に添って部分論理式を取得する

論理式が(¬B)の形だったらBのコード得るgetn(x)を、(BC)の形だったらBのコードを得るgetl(x)Cのコードを得るgetr(x)が必要となります。ただし、すべて原始再帰的でなければなりません。

それぞれ実装していく前に、論理式の長さを取得する関数Lh:NNを実装しましょう。論理式の長さとは括弧も含めた記号の数のことを言います。これは
 Lh(x)=i<xsub(1,sub(1,div(pr(i),x)))
により定義できます。もう少しわかりやすく書くと
 Lh(x)=i<x{1(1x/pr(i))}
のような感じとなります。

それでは説明に入ります。細かい部分の証明は基本省きます。
まず、原始再帰的関数の有限和及び有限積は原始再帰的です。そして、sub
 sub(x,y)={xy(xy)0(x<y)
によって定義されます。マイナスにはならない点には注意してください。故に、sub(1,sub(1,x))
 sub(1,sub(1,x))={1(x>0)0(x=0)
となります。xに適当に5など入れて確認してみてください。
また、divの定義は
 div(x,y)={yx(yx)0(yx)
でした。

すなわち、改めて、sub(1,sub(1,div(pr(i),x)))
 sub(1,sub(1,div(pr(i),x)))={1(xpr(i))0(xpr(i))
というように解釈できます。当然、原始再帰的関数の合成なのでこれも原始再帰的です。

ところで、自然数xの表す論理式のコードは、
 2G0(x)3G1(x) ... pr(Lh(x)1)GLh(x)1(x)     (x)
という形になっており(各Giはゲーデル関数の逆関数)、論理式のコードxpr(0)(=2)で割り切れるか、pr(1)(=3)で割り切れるか、を調べていき、割り切れたら1を足すということを繰り返せば論理式の長さが得られることとなり、上述のLhの定義が得られます。
繰り返しの回数は少なくともpr(x)まで調べればさすがに大丈夫です(かなり余裕を持った上限の取り方になっています)

また、BCにおいての左の部分論理式の長さを得る原始再帰的関数Lhl:NNも定義しましょう。
Lhrさえ分かれば、右の部分論理式の長さを得る原始再帰的関数Lhr:NNLhlを用いて
 Lhr(x)=sub(Lh(x),suc3(Lhl(x)))
により得ることができます。そして、Lhlは以下のようにして得ることができます。
 Lhl(x)=μz<x(sub(i<zsub(suc(div(3,pwr(x,suc(i)))),div(5,pwr(x,suc(i))),suc(i)))=0)
鬼のように見づらいので、もう少しわかりやすく書くと
 Lhl(x)=μz<x[ i<z{1+Gi+1(x)/3Gi+1(x)/5}=i+1 ]
となります。各Gjはゲーデル関数の逆関数です。μz<xについては後述します。

では、Lhlの説明に入ります。
まず、イメージとしては左から順に記号を見ていって、左括弧を見たら1を足す、右括弧を見たら1を引くというようにして、合計が0になった時点の位置を出力するといった具合です。すなわち、右括弧の数がそれまでの左括弧の数と等しくなった位置が左の部分論理式の終わりの位置という考えを利用しました。
詳しく見てみると、まず、1+Gi+1(x)/3Gi+1(x)/5の部分が、自然数xに対応する論理式のi+1番目の記号が「(」ならば2、「)」ならば0、「(」でも「)」でもないならば1を返す関数となっていることがわかります。すなわち、左括弧の数と右括弧の数が等しくなるタイミングで
 i{1+Gi+1(x)/3Gi+1(x)/5}=i+1
となることがわかります。

i0からz1まで動かして和を取るようになっているため、ちょうど上の式が成り立つタイミングで終わるようなzがもらえればよいことになりますが、それを実現してくれるのが限定最小解関数μz<xとなるわけです。
つまり、μz<xというのは、z0から調べていき、括弧の中身の式が初めて成立するようなzを返す関数となります。
再帰的関数の定義にあった通常の最小解関数との違いは、zの探索範囲に上限があるという点です。(今回の上限はx
こちらも証明は省きますが、括弧の中身の式が原始再帰的ならば最小解関数も原始再帰的となります。
また、ここでは詳しくは説明しませんが、=は実は原始再帰的述語と呼ばれるものであり、しっかり原始再帰的です。すなわち、Lhl全体も原始再帰的ということとなります。

これで準備が完了したので、いざgetn,getl,getrを実装していきましょう。
 getn(x)=i<sub(Lh(x),3)exp(pr(i),pwr(x,suc2(i))
 getl(x)=i<Lhl(x)exp(pr(i),pwr(x,suc(i))
 getr(x)=i<Lhr(x)exp(pr(i),pwr(x,add(i,suc2(Lhl(x))))
により得られます。全てもう少し見やすく書くと、
 getn(x)=i<Lh(x)3pr(i)Gi+2(x)
 getl(x)=i<Lhl(x)pr(i)Gi+1(x)
 getr(x)=i<Lhr(x)pr(i)Gi+(Lhl(x)+2)(x)
となっています。

これについては、自然数xに対応する論理式のコードが
 2G0(x)3G1(x) ... pr(Lh(x)1)GLh(x)1(x)  (=  i<Lh(x)pr(i)Gi(x)  )
という形となっていることを思い出せばほとんど見たまんまですが、例えばgetnは部分論理式の長さが全体の長さLh(x)から(¬)3つを抜いたLh(x)3なのでi0からLh(x)4まで動かせばよく、各pr(i)のべきは元の論理式から(¬2個分ずらしていけば否定形の部分論理式を得ることができます。また、原始再帰的関数の有限積は原始再帰的でしたので当然getnも原始再帰的です。
残り二つについても全く同様です。

非常に目が疲れるパートでしたがこれで手順③の目標は達成できたこととなります。

手順④ 取得した論理式について①~③を論理式の形が(pi)になるまで繰り返す

これは与えられた論理式のコードを手順③で得た論理式のコードに書き換えて、同じことを繰り返すだけです。手順①で論理式の形が(pi)になっていることがわかったら、手順⑤に進みます。

手順⑤ f(pi)の値に沿って真理値を更新する

やっと真理値に触れることができます。終わりまでもう少しです。

まず、そもそもf(pi)を求める方法を考えます。
ただこれは簡単で、意味付け関数fは、第i項目が命題変項pifにおける真偽(0または1)となる01の自然数列(のコード)として与えられるので、その第i項目がf(pi)ということになります。
意味付け関数のコードは自然数yで与えられているため、pwr(y,i)により命題変項piの真偽(0または1)を取り出すことができます。
すなわち、
 f(pi)=pwr(y,i)=1
と書けます。
ただし、pwr(y,i)yの素因数分解におけるpr(i)(=i番目の素数)のべきを返す関数でした。

しかし、piiは直接与えられるわけではありません。論理式(pi)のコードxNからiを取り出さなければいけません。
コーディング関数の定義よりα(pi)=13+2iであり、また、(pi)のコードxからpiのコードの部分を取り出すにはpwr(x,1)とすればよいので、i
 div(sub(pwr(x,1),13),2)
により論理式のコードxから取り出すことができます。
ただし、predpred(x)=x1x=0なら0を返す)でした。
故に、iにこの式を当てはめることで改めて、
 f(pi)=pwr(y,div(sub(pwr(x,1),13),2))=1
となることがわかります。
ただし、少し長いのでv(x,y)=pwr(y,div(sub(pwr(x,1),13),2))と書くことにします。
xは入力の論理式のコード、yは入力の意味付け関数のコードでした)

これでf(pi)を求める方法がわかりましたので、真理値を更新方法を考察していきましょう。
真理値を格納する変数をtvとし、初期値は0とします。
更新の方法は、元のtvの値に直接f(pi)の値を代入するという方法でtvの値を更新していきます。なぜこのように真理値を更新していけばよいのかは後に解説します。
したがって、具体的な真理値の更新を表す式は
 tv:=v(x,y)
とすれば大丈夫です。
:=」はNプログラムにおける代入命令に使われる記号で、左の変数に右の式を代入するという意味でした。

手順⑥ 論理式の形を記録したリストを後ろからさかのぼり、形に添って真理値を更新していく

この手順が真偽判定問題の答えを得るための核となる手順です。
この手順で手順②において論理式の形を記録したリストを活用していくこととなります。

リストについて振り返ると、リストの中身は、形が¬であることを記録した7及び形がであることと右の論理式の情報を記録したgetr(x)の二種類しかありません。
なので、論理式の分解が終わり記録をさかのぼって真理値を更新していく際には、この二種類の記録に対してそれぞれどうtvを更新していけばよいかを考えれば十分だとわかります。
リストの自然数がどちらの記録なのかは、その自然数が7であるかどうかを調べればわかるのでした。

まず、7を見たときについて。
今見ている論理式が(¬B)という形だったとします。
今、リストをさかのぼってgetr(x)まで戻ってきたということは、論理式Bの真理値の解析は終わっており、その値はtvに格納されている状況にあることになります。なので、(¬B)の真理値を知りたかったらtvの値を逆転させればよいです。すなわち具体的な更新の式は
 tv:=sub(1,tv)
とすることでtvの値を逆転させることができます。

続いて、getr(x)を見たときについて。
今見ている論理式が(BC)という形だったとします。
今、論理式を分解していくときは左からみていくという方針を取っていました。
なので、リストをさかのぼってgetr(x)まで戻ってきたということは、論理式Bの真理値の解析は終わっており、その値はtvに格納されている状況にあることになります。

もし、Bの真理値が偽だったとしましょう。
この場合はラッキーで、F((BC))=¬F(B)F(C)だったことを思い出せば、Bが偽なら(BC)全体の真理値も真だということとなります。故に、もはやCのことなど忘れて、即座にtvの値を1に更新すればよいこととなります。

では、Bの真理値が真だったとしましょう。
こちらの場合は(BC)全体の真理値はまだわかりません。ただしかし、この場合には実はCの真理値と(BC)全体の真理値が完全に一致することはわかります。
なので、この場合には論理式Bのことなど完全に忘れてもう一度1から論理式Cについて解析しなおせばよいわけです。つまり、与えられた論理式をCとして手順①からやり直せばよいということとなります。

ちなみに、この時点でリストをさかのぼっている状態から、またリストに書き込む状態に戻るため今までの記録が邪魔になるように思えますが、別に削除とかはしなくてもins関数で上書きすればよいだけなので特に気にしなくても大丈夫です。

これで真理値の更新の仕方がわかりました。

ところで、手順⑤で今までのtvの値を無視してtvには直接f(pi)の値を代入すると説明しましたが、今までのtvの値を利用するタイミングというのはこの手順でリストの¬の記録を見た時だけなので、論理式を(pi)まで分解していったときは、今までの値に関係なく新しく解析中の部分論理式の真理値がわかればよいので、そのまま直接f(pi)の値を使ってもよいというためになります。

手順⑦ リストの初めまでさかのぼったら、その時の真理値を出力する

基本は書いてある通りで特に説明することもないのですが、今まで触れなかったカウンターについてここで説明します。
ここでのカウンターは、その時点でリストの第何項目を見ているのかを表すカウンターとなります。なので、リストに対する要素の取得や挿入の操作をする際には、カウンターにより位置を確認します。
カウンターは手順③で部分論理式を取得した直後に+1し、手順⑥で一つさかのぼるごとに1します。

カウンターについての説明はこの程度です。そして、カウンターを見て終了タイミングを把握し、その時のtvを取得すれば大丈夫となります。

実際のNプログラム

今までのことをすべて一つのNプログラムにまとめて、証明終了となります。
ただ、今の私の知識では複雑なNプログラムをTeXで表現することが難しく、非常に見にくい図となってしまっています。申し訳ございません。
input(x,y)L:=1c:=0tv:=0φ:=xpwr(φ,1)=3?L:=ins(L,c,getr(φ))φ:=getl(φ)c:=suc(c)pwr(φ,1)=7?L:=ins(L,c,7)φ:=getn(φ)tv:=v(φ,y)c:=pred(c)get(L,c)=7?tv=0?φ:=get(L,c)tv:=sub(1,tv)tv:=1c=0?output(tv)true          falsetrue   false        falsefalse          truetruetrue      false

Lがリストを表す自然数、cがカウンターを表す自然数、φが論理式のコードを格納している自然数となっています。
今までのことをまとめただけなので特に説明することもありませんが、一応二つ目のノードに関してはL,c,φを並列に初期化していますが、実際はNプログラムに並列な計算は定義されていません。ただ今回は逐次的に連続してこの三つの計算を行えばよく、特に順番も重要でないためこのような書き方をしました。
ちなみに今更ですが裸の定数は実際はsucn(zero()):N0Nsucn回合成)という定数関数で、ちゃんと原始再帰的です。

これで真偽判定問題を表す関数Pが計算可能であることの証明完了となります。
本当は構成したNプログラムが要件を満たすのか確認しなければなりませんがどうかお許しを。。。

おわりに

ここまで見てくださってありがとうございました。
とても見やすい記事とは言えないとは思いますが、楽しんでいただけたら何よりです。

もしこの記事で書いたような内容が面白いと感じたならば、理論計算機科学という分野を学んでみることをお勧めします。
ざっくりいうと論理学や、計算理論、圏論などを用いてプログラミング言語などについて考察していく分野だと僕は思っています。

本当にありがとうございました。お疲れさまでした。

参考文献

[1]
高橋 正子, 計算論 計算可能性とラムダ計算, コンピュータサイエンス大学講座, 近代科学社, 1991
[2]
鹿島 亮, コンピュータサイエンスにおける様相論理, 森北出版株式会社, 2022
[3]
安井 邦夫, 現代論理学, 世界思想社, 1991
投稿日:2022223
OptHub AI Competition

この記事を高評価した人

高評価したユーザはいません

この記事に送られたバッジ

バッジはありません。
バッチを贈って投稿者を応援しよう

バッチを贈ると投稿者に現金やAmazonのギフトカードが還元されます。

投稿者

mineel
mineel
1
1321

コメント

他の人のコメント

コメントはありません。
読み込み中...
読み込み中
  1. はじめに
  2. 真偽判定問題とは
  3. 論理式の定義
  4. 論理式の意味付け
  5. 真偽判定問題
  6. 計算可能性
  7. 真偽判定問題の計算可能性
  8. 原始再帰的関数・再帰的関数
  9. Nプログラム
  10. 真偽判定問題の計算可能性の証明(本題)
  11. コーディング
  12. Nプログラムの構成
  13. おわりに
  14. 参考文献