はじめまして、mineelと申します。私は理論計算機科学等を普段勉強している学部生です。
今回はタイトルの通り命題論理における真偽判定問題の計算可能性について書きました。
こういった記事を投稿するのは初めてで、至らない部分は多いとは思いますが多めにお願いします。
記事のレベルは各分野の教科書の
ただ、今回は数学的な解説記事というよりはやってみた系の記事の方が近く、今回は個人的な興味についての自己紹介のような記事になればいいなと思っています。
よろしくお願いします。
この記事では
また、~番目や~項目などと言うときは必ず
※この記事はスマホからだと死ぬほど見辛いのでPCから見るのを推奨します。
スマホから見るなら横にすればまだ少し見やすいです。
真偽判定問題の前に軽く命題論理の用語の説明から入ります。
いくつか独特な用語や記法を採用しているため、既に命題論理によくなじんでいる方も一応目は通しておおくといいかもしれません。
まずは論理式というものを定義していきます。
命題変項を
命題変項とは、これ以上分解のできない論理式の単位のようなものだと考えていただいて大丈夫です。
また、論理式を表す記号として
論理式を以下で定義する
・
・
・
・
・
括弧がキショすぎますが、後々括弧がある方が扱いやすいのでここでは括弧付き論理式のみ考えます。
また、定義からわかるように
また論理式の意味付けを恣意的に行っていきます。
命題変項ごとに真偽を与える関数をここでは意味付け関数と呼ぶこととします。
任意に与えられた意味付け関数に対し、その定義域を拡張して論理式に意味を与えていきます。
ここでは、命題変項全体の集合を
また、
意味付け関数
・
・
・
この定義によって、各命題変項に対して真or偽を定めてやることで、各論理式に対しても真or偽を定めることができます。
意味付け関数
として定義する。その時の論理式
により、上の意味付け関数
以上の概念を用いて命題論理の真偽判定問題を定義します。
論理式
先ほどの例における
意味付け関数
また、真偽判定問題においては形が同じ論理式は同一視し、論理式に現れる命題変項を
ここからこの問題がコンピュータによって計算可能であるのかを考えていきます。
問題が計算可能であるとは、その問題を解くアルゴリズムがあるということです。
真偽判定問題はどうでしょうか?
先ほどの例でも実演した通り、
それではお疲れさまでした!
は???
そんな雑な説明が認められるのでしょうか?そもそもアルゴリズムって何ですか?
ここから「アルゴリズムがある」という曖昧な言葉をしっかり数学的に定義していきます。
計算可能性を定義するためにはいくつかの方法がありますが、ここでは再帰的関数及びNプログラムという
(原始)再帰的関数とは数学的に定義された自然数上の関数です。以下のように定義しますが、定義は長いのでイメージさえ分かれば大丈夫です。
原始再帰的関数を以下により再帰的に定義する
(1)初期関数
以下の
①零関数
②後者関数
③射影関数
(2)関数合成
で定義される関数
※ただし実際は合成の際の引数の個数は統一されてなくても良いことが示される
(3)原始帰納法
で定義される関数
また、以上の
(4)最小解関数
原始再帰的関数
で定義される関数
・
(3)において、
とすればよい
・
(3)において、
とすればよい
あまり計算論の話を深堀するつもりはないので定義の紹介にとどめておきます。イメージとしてはさまざまな関数を繰り返しによって定義しようという感じです。
一応プログラムとの対応関係のイメージとして、最小解関数がプログラムで言うwhile文、すなわち、繰り返し回数が事前にわからないループに対応します。なので無限ループに陥り計算が止まらない可能性があるというわけです。
そういうわけで定義からも分かるように最小解関数のみが未定義を引き起こす要因となり、原始再帰的関数と違い再帰的関数は入力によっては未定義(=無限ループ)となる可能性を持っています。
ここでようやく計算可能性の定義に移れます。
自然数上の関数
先ほど再帰的関数のイメージはさまざまな関数を繰り返しを用いて定義することと話したことと併せて考えると、計算不可能な関数とはとても繰り返しじゃ定義できないような関数というイメージとなります。
(※本来計算可能性という概念は計算モデルに依存しない普遍的な概念であるはずなので様々に同値な定義がありますが、数学的に定義が理解しやすい再帰的関数による定義をここでは採用させていただきます)
ここで着目してほしい点が、原則として計算可能性というのはあくまで自然数上の関数にのみ定義される概念であるため、先ほどの真偽判定問題の計算可能性を判断するにはそもそも入力の論理式及び意味付け関数を自然数へとコーディングする必要があるという点です。
(※補足:と私はずっと思い込んでいたのですが、チューリング機械による計算可能性の定義が「コンピュータによって計算可能」本来の姿であり、文字列の集合上の関数に対しても計算可能性は定義されます。本当に申し訳ございませんでした)
ただ、その話に入る前にもう少し計算モデルの話を進めます。
再帰的関数の概念は計算可能関数の数学的な性質を扱うにはとても便利な概念なのですが、もう少しプログラムチックにして直感的に扱いやすい形で計算可能関数を表現してみることにします。
Nプログラムとは原始再帰的関数を用いた数学的にはラベル付き有向グラフとして定義されるプログラムです。
記事の一番下の具体的なNプログラムを先に見ておけば雰囲気をつかみやすいかもしれません。
より詳しいことは参考文献の『計算論』(高橋)をご覧ください。Nプログラムの概念はこちらの本から拝借させていただきました。
以下のノード及びラベル付き枝の組み合わせでできるラベル付き有向グラフをNプログラムという。また、Nプログラムが表す自然数上の関数も自然に定義する。
(1)入力命令及び出力命令
(2)代入命令(
(3)判定命令(
すなわち、Nプログラムは変数に式の値を代入する代入命令及び二つの式の値を比較する判定命令によって構成されるプログラムのようなものとなります。
また、定義からわかる通り、Nプログラムも再帰的関数と同様に自然数の組を受け取り自然数を返す自然数上の関数となっています。
そして最も重要な点が、Nプログラムで書ける関数はすべて再帰的関数であり、再帰的関数はすべてNプログラムで書ける関数であるという点です。(ここでは証明は省きます)
これにより、再帰的関数の代わりに直感的に扱いやすいNプログラムを用いて計算可能性について考察することができるということとなります。
さて、ここから先ほどの真偽判定問題の計算可能性をちゃんと証明していきます。
ここからが本題となります。改めてやるべきことをまとめます。
①計算可能性は自然数上の関数にのみ定義されるので、真偽判定問題の入力である論理式及び意味付け関数を自然数へとコーディングする
②コーディングされた自然数を受け取って、正しく
という2つのことができれば真偽判定問題の計算可能性が証明できます。
ますコーディングの話から進めていくことにします。
論理式に使われる各記号に対して自然数を割り当てるコーディング関数
各記号に対してコーディング関数
また、
・
・
・
ただし、出力は自然数列となっている
これにより論理式を自然数列へと変換することができました。目標にはかなり近づいてきましたが、まだ論理式の長さによって変換後の数列の長さが異なっており扱いにくいです。
そこで、自然数列をさらに一つの自然数にまとめるゲーデル関数というものを考えます。
以下を満たす原始再帰的関数
(性質)
を満たす原始再帰的関数
すなわち、ゲーデル関数とは自然数列を一つの自然数へとまとめることができ、かつ一意に元の自然数列を復元できる関数ということになります。
また、各
このゲーデル関数及び先ほどのコーディング関数を組み合わせることで、各論理式を一意に復元できる形で一つの自然数へとコーディングできるようになります。各論理式に対応する自然数をその論理式のゲーデル数と呼ぶことにします。
これで論理式に対する操作や言明をその論理式ゲーデル数への操作や言明、すなわち記号ばかりだったのを、とうとうただの自然数に関する話へとすり替えることができました。いよいよ数学っぽくなってきました。
ところでゲーデル関数は性質だけ示してその存在はまだ示していません。そこで具体的な構成によって存在を示してみます。
ただ、ゲーデル関数の構成方法は一通りでなく、おのおの好きなように実装してもらって構わないです。
ここでは構成方法の一例をあげ、そのゲーデル関数を用いて話を進めていきます。
次の関数
ただし、
証明のイメージ)
まず、
また、
これは、
ついでに「割り切れるか」というのも証明は省きますがちゃんと原始再帰的です。
また、意味付け関数もコーディングする必要がありますが、
ただし、
真偽判定問題においては形が同じ論理式は同一視し、論理式に現れる命題変項を
以上により、①のステップは完了しました。
ここから②のステップへと移っていきます。
コーディングにより真偽判定問題を自然数上の関数として定式化することができました。
すなわち、真偽判定問題は、
という関数
故に、これから示すべきことは関数
当然、
証明用のNプログラムの構成の方針は以下の通りです。簡単に言えば論理式の構文木に沿って左から順にみていき、真理値を更新していく感じです。
ざっくり手順
①与えられた論理式の形を判断する
すなわち、
②論理式の形をリストに記録する
③論理式の形に添って部分論理式を取得する
④取得した論理式について①~③を論理式の形が
⑤
⑥論理式の形を記録したリストを後ろからさかのぼり、形に添って真理値を更新していく
⑦リストの初めまでさかのぼったら、その時の真理値を出力する
順に説明していきます。
本来ならそこそこ面倒な問題だと思われますが、論理式にキショい括弧を付け続けた真価がここでついに発揮されます。
コーディング関数の定義を見直してみましょう。
各記号に対してコーディング関数
また、
・
・
・
ただし、出力は自然数列となっている
よく見ると自然数列へと変換した後の、第
(ただし数列は第
すなわち、
つまり、各論理式のコードの第
ところで、なぜ論理式の形を知る必要があるのかということなのですが、これは論理式には合成性という、その部分論理式について調べれば全体がわかるという性質があるためになります。
故に、形を判断した後はその部分論理式を取得しなければなりません。
ただ、その前に手順②に移ります。
記録する意義、記録をどう使うかについては後に改めて説明します。
論理式の形を記録するというのは、論理式を部分論理式に分解していくときに各部分論理式の形をメモしていくというようなイメージです。
ここではその記録の方法について説明をします。
まず、そもそもNプログラムにおいてはリストなど定義されておりません。
ではリストをどう扱うのか。
ここでは、
具体的には、自然数を素因数分解したときのべきの部分に着目し、
・
・
例からも分かる通り、自然数は無限リストとなっており上限を気にしなくてもよいのはとても便利です。ただし、格納できるのは
また、要素の取得、挿入あたりの操作も一緒に原始再帰的関数で実装します。(削除は特に使わないので今回は無視します)
まず、取得について。
リスト
これは、
(
続いてリストへの挿入について。
リスト
これは、
によって問題なく定義できます。
証明は省きますが、
流石に見づらいのでもう少しわかりやすく書くと
となります。
以上によりリストに対する要素の取得、要素の挿入が定義できました。
話を戻します。論理式の形をリストに記録するという話でしたが、各形式の論理式に対して具体的にどんな自然数を記録すればよいのでしょうか。
論理式が否定形だったら
含意形(
今回の証明の方針では論理式の構文木を"左から"見ていくという方針をとっており、含意形の論理式に対して手順③においては左の部分論理式しか取得しません。
その後に、手順⑥においてさかのぼっていく際に含意形の記録を見つけたら右の部分論理式について見ていくという方針にしています。
故に、含意形の論理式を記録する際には右側の部分論理式の情報も同時に記録する必要があるわけです。
これがこの手順で論理式を記録する必要性が出てくる理由の1つとなります。
そのために、含意形の記録だとわかり、なおかつすぐに右側の部分論理式の情報も得られるような記録といったら、右側の部分論理式のコードをそのまま記録するのが一番良いでしょう。実は記録用リストに記録する情報は否定形か含意形かの
なので、記録が
ところで、論理式が
ちなみに、まだこの段階は論理式の構文を解析してるだけの段階なので、肝心の論理式の真理値についてはまだ何も触れていません。
手順⑤以降で真理値の更新をしていきます。
論理式が
それぞれ実装していく前に、論理式の長さを取得する関数
により定義できます。もう少しわかりやすく書くと
のような感じとなります。
それでは説明に入ります。細かい部分の証明は基本省きます。
まず、原始再帰的関数の有限和及び有限積は原始再帰的です。そして、
によって定義されます。マイナスにはならない点には注意してください。故に、
となります。
また、
でした。
すなわち、改めて、
というように解釈できます。当然、原始再帰的関数の合成なのでこれも原始再帰的です。
ところで、自然数
という形になっており(各
繰り返しの回数は少なくとも
また、
により得ることができます。そして、
鬼のように見づらいので、もう少しわかりやすく書くと
となります。各
では、
まず、イメージとしては左から順に記号を見ていって、左括弧を見たら
詳しく見てみると、まず、
となることがわかります。
今
つまり、
再帰的関数の定義にあった通常の最小解関数との違いは、
こちらも証明は省きますが、括弧の中身の式が原始再帰的ならば最小解関数も原始再帰的となります。
また、ここでは詳しくは説明しませんが、
これで準備が完了したので、いざ
により得られます。全てもう少し見やすく書くと、
となっています。
これについては、自然数
という形となっていることを思い出せばほとんど見たまんまですが、例えば
残り二つについても全く同様です。
非常に目が疲れるパートでしたがこれで手順③の目標は達成できたこととなります。
これは与えられた論理式のコードを手順③で得た論理式のコードに書き換えて、同じことを繰り返すだけです。手順①で論理式の形が
やっと真理値に触れることができます。終わりまでもう少しです。
まず、そもそも
ただこれは簡単で、意味付け関数
意味付け関数のコードは自然数
すなわち、
と書けます。
ただし、
しかし、
コーディング関数の定義より
により論理式のコード
ただし、
故に、
となることがわかります。
ただし、少し長いので
(
これで
真理値を格納する変数を
更新の方法は、元の
したがって、具体的な真理値の更新を表す式は
とすれば大丈夫です。
「
この手順が真偽判定問題の答えを得るための核となる手順です。
この手順で手順②において論理式の形を記録したリストを活用していくこととなります。
リストについて振り返ると、リストの中身は、形が
なので、論理式の分解が終わり記録をさかのぼって真理値を更新していく際には、この二種類の記録に対してそれぞれどう
リストの自然数がどちらの記録なのかは、その自然数が
まず、
今見ている論理式が
今、リストをさかのぼって
とすることで
続いて、
今見ている論理式が
今、論理式を分解していくときは左からみていくという方針を取っていました。
なので、リストをさかのぼって
もし、
この場合はラッキーで、
では、
こちらの場合は
なので、この場合には論理式
ちなみに、この時点でリストをさかのぼっている状態から、またリストに書き込む状態に戻るため今までの記録が邪魔になるように思えますが、別に削除とかはしなくても
これで真理値の更新の仕方がわかりました。
ところで、手順⑤で今までの
基本は書いてある通りで特に説明することもないのですが、今まで触れなかったカウンターについてここで説明します。
ここでのカウンターは、その時点でリストの第何項目を見ているのかを表すカウンターとなります。なので、リストに対する要素の取得や挿入の操作をする際には、カウンターにより位置を確認します。
カウンターは手順③で部分論理式を取得した直後に
カウンターについての説明はこの程度です。そして、カウンターを見て終了タイミングを把握し、その時の
今までのことをすべて一つのNプログラムにまとめて、証明終了となります。
ただ、今の私の知識では複雑なNプログラムを
今までのことをまとめただけなので特に説明することもありませんが、一応二つ目のノードに関しては
ちなみに今更ですが裸の定数は実際は
これで真偽判定問題を表す関数
本当は構成したNプログラムが要件を満たすのか確認しなければなりませんがどうかお許しを。。。
ここまで見てくださってありがとうございました。
とても見やすい記事とは言えないとは思いますが、楽しんでいただけたら何よりです。
もしこの記事で書いたような内容が面白いと感じたならば、理論計算機科学という分野を学んでみることをお勧めします。
ざっくりいうと論理学や、計算理論、圏論などを用いてプログラミング言語などについて考察していく分野だと僕は思っています。
本当にありがとうございました。お疲れさまでした。