@mame さんが PPL であみだくじの証明について問いかけていた。
| あみだくじの停止性を証明してください。 | | つまり、あみだくじの上端のどれかから、あみだくじのルールで辿っていったら、必ず下端のどれかにたどり着くこと(無限ループに陥ってしまわないこと)を証明してください。 | | 斜めの枝や、枝と枝の交差は認めるものとします。他の条件は、常識的な感じで。
無限ループにならないのは、以下のように説明できる。
一般に、決定的な状態遷移機械で、行き止まりに到達する状態遷移列は、同じ状態を含まない。 これは、同じ状態を 2つ以上含む状態遷移列はその部分を繰り返すため、行き止まりに到達しないからである。
あみだくじをたどる規則を考えると、あみだくじを逆にたどることは決定的に可能である。 なので、あみだくじを逆にたどる決定的な状態遷移機械を考える。 そうすると、そのような状態遷移機械が行き止まりに到達する (上端に到達する) のであれば、その状態遷移列は同じ状態を複数含むことはなく、無限ループにはならない。
というわけで、上端から (順方向に) たどれる経路は、無限ループを含まない。
せっかくなので、「決定的な状態遷移機械で、行き止まりに到達する状態遷移列は、同じ状態を含まない」ということを Coq で証明してみた。 (あみだくじがもとになっているので、いろいろとあみだくじっぽい名前を使っているが、面倒なのでそのまま)
From mathcomp Require Import all_ssreflect. Section amida_kuji. Variable segment : eqType. Definition path := seq segment. (* 次の状態を求める関数。関数なので決定的。next x の返値が Some y というのが x から y に遷移することを表現する。None は行き止まり。 *) Variable next : segment -> option segment. (* 状態列で、隣り合う要素 x, y が next x = Some y になっているという述語。空の場合はどちらでもいいはずだが、とりあえず valid としている。たぶんこっちのほうが証明の手間が少し減る。 *) Definition valid_path (p : path) : bool := match p with | nil => true | seg1 :: rest => all id (pairmap (fun x y => next x == Some y) seg1 rest) end. Lemma valid_path_cons (seg : segment) (p : path) : valid_path (seg :: p) -> valid_path p. Proof. case: p. by []. simpl. by move=> seg2 p /andP []. Qed. Lemma no_cycle seg0 p : valid_path (seg0 :: p) -> next (last seg0 p) = None -> uniq (seg0 :: p). Proof. elim: p seg0. by []. move=> seg1 p IH seg0 Hv Hnl. specialize (IH seg1 (valid_path_cons _ _ Hv) Hnl). rewrite cons_uniq IH andbT. apply/negP => Hin. move: Hv => /= /andP [/eqP Hns0 Hv]. move: Hin; rewrite in_cons => /orP [/eqP H01|]. subst seg0. case: p => [|seg2 p] in Hnl IH Hv Hns0. suff: None = Some seg1; first by []. by rewrite -Hnl -Hns0. have {Hv Hns0 Hnl} H12: seg1 = seg2. move: Hv => /= /andP [] Hns1 _. move: Hns1. by rewrite Hns0 => /eqP []. subst seg2. by rewrite /= mem_head in IH. move: (seg1) => dummy. move/nthP => /(_ dummy) [] i. change (i < size p) with (i.+1 < size (seg1 :: p)). change (nth dummy p i = seg0) with (nth dummy (seg1 :: p) i.+1 = seg0). rewrite leq_eqVlt => /orP [/eqP [Hi] Hns1|Hi2 Hsi1 {Hnl}]. suff: None = Some seg1; first by []. by rewrite -{}Hnl -{}Hns0 -[in RHS]Hns1 Hi nth_last. have {Hsi1 Hv} Hsi2 : nth dummy (seg1 :: p) i.+2 = seg1. rewrite /= ltnS in Hi2. move: Hv => /all_nthP. rewrite size_pairmap => /(_ true i.+1 Hi2). rewrite (nth_pairmap dummy); last by []. by rewrite Hsi1 Hns0 => /eqP [] /= <-. have Hi1 : 0 < size (seg1 :: p). by []. move: (nth_uniq dummy Hi1 Hi2 IH). rewrite Hsi2 /=. by rewrite eq_refl. Qed. End amida_kuji. About no_cycle. (* no_cycle : forall (segment : eqType) (next : segment -> option segment) (seg0 : segment) (p : seq segment), valid_path segment next (seg0 :: p) -> next (last seg0 p) = None -> uniq (seg0 :: p) *)
no_cycle という定理がサイクルを期待される性質を証明したもので、
no_cycle seg0 p : valid_path (seg0 :: p) -> next (last seg0 p) = None -> uniq (seg0 :: p)
seg0 と p で要素が 1個以上の状態遷移列を表現している。 最初の状態が seg0 で、以降の状態の列が p である。
その状態遷移列が valid である (隣り合う要素 x, y が next x = Some y となっている)、 そして、最後の要素の次の要素がない (next (last seg0 p) = None) ならば、 その状態列に同じ要素は含まれていない (uniq (seg0 :: p)) ということを示している。
なお、最初は、あみだくじを構築していく (横線をひとつずつ加えていく) ときにパスがどう変化するかぜんぶ書くという 地道な方法で挑戦したのだが、あまりに面倒なのであきらめてしまった。
証明の場合分けが不必要に多かったので減らしてみた。 (他にもいくらか調整した。)
From mathcomp Require Import all_ssreflect. Section amida_kuji. Variable segment : eqType. Definition path := seq segment. (* amida-kuji has determinstic function to obtain next segment *) Variable next : segment -> option segment. Definition valid_path (seg : segment) (p : path) : bool := all id (pairmap (fun x y => next x == Some y) seg p). Lemma valid_path_cons (seg0 seg1 : segment) (p : path) : valid_path seg0 (seg1 :: p) -> next seg0 = Some seg1 /\ valid_path seg1 p. Proof. by rewrite /valid_path /= => /andP [] => /eqP Hn Hv. Qed. Lemma next_nth (dummy seg x y : segment) (p : path) (i : nat): valid_path seg p -> i < size p -> next x = Some y -> nth dummy (seg :: p) i = x -> nth dummy (seg :: p) i.+1 = y. Proof. move=> Hv Hi Hxy Hni. move: Hv => /all_nthP. rewrite size_pairmap => /(_ true i Hi). rewrite (nth_pairmap dummy); last by []. by rewrite Hni Hxy => /eqP [] ->. Qed. Lemma no_cycle seg0 p : valid_path seg0 p -> next (last seg0 p) = None -> uniq (seg0 :: p). Proof. elim: p seg0. by []. move=> seg1 p IH seg0 Hv Hnl. move: (valid_path_cons seg0 seg1 p Hv) => [] Hns0 {}Hv. specialize (IH seg1 Hv Hnl). rewrite cons_uniq IH andbT. apply/negP => Hin. move: (seg1) => dummy. move/nthP: Hin => /(_ dummy) [] i /=. rewrite leq_eqVlt => /orP []. rewrite eqSS => /eqP Hi. rewrite Hi nth_last /= => Hl. rewrite /= Hl in Hnl. suff: None = Some seg1; first by []. by rewrite -Hns0 -Hnl. change (size p).+1 with (size (seg1 :: p)). move=> Hi2 Hsi. suff: 0 == i.+1; first by []. have Hi1 : 0 < size (seg1 :: p). by []. rewrite -(nth_uniq dummy Hi1 Hi2 IH). rewrite (next_nth dummy seg1 seg0 seg1 p i Hv Hi2 Hns0 Hsi) /=. by rewrite eq_refl. Qed. End amida_kuji.
[latest]