Isabelle Tutorial その15

5.17 Managing Large Proof

長くなりがちな証明のステップを短くする方法を見ていきます。

5.17.1 Tacticals, or Control Structures

長い証明の中には、規則性が現れることがあります。プラス記号(+)を使用して、ステップを繰り返すことができます。例えば、次の証明を見てみます。

lemma "[|P --> Q; Q --> R; R --> S; P|] ==> S"
apply(drule mp, assumption)
apply(drule mp, assumption)
apply(drule mp, assumption)
apply(assumption)
done

同じコマンドが三回続いています。これを、次のように証明できます。

lemma "[|P --> Q; Q --> R; R --> S; P|] ==> S"
by(drule mp, assumption)+

byを使用しているのは最後のassumptionが必要なためです。+は適用が可能なだけ繰り返し適用します。

縦棒(|)で二つのメソッドを区切ると、まず左のメソッドが適用され、適用できなかった場合に右のメソッドが適用されます。これは、繰り返しとともに使用すると便利です。次の証明では、assumptionが必要な場合と、x + 1 < 5からx < 5を推論するためにarithが必要な場合があります。そこで、次のように証明します。

lemma "[|Q --> R; P --> Q; x < 5 --> P; Suc x < 5|] ==> R"
by(drule mp, (assumption|arith))+

はてな(?)はメソッドを0または一回適用します。(m+)?とするとmを0回以上繰り返します。

5.17.2 Subgoal Numbering

サブゴールの数が非常に多くなる場合があります。Isabelleはデフォルトではサブゴールを10個までしか表示しません。コマンドprはこの数を変更します。

pr 2
 1. subgoal1
 2. subgoal2
A total of 6 subgoals...

多くのメソッドは最初のサブゴールに適用されます。他のサブゴールに注目したい場合、コマンドdeferまたはpreferを使用します。コマンドdeferは最初のサブゴールを後ろにまわします。コマンドpreferは指定した数字のサブゴールを先頭にまわします。これにより、難しいサブゴールを後回しにしたり、証明可能かどうか疑わしいサブゴールを先に確認したりすることができます。

Exercise 5.17.1

次の証明の中で?と+がどのように使われているか説明します。

lemma "[|P & Q --> R; P --> Q; P|] ==> R"
by(drule mp, (intro conjI)?, assumption)+

証明のステップを分けてみます。

lemma "[|P & Q --> R; P --> Q; P|] ==> R"
apply(drule mp, assumption)
apply(drule mp, intro conjI, assumption)
apply(assumption)
apply(assumption)
done

1ステップ目と2ステップ目は、intro conjIの部分が異なっています。そこで、intro conjIに?を付けて0または一回適用することにし、更に全体を+にすることで繰り返し適用します。残りのassumptionはbyが自動的に実行します。

5.18 Proving the Correctness of Euclid's Algorithm

gcdが最大公約数を返すことを証明してみます。gcdの定義と単純化規則は3.5.3で定義したものを使用します。

まず、次の補題を証明します。

lemma gcd_dvd_both: "(gcd m n dvd m) & (gcd m n dvd n)"
apply(induct_tac m n rule: gcd.induct)

帰納ケースにおいてお互いに参照し合うため、論理積の左右両方を同時に証明する必要があります。サブゴールは次のようになります。

 1. !!m n. (n ~= 0 ==> gcd n (m mod n) dvd n & gcd n (m mod n) dvd m mod n)
           ==> gcd m n dvd m & gcd m n dvd n

前提から、n = 0かどうかで場合分けすると良さそうです。

apply(case_tac "n = 0")
apply(simp_all)
 1. !!m n. [| gcd n (m mod n) dvd n & gcd n (m mod n) dvd m mod n; 0 < n |]
           ==> gcd n (m mod n) dvd m

このサブゴールを証明するために、次の定理を利用します。

[|?k dvd (?m mod ?n); ?k dvd ?n|] ==> ?k dvd ?m    (dvd_mod_imp_dvd)

この定理は導入規則としても使用できますが、ここでは破壊規則として使用してみます。サブゴールは次のステップで証明が完了します。

apply(blast dest: dvd_mod_imp_dvd)

論理積の左右それぞれを定理とするために分割します。

lemmas gcd_dvd1 [iff] = gcd_dvd_both [THEN conjunct1]
lemmas gcd_dvd2 [iff] = gcd_dvd_both [THEN conjunct2]

gcdが公約数であることは証明できましたので、それが最大であることを証明します。

lemma gcd_greatest [rule_format]: "k dvd m --> k dvd n --> k dvd gcd m n"

ゴールはHOLの含意を使用していますが、これは証明に帰納法を使用するためです。オプションrule_formatは、証明後に-->==>に変換します。また、全称限量子を取り除き一般的な推論規則の形に変形もします。これは、THENを使用してmpspecを必要なだけ適用したのと同じことです。今回の場合は次のように書くのと同じです。

lemma gcd_greatest [THEN mp, THEN mp]: "k dvd m --> k dvd n --> k dvd gcd m n"

証明の最初のステップは先ほどと同じように、帰納法の適用と場合分けです。サブゴールは次のようになります。

 1. !!m n. [| n ~= 0 ==> k dvd n --> k dvd m mod n --> k dvd gcd n (m mod n);
              n = 0 |]
           ==> k dvd m --> k dvd n --> k dvd gcd m n
 2. !!m n. [| n ~= 0 ==> k dvd n --> k dvd m mod n --> k dvd gcd n (m mod n);
              n ~= 0 |]
           ==> k dvd m --> k dvd n --> k dvd gcd m n

一つ目のサブゴールは単純化で証明できます。二つ目のサブゴールには、次の定理を使用します。

[|?f dvd ?m; ?f dvd ?n|] ==> ?f dvd ?m mod ?n    (dvd_mod)

よって次のコマンドで証明が完了します。

apply(simp_all add: dvd_mod)

最後に、これまでの定理を論理的同値でまとめます。

theorem gcd_greatest_iff [iff]: "(k dvd gcd m n) = (k dvd m & k dvd n)"
by(blast intro!: gcd_greatest intro: dvd_trans)

gcd_greatestを導入規則として使用しています。intro!は指定したルールがsafeルールであることを示しており、そのルールはより積極的に利用されます。!がない場合はunsafeルールとして使用されます。dvd_transは整除関係の推移律です。

[|?m dvd ?n; ?n dvd ?p|] ==> ?m dvd ?p    (dvd_trans)

さらにこの証明の中では、gcd_dvd1gcd_dvd2が暗黙のうちに使用されています。