言明を読む手順 — 「Lean で検査済み」を受け取ったら
「Lean で検査した」と渡された結果は、受け取る側が確かめられます。機械にやらせるものが四つ、読むものが四つです。前半は数分で済み、後半に本体があります。
「Lean で検査済み」を受け取ったとき
「Lean で検査した」と言われた結果を受け取ったとき、確かめられることは七つあります。前の四つは機械にやらせるもので、数分で終わります。後の三つは読むもので、こちらが本体です。
順序に意味があります。前半で「証明が本当に閉じているか」を見て、後半で「閉じている言明が、聞きたかった問題か」を見ます。前半が通っても後半が通らないことは普通に起こり、その型は 08 に四つ挙げました。
① 自分の手で検査し直す
配布物を展開して、自分の機械で検査を通します。Lean 検証一式には verify.sh が付いていて、機械の RAM と CPU の数から並列数を決め、lake exe cache get と全体のビルドを一手で走らせ、最後に公理の一覧と穴の数を要約します。展開してその一本を叩くだけです。
個別のファイルだけを見たいときは、他のファイルに依存しない一本もの(連結版)を lake env lean に直接渡すのが速いです。連結版は import Mathlib の一行しか読まないので、その一本だけで閉じていることが分かります。
$ time lake env lean PerfectFamily.lean 'PerfectFamily.cube3_bool' does not depend on any axioms 'PerfectFamily.dir_count' depends on axioms: [propext, Quot.sound] ...(以下 22 行、③ で見る `#print axioms` の出力) real 0m7.367s $ echo $? 0
見るのは終了コードだけではありません。Lean はエラーが無くても警告を出すことがあり、警告の中には後の項で問題になるもの(sorry の使用・非推奨の名前)が混ざります。エラーも警告も一行も無いことを確かめます。上の実行で出ているのは #print axioms の出力だけで、エラーと警告は 0 行、終了コードは 0 でした。
検査が通らない場合、まず環境を疑います。Lean と mathlib の版が配布物の lean-toolchain と lake-manifest.json で固定されているので、版が合っていれば結果は一致します。版のずれで落ちる形は 11 よくある罠の最後にまとめました。
② 穴の語を探す
Lean には「証明を後回しにする」書き方があり、それを使った定理も検査は通ります。ソース全体を機械的に探します。
grep -rn -E 'sorry|admit|native_decide|nativeDecide|ofReduceBool' .
| 語 | 何をするか | あったらどう読むか |
|---|---|---|
sorry | 証明を空欄にする。その宣言は sorryAx という公理に依拠する | その定理は証明されていない。依存する定理も全部同じ |
admit | sorry と同じ働きの別名 | 同じ |
native_decide | 判定をコンパイル済みのコードで走らせ、結果を信じる。Lean の核(kernel)は計算を検め直さない | 信頼の対象が Lean の核からコンパイラと実行環境まで広がる。公理 Lean.ofReduceBool が出る |
sorry を使った定理は、警告としても出ます。試しに一本置いて検査すると、こう出ます。
warning: declaration uses `sorry`
この警告は一行だけで、長い出力の中では見落とします。②の grep と④の公理の確認は、同じことを別の側から見るためにあります。
③ 公理の一覧を見る
#print axioms 定理名 は、その定理が最終的に何に依拠しているかを並べます。依存の木を全部たどった結果なので、途中のどこかに穴があれば必ず出ます。
#print axioms PerfectFamily.no_perfectZ_of_five
出力はこうなります。
'PerfectFamily.no_perfectZ_of_five' depends on axioms: [propext, Quot.sound]
読み方の基準は一つです。
| 出たもの | 意味 |
|---|---|
propext・Classical.choice・Quot.sound | Lean と mathlib の標準三公理。通常の数学がこの上に建っている。三つとも出る/一部だけ出る/何も出ないのは、証明が古典論理や商型を使ったかどうかの違いで、強さの問題ではない |
sorryAx | 穴がある。その定理は証明されていない |
Lean.ofReduceBool | native_decide を通った。核の外の計算を信じている |
| 上以外 | 独自の公理が足されている。何を足したかを読む |
公理の欄がきれいであることは、言明が意味のあることを何も保証しません。偽の仮定を置いた定理も、空集合の上の全称も、標準三公理の内側で通ります(08 §04 に実物)。この項は「証明が閉じているか」だけを見ています。
看板になる定理が複数あるときは、#print axioms を並べただけのファイルを一本作っておくと、検査のたびに全部の公理が出ます。配布物では ChkAll.lean がその役です。
④ 主定理の言明を読み、定義を全部たどる
ここから本体です。定理名を読まずに、言明を読みます。そして言明に出てくる定義を一つずつ開いて、紙の側の定義と突き合わせます。
例として、超立方体の辺の族についての定理を取ります。言明はこうです。
theorem no_perfectZ_of_five {d : ℕ} (hd : 5 ≤ d) (J : Fin d → W d → Bool) : ¬ PerfectZ J
読めるのは「d ≥ 5 なら PerfectZ を満たす J は無い」までです。PerfectZ が何かを知らなければ、この定理は何も言っていません。開きます。
/--
`J μ x`:`x` から `x + e_μ` へ向かう辺が族に入る。
`PerfectZ J`:どのプラケット `(μ, ν, x)`(`μ ≠ ν`)についても、その 4 辺
`(μ, x)`・`(μ, x+e_ν)`・`(ν, x)`・`(ν, x+e_μ)` のうちちょうど一本が `J` に入る。
-/
def PerfectZ (J : Fin d → W d → Bool) : Prop :=
∀ μ ν : Fin d, μ ≠ ν → ∀ x : W d,
(J μ x).toNat + (J μ (shift x ν)).toNat
+ (J ν x).toNat + (J ν (shift x μ)).toNat = 1
ここで突き合わせるのは「プラケットの 4 辺」が紙と同じ 4 辺かです。四つの項を一つずつ見ます。shift x ν はまだ開いていない定義なので、これも開きます。
def shift (x : W d) (i : Fin d) : W d := Function.update x i (x i + 1)
x の i 座標だけを 1 増やす関数です。したがって四つの項は、x から出る μ 方向の辺・x + e_ν から出る μ 方向の辺・x から出る ν 方向の辺・x + e_μ から出る ν 方向の辺で、これは平面 (μ, ν) の正方形の 4 辺です。紙の定義と一致します。
さらに W d の中身も見ます。
/-- `ℤ^d` の頂点。 -/ abbrev W (d : ℕ) : Type := Fin d → ℤ
無限格子です。周期を課していないので、定理は周期的でない族も含みます。これは言明の強さに直結する一点で、型を見ないと分かりません。同じ「完全な族」を周期格子 Fin d → ZMod L の上で述べた定理が別にあり、そちらとこちらは別の定理です。
この一続きが手順です。
定義をたどる手順
- 主定理の言明から始める。名前は読まない
- 言明に出てくる定義(
def・structure・abbrev)を列挙する - 一つずつ開く。開いた中に別の定義が出たら、それも開く。組み込みの型と mathlib の定義に当たるまで降りる
- 降りきったところで、紙の定義と一つずつ突き合わせる。個数・向き・境界(等号を含むか)・型(有限か無限か・周期があるか)の四つを見る
- 差が出たら、その差が結論を強くするか弱くするかを決める
この手順を踏むと、08 §02 の「同じ言葉の下に二つの定義が並ぶ」型が見つかります。定義の名前が同じでも、開いた中身が違えば別の定理です。
⑤ 仮定の一覧を作る
言明の : の左側に並んでいるものが仮定です。一つずつ、誰が満たすのかを決めます。
| 仮定の種類 | 例 | どう扱うか |
|---|---|---|
| 型の条件 | [Fintype S]・[NeZero L]・[DecidableEq E] | 対象がその形であることの宣言。追い出した仮定ではない |
| 対象の条件 | 2 ∣ L・5 ≤ d・‖U e‖ = 1 | 定理の適用範囲。範囲の外は何も言っていない |
| 追い出した数学 | h6 : ∀ e, (…).card = 6 | ここが効く。別の定理がこれを与えているか、与えていないかを確かめる |
三つ目が肝です。扱いにくい部分を仮定に追い出して残りを閉じるのは普通の進め方ですが、追い出した仮定を誰も満たしていないなら、結論はまだ使えません。08 §05 に、同じ結論を述べる三段の定理で仮定が消えていく実例があります。
見分け方は一つです。その仮定を満たす具体例が、同じ配布物の中に定理として在るか。在れば追い出しは解消されており、無ければ解消されていません。
⑥ 対照が置かれているか
decide で「成り立つ」を出した判定は、判定が何でも通しているだけかもしれません。これを排除するのが対照です。
| 置くもの | 何を排除するか | |
|---|---|---|
| 陽性対照 | 仮定を満たす具体的な値を入れた example | 言明が空虚に真であること |
| 陰性対照 | 通ってはいけないものを入れて、落ちることを確かめる theorem | 判定が粗くて何でも通していること |
陰性対照は、正解を一箇所だけ壊したものが入っているかを見ます。空のものと満杯のものが落ちるだけでは、判定が粗くても通ります。実物は 08 §04 の (b) にあります。
対照が一本も無い場合、それは結果が誤りだという意味ではありませんが、受け取る側が自分で対照を書いて確かめることになります。判定関数に極端な入力を入れて decide を回すだけなので、数行で済みます。
⑦ Lean・紙・計算の境界の表が付いているか
ひとまとまりの結果には、各行がどの等級かを書いた表が付いているべきです。表が無いときは、次の三つが混ざったまま渡されています。
Lean機械検査済み。定理名が添えてあることを見ます。名前が無い行は、確かめる手段がありません。 紙証明はあるが機械検査は未了。どこまでが紙かが書いてあることを見ます。 計算手元の計算で確かめた範囲。外に出す主張にしていないことを見ます。
表を見るときの要点は、Lean の行と紙の行の継ぎ目を探すことです。継ぎ目が結論の直前にあるなら、結論は紙の等級です。08 §06 に二つの例があり、どちらも継ぎ目が一箇所に見えるように書いてあります。
もう一つ、「列挙がそれで全部」の行が表に在るかを見ます。候補を並べて一つずつ潰す形の結果では、並べた候補が全部であることが別の等級になります。この行は抜けやすく、抜けると上界が丸ごと機械検査済みに見えます(10 §03)。
チェックリスト
機械にやらせる(数分)
- 自分の手で検査し直す。連結版なら
lake env lean 〈ファイル〉一本。終了コード 0 だけでなく、出力が空であることを見る(警告も無いこと) grep -rn -E 'sorry|admit|native_decide|nativeDecide|ofReduceBool' .が 0 件であること#print axioms 〈主定理〉がpropext・Classical.choice・Quot.soundの内側であること。sorryAxとLean.ofReduceBoolが出ないこと- Lean と mathlib の版が
lean-toolchain・lake-manifest.jsonと一致していること
読む(ここが本体)
- 主定理の言明に出てくる定義を、組み込みと mathlib に当たるまで全部たどる。個数・向き・境界・型の四つを紙の定義と突き合わせる
- 仮定を一覧にする。型の条件・適用範囲・追い出した数学の三つに分け、三つ目を誰が満たしているかを確かめる
- 陽性対照と陰性対照が在るか。陰性対照は「正解を一箇所だけ壊したもの」が入っているか
- Lean・紙・計算の境界の表が在るか。Lean の行に定理名が添えてあるか。「列挙がそれで全部」の行が抜けていないか
1〜4 が通り 5〜8 のどこかが通らない、というのが最も多い形です。そのとき結果は誤りではなく、言明が思っていたものと違います。違いの型は 08 の四つに当てはまります。
次は 10 Lean では現実的に厳しいもの——受け取る側が「なぜこの行は紙のままなのか」を読むための背景です。用語は 12 用語集。