如何证明Coq中的两个Fibonacci实现是相等的?

时间:2016-11-13 07:15:51

标签: functional-programming fibonacci coq

我有两个Fibonacci实现,如下所示,我想要证明它们在功能上是等价的。

我已经证明了关于自然数的属性,但是这个练习需要另一种我无法弄清楚的方法。

我正在使用的教科书介绍了Coq的以下语法,因此应该可以使用这种表示法证明相等:

<definition> ::= <keyword> <identifier> : <statement> <proof>

<keyword> ::= Proposition | Lemma | Theorem | Corollary

<statement> ::= {<quantifier>,}* <expression>

<quantifier> ::= forall {<identifier>}+ : <type>
               | forall {({<identifier>}+ : <type>)}+

<proof> ::= Proof. {<tactic>.}* <end-of-proof>

<end-of-proof> ::= Qed. | Admitted. | Abort.

以下是两个实现:

Fixpoint fib_v1 (n : nat) : nat :=
  match n with
  | 0 => O
  | S n' => match n' with
            | O => 1
            | S n'' => (fib_v1 n') + (fib_v1 n'')
            end
  end.

Fixpoint visit_fib_v2 (n a1 a2 : nat) : nat :=
  match n with
  | 0 => a1
  | S n' => visit_fib_v2 n' a2 (a1 + a2)
  end.

很明显,这些函数为基本案例n = 0计算相同的值,但是归纳案例要难得多吗?

我已经尝试过证明以下引理,但我陷入了归纳的情况:

Lemma about_visit_fib_v2 :
  forall i j : nat,
    visit_fib_v2 i (fib_v1 (S j)) ((fib_v1 j) + (fib_v1 (S j))) = (fib_v1 (add_v1 i (S j))).
Proof.
  induction i as [| i' IHi'].

  intro j.
  rewrite -> (unfold_visit_fib_v2_0 (fib_v1 (S j)) ((fib_v1 j) + (fib_v1 (S j)))).
  rewrite -> (add_v1_0_n (S j)).
  reflexivity.

  intro j.
  rewrite -> (unfold_visit_fib_v2_S i' (fib_v1 (S j)) ((fib_v1 j) + (fib_v1 (S j)))).

 Admitted.

其中:

Fixpoint add_v1 (i j : nat) : nat :=
  match i with
  | O => j
  | S i' => S (add_v1 i' j)
  end.

6 个答案:

答案 0 :(得分:4)

@ larsr&#39; s answer启发了这个替代答案。

首先,让我们定义fib_v2

Require Import Coq.Arith.Arith. 

Definition fib_v2 n := visit_fib_v2 n 0 1.

然后,我们需要一个引理,这与@ larsr的答案中的fib_v2_lemma相同。我将此处包括在内以保持一致性并显示替代证据。

Lemma visit_fib_v2_main_property n: forall a0 a1,
  visit_fib_v2 (S (S n)) a0 a1 =
  visit_fib_v2 (S n) a0 a1 + visit_fib_v2 n a0 a1.
Proof.
  induction n; intros a0 a1; auto with arith.
  change (visit_fib_v2 (S (S (S n))) a0 a1) with
         (visit_fib_v2 (S (S n)) a1 (a0 + a1)).
  apply IHn.
Qed.

根据larsr的评论中的建议,visit_fib_v2_main_property引理也可以通过以下令人印象深刻的单行证明:

now induction n; firstorder.

由于Fibonacci系列中数字的性质,定义替代归纳原理非常方便:

Lemma pair_induction (P : nat -> Prop) :
  P 0 ->
  P 1 ->
  (forall n, P n -> P (S n) -> P (S (S n))) ->
  forall n, P n.
Proof.
  intros H0 H1 Hstep n.
  enough (P n /\ P (S n)) by tauto.
  induction n; intuition.
Qed.

pair_induction原则基本上说,如果我们可以为P0证明某些属性1,并且对于每个自然数k > 1,我们可以证明P kP (k - 1)P (k - 2)成立的假设下成立,然后我们可以证明forall n, P n

使用我们的自定义归纳原理,我们得到如下证据:

Lemma fib_v1_eq_fib2 n :
  fib_v1 n = fib_v2 n.
Proof.
  induction n using pair_induction.
  - reflexivity.
  - reflexivity.
  - unfold fib_v2.
    rewrite visit_fib_v2_main_property.
    simpl; auto.
Qed.

答案 1 :(得分:3)

警告提示:在接下来的内容中,我将尝试展示这种证据的主要概念,因此我不会坚持使用Coq的某些子集而我不会手动算术。相反,我会使用一些证明自动化,即。 ring战术。Require Import Arith. (* for `ring` tactic *) Lemma fib_v1_eq_fib2_generalized n : forall a0 a1, visit_fib_v2 (S n) a0 a1 = a0 * fib_v1 n + a1 * fib_v1 (S n). Proof. induction n; intros a0 a1. - simpl; ring. - change (visit_fib_v2 (S (S n)) a0 a1) with (visit_fib_v2 (S n) a1 (a0 + a1)). rewrite IHn. simpl; ring. Qed. 。但是,您可以随意提出其他问题,这样您就可以将证明转换为适合您目的的证据。

我认为从一些概括开始更容易:

ring

如果使用rewrite并不适合您的需求,您可以使用Arith模块的词条执行多个Definition fib_v2 n := visit_fib_v2 n 0 1. Lemma fib_v1_eq_fib2 n : fib_v1 n = fib_v2 n. Proof. destruct n. - reflexivity. - unfold fib_v2. rewrite fib_v1_eq_fib2_generalized. ring. Qed. 步骤。

现在,让我们实现目标:

y

答案 2 :(得分:3)

安东的证据非常漂亮,比我的好,但无论如何它可能都很有用。

我没有提出泛化引理,而是加强了归纳假设。

说原始目标是Q n。然后我用

改变了目标
cut (Q n /\ Q (S n))

来自

 Q n

 Q n /\ Q (S n)

这个新目标通常意味着最初的目标,但随之而来的是归纳假设变得更强,因此可以重写更多。

IHn : Q n /\ Q (S n)
=========================
Q (S n) /\ Q (S (S n))

这个想法在软件基础中解释,其中一个证明了偶数。

因为这个提议通常很长,所以我制定了一个Ltac策略来命名这个漫长而混乱的术语。

Ltac nameit Q :=
  match goal with [ _:_ |- ?P ?n] => let X := fresh Q in remember P as X end.

Require Import Ring Arith.

(顺便说一下,我将vistit_fib_v2重命名为fib_v2。) 我需要一个关于fib_v2的一步的引理。

Lemma fib_v2_lemma: forall n a b, fib_v2 (S (S n)) a b = fib_v2 (S n) a b + fib_v2 n a b.
  intro n.
  pattern n.
  nameit Q.
  cut (Q n /\ Q (S n)).
  tauto.                             (* Q n /\ Q (S n) -> Q n *)

  induction n.
  split; subst; simpl; intros; ring. (* Q 0 /\ Q 1  *)
  split; try tauto.                  (* Q (S n)     *)

  subst Q.                           (* Q (S (S n)) *)
  destruct IHn as [H1 H2].
  assert (L1: forall n a b, fib_v2 (S n) a b = fib_v2 n b (a+b)) by reflexivity.
  congruence.
Qed.

congruence策略处理一系列A = B假设和重写后的目标。

证明定理非常相似。

Theorem fib_v1_fib_v2 : forall n, fib_v1 n = fib_v2 n 0 1.
  intro n.
  pattern n.
  nameit Q.
  cut (Q n /\ Q (S n)).
  tauto.                             (* Q n /\ Q (S n) -> Q n *)

  induction n.
  split; subst; simpl; intros; ring. (* Q 0 /\ Q 1  *)
  split; try tauto.                  (* Q (S n)     *)

  subst Q.                           (* Q (S (S n)) *)
  destruct IHn as [H1 H2].
  assert (fib_v1 (S (S n)) = fib_v1 (S n) + fib_v1 n) by reflexivity.
  assert (fib_v2 (S (S n)) 0 1 = fib_v2 (S n) 0 1 + fib_v2 n 0 1) by
      (pose fib_v2_lemma; congruence).
  congruence.
Qed.  

所有锅炉板代码都可以采用策略,但我不想对Ltac发疯,因为那不是问题所在。

答案 3 :(得分:2)

此证明脚本仅显示证明结构。解释证据的概念可能很有用。

Require Import Ring Arith Psatz.  (* Psatz required by firstorder *)

Theorem fibfib: forall n, fib_v2 n 0 1 = fib_v1 n.
Proof with (intros; simpl in *; ring || firstorder).
  assert (H: forall n a0 a1, fib_v2 (S n) a0 a1 = a0 * (fib_v1 n) + a1 * (fib_v1 (S n))).
  { induction n... rewrite IHn; destruct n... }
  destruct n; try rewrite H...
Qed.

答案 4 :(得分:2)

有一个非常强大的库 - math-comp用Ssreflect正式证明语言编写,而该语言又基于Coq。在这个答案中,我提出了一个使用其设施的版本。这只是this开发的一个简化部分。所有功劳都归原作者所有。

让我们做一些导入和两个函数的定义,math-comp(ssreflect)样式:

From mathcomp
Require Import ssreflect ssrnat ssrfun eqtype ssrbool.

Fixpoint fib_rec (n : nat) {struct n} : nat :=
  if n is n1.+1 then
    if n1 is n2.+1 then fib_rec n1 + fib_rec n2
    else 1
  else 0.

Fixpoint fib_iter (a b n : nat) {struct n} : nat :=
  if n is n1.+1 then
    if n1 is n2.+1
      then fib_iter b (b + a) n1
      else b
    else a.

表达斐波那契数字基本性质的辅助引理:

Lemma fib_iter_property : forall n a b,
  fib_iter a b n.+2 = fib_iter a b n.+1 + fib_iter a b n.
Proof.
case=>//; elim => [//|n IHn] a b; apply: IHn.
Qed.

现在,让我们来解决两个实现的等价问题。 这里的主要思想,即将以下证明与其他证据区分开来,截至本文撰写时,我们已经表现出来 类似complete induction,使用elim: n {-2}n (leqnn n)。这给了我们以下(强)归纳假设:

IHn : forall n0 : nat, n0 <= n -> fib_rec n0 = fib_iter 0 1 n0

这是主要的引理及其证明:

Lemma fib_rec_eq_fib_iter : fib_rec =1 fib_iter 0 1.
Proof.
move=>n; elim: n {-2}n (leqnn n)=> [n|n IHn].
  by rewrite leqn0; move/eqP=>->.
case=>//; case=>// n0; rewrite ltnS=> ltn0n.
rewrite fib_iter_property.
by rewrite <- (IHn _ ltn0n), <- (IHn _ (ltnW ltn0n)).
Qed.

答案 5 :(得分:0)

这是另一个答案,类似于one using mathcomp,但是这个使用&#34; vanilla&#34; COQ。

首先,我们需要一些导入,其他定义和一些帮助引理:

Require Import Coq.Arith.Arith.

Definition fib_v2 n := visit_fib_v2 n 0 1.

Lemma visit_fib_v2_property n: forall a0 a1,
  visit_fib_v2 (S (S n)) a0 a1 =
  visit_fib_v2 (S n) a0 a1 + visit_fib_v2 n a0 a1.
Proof. now induction n; firstorder. Qed.

Lemma fib_v2_property n:
  fib_v2 (S (S n)) = fib_v2 (S n) + fib_v2 n.
Proof. apply visit_fib_v2_property. Qed.

为证明主要引理,我们将使用标准有根据的归纳lt_wf_ind原则来表示<关系的自然数(a.k.a.完全归纳):

这次我们只需证明一个子目标,因为n = 0完全归纳的情况总是空洞的。不出所料,我们的归纳假设看起来像这样:

IH : forall m : nat, m < n -> fib_v1 m = fib_v2 m

以下是证据:

Lemma fib_v1_eq_fib2 n :
  fib_v1 n = fib_v2 n.
Proof.
  pattern n; apply lt_wf_ind; clear n; intros n IH.
  do 2 (destruct n; trivial).
  rewrite fib_v2_property.
  rewrite <- !IH; auto.
Qed.