Lean 4 で論理パズルを解くための 5 つのステップ
Lean 4 で論理パズルを解くための 5 つのステップ
Section titled “Lean 4 で論理パズルを解くための 5 つのステップ”1. ドメインのモデル化(型の設計)
Section titled “1. ドメインのモデル化(型の設計)”まず、世界に存在する「物」と「その属性」を厳密に定義します。
- 有限の属性:
ColorやSizeはinductiveで定義し、DecidableEqを派生(derive)させます。これが無いと、後で「同じ色か?」を計算できません。 - 座標系: 4x4 グリッドなら
Fin 4 × Fin 4を使います。これは Mathlib のFintype(有限集合)の仕組みに乗るため、全探索が可能になります。
2. 述語の定義(言葉の定義)
Section titled “2. 述語の定義(言葉の定義)”論理式に登場する概念(adj, same_row, above など)を関数として定義します。
abbrevの活用:defよりもabbrevを使うのがコツです。abbrevは証明中に自動で展開されるため、型の不一致エラー(Type mismatch)を劇的に減らせます。@[simp]属性: 定義に@[simp]をつけると、Lean の簡約化タクティクがその中身を自由に参照できるようになり、証明がスムーズになります。
3. 「判定可能性(Decidability)」の確保
Section titled “3. 「判定可能性(Decidability)」の確保”ここが最も重要なテクニカルポイントです。
- 有限集合のインポート:
Mathlib.Data.Fintype.Prodをインポートすることで、Lean は「$16 \times 16$ マスの全探索なら有限時間で終わる」と確信し、decideタクティクが使えるようになります。 - 計算可能な定義: 定義の中で
Propを返す際、その中身が「等号=」や「比較<」などの計算可能な要素で構成されている必要があります。
4. グリッドの設計(パズル解法)
Section titled “4. グリッドの設計(パズル解法)”論理式をすべて満たす配置を考えるステップです。今回のような複雑な条件では、以下の「アンカー(固定条件)」から埋めていくのがセオリーです。
- 特定の存在条件を配置: 「青の下に緑がある(条件10)」や「隣がすべて異サイズ(条件7)」など、場所を限定しやすいものから配置します。
- 全称条件をチェック: 「すべての緑の行には青がある(条件5)」などを確認し、矛盾があれば修正します。
- 最後に色を塗る: 「すべてのドットは隣に同色を持つ(条件8)」を満たすように、島を作るイメージで色を調整します。
5. Mathlib スタイルの証明
Section titled “5. Mathlib スタイルの証明”Lean のお作法に則った証明を書きます。
decideタクティク: 有限な探索で済むものはすべてこれで解決します。Prod.extの使用: 座標 $(r, c)$ の一致を証明する際は、extよりもProd.extを使う方が型エラーに強く、Mathlib スタイルに合致しています。- 存在記号
∃のネスト:
⟨証拠1, ⟨証拠2, 証明⟩⟩のように、匿名コンストラクタ(角括弧)を使って構造化すると、コードの可読性が上がります。
まとめ:作成のコツ
Section titled “まとめ:作成のコツ”| 項目 | 推奨されるやり方 | 理由 |
|---|---|---|
| 定義 | abbrev を使う | 証明時の展開がスムーズになる |
| 比較 | p1.1 = p2.1 など直接書く | decide が計算しやすくなる |
| 存在証明 | ⟨ ⟩ で構造化する | 複雑な論理構造を整理できる |
| 等号証明 | Prod.ext を使う | ペア(行, 列)の比較に最適 |
import Mathlib.Data.Fintype.Prodimport Mathlib.Data.Fin.Basicimport Mathlib.Tactic
/-!# 4x4 Dots World Puzzle (Fixed Version)定義の展開と型推論の問題を修正したバージョンです。-/
inductive Color | blue | green deriving DecidableEq, Repr
inductive Size | small | large deriving DecidableEq, Repr
structure Dot where color : Color size : Size deriving DecidableEq, Repr
abbrev Pos := Fin 4 × Fin 4def World := Pos → Dot
namespace World
variable (w : World)
-- 述語を定義 (simp属性を付与してタクティクが中身を見やすくします)@[simp] abbrev blue (p : Pos) : Prop := (w p).color = Color.blue@[simp] abbrev green (p : Pos) : Prop := (w p).color = Color.green@[simp] abbrev small (p : Pos) : Prop := (w p).size = Size.small@[simp] abbrev large (p : Pos) : Prop := (w p).size = Size.large
@[simp] abbrev same_color (p1 p2 : Pos) : Prop := (w p1).color = (w p2).color@[simp] abbrev same_size (p1 p2 : Pos) : Prop := (w p1).size = (w p2).size
@[simp] abbrev same_row (p1 p2 : Pos) : Prop := p1.1 = p2.1@[simp] abbrev same_column (p1 p2 : Pos) : Prop := p1.2 = p2.2
@[simp] abbrev adj (p1 p2 : Pos) : Prop := (p1.1 = p2.1 ∧ (p1.2.val + 1 = p2.2.val ∨ p2.2.val + 1 = p1.2.val)) ∨ (p1.2 = p2.2 ∧ (p1.1.val + 1 = p2.1.val ∨ p2.1.val + 1 = p1.1.val))
@[simp] abbrev right_of (p1 p2 : Pos) : Prop := p1.2 > p2.2@[simp] abbrev left_of (p1 p2 : Pos) : Prop := p1.2 < p2.2@[simp] abbrev above (p1 p2 : Pos) : Prop := p1.1 < p2.1 ∧ p1.2 = p2.2
/-! ## 3. 世界の具体的な構築 -/
def myWorld : World | (0, 0) => ⟨.blue, .large⟩ | (0, 1) => ⟨.blue, .small⟩ | (0, 2) => ⟨.green, .large⟩ | (0, 3) => ⟨.green, .small⟩ | (1, 0) => ⟨.blue, .small⟩ | (1, 1) => ⟨.blue, .large⟩ | (1, 2) => ⟨.blue, .small⟩ | (1, 3) => ⟨.green, .small⟩ | (2, 0) => ⟨.blue, .large⟩ | (2, 1) => ⟨.blue, .small⟩ | (2, 2) => ⟨.green, .small⟩ | (2, 3) => ⟨.green, .small⟩ | (3, 0) => ⟨.blue, .small⟩ | (3, 1) => ⟨.blue, .small⟩ | (3, 2) => ⟨.blue, .small⟩ | (3, 3) => ⟨.blue, .small⟩
/-! ## 4. 定理の証明 -/
-- 1. ∀x(green(x)∨blue(x))theorem cond1 : ∀ x, green myWorld x ∨ blue myWorld x := by decide
-- 2. ∃x,y(adj(x,y)∧green(x)∧green(y))theorem cond2 : ∃ x y, adj x y ∧ green myWorld x ∧ green myWorld y := ⟨(0, 2), ⟨(0, 3), by decide⟩⟩
-- 3. ∃x(∃z right-of(z,x)∧∀y(left-of(x,y)→blue(y)∨small(y)))theorem cond3 : ∃ x, (∃ z, right_of z x) ∧ (∀ y, left_of x y → blue myWorld y ∨ small myWorld y) := ⟨(0, 2), ⟨⟨(0, 3), by decide⟩, by decide⟩⟩
-- 4. ∀x(large(x)→∃y(small(y)∧adj(x,y)))theorem cond4 : ∀ x, large myWorld x → ∃ y, small myWorld y ∧ adj x y := by decide
-- 5. ∀x(green(x)→∃y(same-row(x,y)∧blue(y)))theorem cond5 : ∀ x, green myWorld x → ∃ y, same_row x y ∧ blue myWorld y := by decide
-- 6. 同じ行かつ同じ列ならば、それは同一のドットである (修正ポイント)theorem cond6 : ∀ x y : Pos, same_row x y ∧ same_column x y → x = y := by intro x y h exact Prod.ext h.1 h.2
-- 7. ∃x∀y(adj(x,y)→¬same-size(x,y))theorem cond7 : ∃ x, ∀ y, adj x y → ¬same_size myWorld x y := ⟨(1, 1), by decide⟩
-- 8. ∀x∃y(adj(x,y)∧same-color(x,y))theorem cond8 : ∀ x, ∃ y, adj x y ∧ same_color myWorld x y := by decide
-- 9. ∃y∀x(adj(x,y)→same-color(x,y))theorem cond9 : ∃ y, ∀ x, adj x y → same_color myWorld x y := ⟨(3, 1), by decide⟩
-- 10. ∃x(blue(x)∧∃y(green(y)∧above(x,y)))theorem cond10 : ∃ x, blue myWorld x ∧ ∃ y, green myWorld y ∧ above x y := ⟨(1, 2), ⟨by decide, ⟨(2, 2), by decide⟩⟩⟩
end Worldimport Mathlib.Tactic
-- 1. 独自の名前で定義し、自動展開されるよう abbrev を使いますabbrev PropRefl (α : Type) (R : α → α → Prop) := ∀ x : α, R x xabbrev PropSymm (α : Type) (R : α → α → Prop) := ∀ x y : α, R x y → R y xabbrev PropTrans (α : Type) (R : α → α → Prop) := ∀ x y z : α, R x y → R y z → R x z
/-! Case 1: 反射・対称だが、推移的ではない -/namespace Case1 -- abbrev にすることで、Lean が α = Fin 3 と即座に認識できるようになります abbrev α := Fin 3 def R : α → α → Prop := fun x y => x = y ∨ (x = 0 ∧ y = 1) ∨ (x = 1 ∧ y = 0) ∨ (x = 1 ∧ y = 2) ∨ (x = 2 ∧ y = 1)
theorem independent : PropRefl α R ∧ PropSymm α R ∧ ¬ PropTrans α R := by refine ⟨?refl, ?symm, ?trans⟩ case refl => intro x; left; rfl case symm => intro x y h; dsimp [R] at *; aesop case trans => intro h have h01 : R 0 1 := by dsimp [R]; right; left; simp have h12 : R 1 2 := by dsimp [R]; right; right; right; left; simp have h02 : R 0 2 := h 0 1 2 h01 h12 -- R を展開して具体的な数値の式にする simp [R] at h02end Case1
/-! Case 2: 反射・推移だが、対称ではない -/namespace Case2 abbrev α := Fin 2 def R : α → α → Prop := fun x y => (x : ℕ) ≤ (y : ℕ)
theorem independent : PropRefl α R ∧ PropTrans α R ∧ ¬ PropSymm α R := by refine ⟨?refl, ?trans, ?symm⟩ case refl => intro x -- R を展開して (x : ℕ) ≤ (x : ℕ) に変える dsimp [R] -- 自然数の反射律を適用 exact Nat.le_refl x case trans => intro x y z h1 h2 -- 仮定の中の R も展開しておくと確実です dsimp [R] at * exact Nat.le_trans h1 h2 case symm => intro h -- R 0 1 を具体的に証明(0 ≤ 1) have h01 : R 0 1 := by dsimp [R]; norm_num -- 対称律の仮定から R 1 0 (1 ≤ 0) を導く have h10 : R 1 0 := h 0 1 h01 -- 矛盾を粉砕 dsimp [R] at h10 norm_num at h10end Case2
/-! Case 3: 対称・推移だが、反射ではない -/namespace Case3 abbrev α := Fin 1 def R : α → α → Prop := fun _ _ => False
theorem independent : PropSymm α R ∧ PropTrans α R ∧ ¬ PropRefl α R := by refine ⟨?symm, ?trans, ?refl⟩ case symm => intro x y h; exact h.elim case trans => intro x y z h1 h2; exact h1.elim case refl => intro h exact h 0end Case3Lean 4 で論理パズルを解くための 5 つのステップ(強化版)
Section titled “Lean 4 で論理パズルを解くための 5 つのステップ(強化版)”1. ドメインのモデル化(型の設計)
Section titled “1. ドメインのモデル化(型の設計)”まず、世界に存在する「物」と「その属性」を厳密に定義します。
- 有限の属性:
ColorやSizeはinductiveで定義し、DecidableEqを派生(derive)させます。これが無いと、後で「同じ色か?」を計算できません。 - 座標系: 4x4 グリッドなら
Fin 4 × Fin 4を使います。 - エイリアスの作成:
def α := Fin 3ではなく、abbrev α := Fin 3を使用します。これにより、型クラスのインスタンス(数値リテラルの0や1など)がαに自動的に引き継がれ、OfNatエラーを防げます。
2. 述語の定義(言葉の定義)
Section titled “2. 述語の定義(言葉の定義)”論理式に登場する概念(adj, same_row, above など)を関数として定義します。
abbrevの徹底:defよりもabbrevを優先します。defは「中身を隠す」性質があるため、証明中にわざわざunfoldする手間が発生しますが、abbrevなら Lean が自動で中身を覗きに行ってくれます。@[simp]属性: 定義に@[simp]をつけると、simpタクティクがその定義を自由に展開できるようになり、複雑な論理式の簡約化が劇的に速くなります。
3. 「判定可能性(Decidability)」の確保
Section titled “3. 「判定可能性(Decidability)」の確保”コンピュータに「計算」させるための重要なステップです。
- 有限集合の活用:
Mathlib.Data.Fintype.Prodをインポートすることで、Lean は「すべての組み合わせを試せば終わる」と確信し、decideタクティクが有効になります。 - 計算可能な述語: 関係 $R$ を定義する際、中身が等号
=や不等号≤など、具体的な数値計算に基づいている必要があります。
4. グリッドの設計(パズル解法)
Section titled “4. グリッドの設計(パズル解法)”論理式をすべて満たす配置を構築するステップです。
- アンカー(固定条件)の配置: 場所が限定されやすい条件(「青の下に緑がある」など)から埋めます。
- 具体的反例の構築: 今回の「独立性の証明」のように、「$A$ と $B$ は満たすが $C$ は満たさない」具体的なモデルを作る際は、最小の要素(
Fin 1,Fin 2など)から試すと矛盾が見つかりやすくなります。
5. Mathlib スタイルの証明
Section titled “5. Mathlib スタイルの証明”Lean のお作法に則り、堅牢な証明を書きます。
decideとnative_decide: 全探索で済むものはdecideに任せます。norm_numによる矛盾の解消:0 = 2や1 ≤ 0といった数値レベルの矛盾が仮定に現れた場合、norm_num at hを実行することで、仮定をFalseに変えて証明を終了させることができます。- 存在記号
∃の構造化:
⟨証拠1, ⟨証拠2, 証明⟩⟩のように、匿名コンストラクタ(角括弧)を使ってネストを表現します。
まとめ:作成のコツ
Section titled “まとめ:作成のコツ”| 項目 | 推奨されるやり方 | 理由 |
|---|---|---|
| 型定義 | abbrev を使う | 数値リテラルや型クラスのエラーを回避できる |
| 定義展開 | dsimp [R] や unfold | Lean に定義の中身(≤ や ∨)を見せるため |
| 矛盾解消 | norm_num at h | 0 = 2 などの偽の等式を False に確定させる |
| 存在証明 | ⟨ ⟩ で構造化する | 複雑な論理構造を視覚的に整理できる |
| 等号証明 | Prod.ext を使う | 座標ペア(行, 列)を個別に比較するのに最適 |
import Mathlib.Tactic
/-! ### 1. 属性の定義 -/inductive Color | Red | Green | Ivory | Yellow | Blue deriving DecidableEq, Fintype, Reprinductive Nation | English | Spaniard | Ukrainian | Norwegian | Japanese deriving DecidableEq, Fintype, Reprinductive Drink | Coffee | Tea | Milk | OrangeJuice | Water deriving DecidableEq, Fintype, Reprinductive Smoke | OldGold | Kools | Chesterfields | LuckyStrike | Parliaments deriving DecidableEq, Fintype, Reprinductive Pet | Dog | Snails | Fox | Horse | Zebra deriving DecidableEq, Fintype, Repr
open Color Nation Drink Smoke Pet
/-! ### 2. 世界の構造 -/structure ZebraWorld where color : Color → Fin 5 nat : Nation → Fin 5 drink : Drink → Fin 5 smoke : Smoke → Fin 5 pet : Pet → Fin 5deriving DecidableEq
/-! ### 3. スマートな制約述語 -/def sameHouse {α β : Type} (f : α → Fin 5) (g : β → Fin 5) (a : α) (b : β) : Prop := f a = g bdef rightOf {α β : Type} (f : α → Fin 5) (g : β → Fin 5) (a : α) (b : β) : Prop := (f a : ℕ) = (g b : ℕ) + 1def neighborOf {α β : Type} (f : α → Fin 5) (g : β → Fin 5) (a : α) (b : β) : Prop := (f a : ℕ) + 1 = (g b : ℕ) ∨ (g b : ℕ) + 1 = (f a : ℕ)
/-! ### 4. 制約の定義 -/def IsSolution (w : ZebraWorld) : Prop := sameHouse w.nat w.color English Red ∧ sameHouse w.nat w.pet Spaniard Dog ∧ sameHouse w.color w.drink Green Coffee ∧ sameHouse w.nat w.drink Ukrainian Tea ∧ rightOf w.color w.color Green Ivory ∧ sameHouse w.smoke w.pet OldGold Snails ∧ sameHouse w.color w.smoke Yellow Kools ∧ (w.drink Milk = 2) ∧ (w.nat Norwegian = 0) ∧ neighborOf w.smoke w.pet Chesterfields Fox ∧ neighborOf w.smoke w.pet Kools Horse ∧ sameHouse w.smoke w.drink LuckyStrike OrangeJuice ∧ sameHouse w.nat w.smoke Japanese Parliaments ∧ neighborOf w.nat w.color Norwegian Blue
-- 計算可能にするためのインスタンスinstance (w : ZebraWorld) : Decidable (IsSolution w) := by dsimp [IsSolution, sameHouse, rightOf, neighborOf] infer_instance
/-! ### 5. 正解の構成 -/def solution : ZebraWorld := { color := fun | Red => 2 | Green => 4 | Ivory => 3 | Yellow => 0 | Blue => 1 nat := fun | English => 2 | Spaniard => 3 | Ukrainian => 1 | Norwegian => 0 | Japanese => 4 drink := fun | Coffee => 4 | Tea => 1 | Milk => 2 | OrangeJuice => 3 | Water => 0 smoke := fun | OldGold => 2 | Kools => 0 | Chesterfields => 1 | LuckyStrike => 3 | Parliaments => 4 pet := fun | Dog => 3 | Snails => 2 | Fox => 0 | Horse => 1 | Zebra => 4}
/-! ### 6. 検証 -/theorem zebra_is_solved : IsSolution solution := by -- ここで decide を実行します。sorry がなければ True に簡約されます。 decide
-- 1. 命題を独立した def として定義する(デバッグしやすくなります)def UniqueSolutionProp : Prop := ∀ (w : ZebraWorld), IsSolution w → w = solution
-- これが通る=少なくとも solution という解が存在する=矛盾がないtheorem consistency_check : ∃ w, IsSolution w := ⟨solution, zebra_is_solved⟩
#eval solution.nat Japanese -- 4#eval solution.pet Zebra -- 41. モデリング:世界の構造化
Section titled “1. モデリング:世界の構造化”CSP の基本要素である「変数」「ドメイン」「制約」を Lean の型にマッピングします。
- ドメインの定義 (
inductive): 変数が取りうる値を列挙型で定義します。DecidableEq(比較可能)とFintype(有限集合)を派生させることが、後の自動探索の鍵となります。 - 変数のマッピング (
structure): 「家の中に属性がある」と考えるのではなく、**「属性から位置(Fin n)への関数」**としてモデル化するのが Lean 流です。これにより、境界条件(0番目や4番目の家など)の処理が型レベルで安全になります。
2. 制約の記述:述語による抽象化
Section titled “2. 制約の記述:述語による抽象化”自然言語の制約を、Lean の Prop(命題)に変換します。
- 高階述語の活用 (
abbrev):sameHouse(同じ位置),neighborOf(隣接),rightOf(順序)といった汎用的な述語を先に定義します。 abbrevの重要性:defではなくabbrevを使うことで、Lean の計算機(decide)が定義の境界を飛び越えて中身を直接評価できるようになり、計算効率が劇的に向上します。
3. 計算可能性の確保:Decidable インスタンス
Section titled “3. 計算可能性の確保:Decidable インスタンス”Lean は「すべての命題が計算可能である」とは見なしません。パズルを解かせるには、その制約が判定可能であることを示す必要があります。
- インスタンスの合成:
instance : Decidable (IsSolution w)を定義し、dsimpで制約を展開してからinfer_instanceで判定器を自動生成させます。 native_decideの利用: 探索空間が膨大な場合、Lean の標準の計算機では力不足です。native_decideを使って C++ 並みの速度で全探索を実行させます。
4. 検証の三段階
Section titled “4. 検証の三段階”パズルが「解けた」と言えるためには、以下の3つを確認します。
| 検証フェーズ | 数学的な意味 | Lean での手法 |
|---|---|---|
| 存在証明 (Consistency) | 少なくとも1つは解がある | 具体的な solution を作り decide する |
| 一意性証明 (Uniqueness) | 解がそれ以外に存在しない | ∀ w, IsSolution w → w = solution を証明 |
| 独立性証明 (Independence) | 条件に無駄がない | 特定の条件を抜いて解が複数出ることを確認 |
5. Lean 4 CSP 攻略チェックリスト
Section titled “5. Lean 4 CSP 攻略チェックリスト”新しいパズルや問題に取り組む際は、この順序で設計します。
- Types: 属性はすべて
inductiveで定義したか? - Bijective: 各属性の割り当てが 1 対 1 であることを考慮したか?
- Predicates: 問題文を
same,neighbor等の共通部品で書いたか? - Deriving:
DecidableEq,Fintypeは付与したか? - Check:
native_decideが通る「閉じられた命題」になっているか?