Formalization of a Result of Fibonacci Sequence
Table of Contents
Continuing my previous post on AI for math, I want to give a concrete example of how AI can be used to formalize a mathematical result. More specifically, in this post, I will be formalizing an elementary result about the Fibonacci sequence that I really like. The Fibbonaci sequence is a sequence $(f_n)_{n\in\mathbb Z}$ defined as follows: $f_0=0$, $f_1=1$, and $f_n=f_{n-1}+f_{n-2}$ for $n\in\mathbb Z$. The result I want to formalize is: $f_{\gcd(m,n)}=\gcd(f_m,f_n)$ for all $m,n\in\mathbb N$, which is called the strong divisibility of Fibonacci sequence. In particular, this implies that if $n,m\in\mathbb N$ are such that $n\mid m$ then $f_n\mid f_m$ -- This is known as weak divisbility. I will be using Lean proof assistant and Codex to vibe prove this result. Find the GitHub repo here.
Lean Syntax
We begin by defining Fibonacci sequence.
1def fib : Nat -> Nat
2| 0 => 0
3| 1 => 1
4| n + 2 => fib (n + 1) + fib (n)
5
6#eval fib 10One can use the middle line to indicate "divides"
1example : 3 ∣ 12 := by
2 change ∃ k, 12 = 3 * k
3 exact ⟨4, rfl⟩The Nat type already has gcd implemented
1example : Nat.gcd 12 18 = 6 := by
2 rfl
3#eval Nat.gcd 12 18Now we can try formalizing Bezout's lemma.
1theorem bezout (a b : Nat) : ∃ x y : Int, (Nat.gcd a b : Int) = x * a + y * b := by
2 refine ⟨a.gcdA b, a.gcdB b, ?_⟩
3 -- `Nat.gcd_eq_gcd_ab` gives the Bezout identity with coefficients `gcdA` and `gcdB`.
4 calc
5 (Nat.gcd a b : Int) = (a : Int) * a.gcdA b + (b : Int) * a.gcdB b := Nat.gcd_eq_gcd_ab a b
6 _ = a.gcdA b * a + a.gcdB b * b := by ringThis is basically delegating the theorem to what is already proved in Mathlib.
Proof of Strong Divisibility
We give the following proof of strong divisibility. We observe that for all $n\in\mathbb Z$
$$\begin{pmatrix}f_{n+1}\\ f_n\end{pmatrix} = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} \begin{pmatrix}f_{n}\\ f_{n-1}\end{pmatrix} $$Therefore for all $n\ge 0$
$$\begin{pmatrix}f_{n+1}\\ f_n\end{pmatrix} =\begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}^n \begin{pmatrix}f_{1}\\ f_{0}\end{pmatrix}=\begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}^n \begin{pmatrix}1\\ 0\end{pmatrix}\tag{1}$$and offsetting the index by $1$ we have for all $n\ge 0$
$$\begin{pmatrix}f_{n}\\ f_{n-1}\end{pmatrix} =\begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}^n \begin{pmatrix}f_{0}\\ f_{-1}\end{pmatrix}=\begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}^n \begin{pmatrix}0\\ 1\end{pmatrix}\tag{2}$$Since we know where the standard basis vectors map to, if we let
$$A=\begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}$$then we have for all $n\ge 0$
$$A^n=\begin{pmatrix} f_{n+1} & f_n \\ f_n & f_{n-1} \end{pmatrix}\tag{3}$$Since $\det A=-1$, $A$ is invertible, and by (1) its inverse satisfies
$$\begin{pmatrix}f_{n}\\ f_{n-1}\end{pmatrix} = A^{-1} \begin{pmatrix}f_{n+1}\\ f_{n}\end{pmatrix} $$for all $n\ge 0$. Therefore equations (1) and (2) holds hold for all $n\in\mathbb Z$, and hence equation (3) hold for all $n\in\mathbb Z$ as well.
We can find $A^{-1}$ with a formula1 , but one can avoid that for a more conceptual approach. Observe for all $n\in\mathbb Z$
$$\begin{pmatrix}f_{n}\\ f_{n-1}\end{pmatrix} = \begin{pmatrix} 0 & 1 \\ 1 & -1 \end{pmatrix} \begin{pmatrix}f_{n+1}\\ f_{n}\end{pmatrix} $$by definition of $f_n$. This reverses what $A$ does, so we expect $B$ to be the inverse of $A$ where
$$B=\begin{pmatrix} 0 & 1 \\ 1 & -1 \end{pmatrix}$$Since $A$ shifts the vector $(f_n,f_{n-1})$ forward to $(f_{n+1},f_n)$ and $B$ shifts the vector $(f_n,f_{n-1})$ backwards to $(f_{n-1},f_{n-2})$, they cancel each other out. Therefore $AB$ and $BA$ fix the set of vectors $\{(f_{n+1},f_n):n\in\mathbb Z\}$, in particular they fix the standard basis vectors $(1,0)=(f_1,f_0)$ and $(0,1)=(f_0,f_{-1})$, hence $AB=BA=I$, and $B=A^{-1}$.
More abstractly, let $V$ be the vector space over $\mathbb R$ (or free module over $\mathbb Z$ if you like, which is arguably better) of all sequences $(a_n)_{n\in\mathbb Z}$ of real numbers that satisfy $a_{n+2}=a_{n+1}+a_n$ for all $n\in\mathbb Z$, then $V$ is $2$-dimensional, by an isomorphism
$$\phi:V\rightarrow \mathbb R^2\qquad \phi((a_n))=(a_0,a_1)$$Let $T:V\rightarrow V$ be the shifting operator $(a_n)_n\mapsto (a_{n+1})_n$ then with respect to the ordered basis $((f_n)_n,(f_{n-1})_n)$, the operator $T$ has matrix $A$. It is easy to see $T$ is invertible, since shifting is reversable. It is also much more intuitive to see that in this same basis $((f_n)_n,(f_{n-1})_n)$, we have $T^n$ has matrix that of in (3).
With the linear algebra setup above, we can obtain many classic results about Fibonacci numbers immediately.
By ⟦ref:rmk-vec⟧, the operator $T$ acts as shifting, with matrix $A$. We have
$$A^n\begin{pmatrix}f_i\\0\end{pmatrix}=\begin{pmatrix}f_{n+i}\\ f_n\end{pmatrix}\quad\mathrm{and}\quad A^n\begin{pmatrix}f_{i+j}\\ f_j\end{pmatrix}=\begin{pmatrix}f_{n+i+j}\\ f_{n+j}\end{pmatrix}$$Therefore we have
$$\det\begin{pmatrix}f_{n+i}&f_{n+i+j}\\ f_n&f_{n+j}\end{pmatrix}=\det A^n\det\begin{pmatrix}f_i&f_{i+j}\\0&f_j\end{pmatrix} $$and the result follows.
By the $2$-by-$2$ inverse matrix formula on (3), we find
$$A^{-n}=\frac{1}{f_{n-1}f_{n+1}-f_n^2}\begin{pmatrix}f_{n-1}&-f_n\\ -f_n&f_{n+1}\end{pmatrix}=\begin{pmatrix}(-1)^nf_{n-1}&(-1)^{n+1}f_n\\ (-1)^{n+1}f_n&(-1)^nf_{n+1}\end{pmatrix}$$by Cassini's identity. Comparing $(2,1)$-entry with (3) then we are done.
We now know enough to prove divisibility and strong divisibility. There is a natural reduction mod $n\ne 0$ of matrices
$$\mathrm M_2(\mathbb Z)\xrightarrow{\mathrm{mod\ }n} \mathrm M_2(\mathbb Z/n\mathbb Z)$$reducing mod $n$ entry wise. Since matrix addition and multiplications are algebraic, this is a homomorphhism.
We can even find a general formula for the Fibonacci numbers using the linear algebra developed above. Let $T$ be the operator in ⟦ref:rmk-vec⟧, it has minimal polynomial $x^2-x-1$ coming from the recurrence and eigenvalues $\varphi=\frac{1+\sqrt{5}}{2}$ and its conjugate $\overline{\varphi}=\frac{1-\sqrt{5}}{2}$. The sequences $(\varphi^n)_n$ and $(\overline{\varphi}^n)_n$ are linearly independent and both are elements of the vector space, hence they form a basis of the $2$-dimensional vector space $V$. Thus $f_n=A\varphi^n+B\overline{\varphi}_n$ for some real numbers $A,B$, which one can find by evaluating at $n=0,1$ and solving a system of linear equations. One finds the Binet formula:
$$f_n=\frac{\varphi^n-\overline{\varphi}^n}{\sqrt{5}}$$for Fibonacci numbers. Generating function methods can also be used to arrive at this formula, but I will not expand on that.
Formalization
To make formalization easier, we show identity (3) with induction in lean.
1open Matrix
2
3def A : Matrix (Fin 2) (Fin 2) Nat := !![1, 1; 1, 0]
4
5theorem fib_matrix_pow_succ (n : Nat) :
6 A ^ (n + 1) = !![fib (n + 2), fib (n + 1); fib (n + 1), fib n] := by
7 induction n with
8 | zero =>
9 simp [A, fib]
10 | succ n ih =>
11 rw [pow_succ, ih]
12 ext i j
13 fin_cases i
14 · fin_cases j <;> simp [A, Matrix.mul_apply, Fin.sum_univ_two, fib, add_comm, add_left_comm]
15 · fin_cases j <;> simp [A, Matrix.mul_apply, Fin.sum_univ_two, fib, add_comm, add_left_comm]Then we proceed to prove weak divisibility. First, lower left of power of diagonal is 0.
1theorem lowerLeft_diagonal_pow_zero {R : Type*} [Semiring R] (a b : R) (k : Nat) :
2 (((!![a, 0; 0, b] : Matrix (Fin 2) (Fin 2) R) ^ k) 1 0) = 0 := by
3 induction k with
4 | zero =>
5 simp
6 | succ k ih =>
7 rw [pow_succ]
8 simp [Matrix.mul_apply, Fin.sum_univ_two, ih]Then, with this the weak divisibility result follows. Prompting Codex to generate the theorem for weak divisibility took about 15 minutes, and the code could not run. After my corrections and some back and forth, it was able to produces the following code which runs.
1theorem fib_dvd_of_dvd {n m : Nat} (h : n ∣ m) : fib n ∣ fib m := by
2 rcases h with ⟨k, rfl⟩
3 cases n with
4 | zero =>
5 simp [fib]
6 | succ n =>
7 cases k with
8 | zero =>
9 simp [fib]
10 | succ k =>
11 let d := fib (n + 1)
12 have hdiag :
13 AMod d ^ (n + 1) =
14 !![(fib (n + 2) : ZMod d), 0; 0, fib n] := by
15 simpa [d] using fib_matrix_pow_succ_mod d n
16 have hoff :
17 ((AMod d ^ ((n + 1) * (k + 1))) 1 0) = 0 := by
18 rw [pow_mul, hdiag]
19 exact lowerLeft_diagonal_pow_zero (fib (n + 2) : ZMod d) (fib n : ZMod d) (k + 1)
20 have hentry :
21 ((AMod d ^ ((n + 1) * (k + 1))) 1 0) =
22 (fib ((n + 1) * (k + 1)) : ZMod d) := by
23 have hpos : 0 < (n + 1) * (k + 1) := Nat.mul_pos (Nat.succ_pos _) (Nat.succ_pos _)
24 simpa [d] using congrArg (fun M => M 1 0) (fib_matrix_pow_mod (d := d) hpos)
25 have hzero : (fib ((n + 1) * (k + 1)) : ZMod d) = 0 := by
26 rw [← hentry]
27 exact hoff
28 simpa [d] using (ZMod.natCast_eq_zero_iff (fib ((n + 1) * (k + 1))) d).mp hzeroFinally I fed the proof of strong divisibility to Codex and prompt it to generate a lean proof. After 31 minutes and burning through a ton of tokens, it finally produced code that could run, it is the following.
1theorem fib_eq_natFib : ∀ n : Nat, fib n = Nat.fib n
2 | 0 => by simp [fib, Nat.fib_zero]
3 | 1 => by simp [fib, Nat.fib_one]
4 | n + 2 => by
5 rw [fib, fib_eq_natFib (n + 1), fib_eq_natFib n, Nat.fib_add_two, add_comm]
6
7theorem lowerLeft_diagonal_unit_combo_zero {R : Type*} [CommRing R]
8 (u v : (Fin 2 → R)ˣ) (x y : Int) :
9 ((((Units.map (Matrix.diagonalRingHom (Fin 2) R) u) ^ x *
10 (Units.map (Matrix.diagonalRingHom (Fin 2) R) v) ^ y :
11 Units (Matrix (Fin 2) (Fin 2) R)) :
12 Matrix (Fin 2) (Fin 2) R) 1 0) = 0 := by
13 let F : (Fin 2 → R)ˣ →* (Matrix (Fin 2) (Fin 2) R)ˣ :=
14 Units.map (Matrix.diagonalRingHom (Fin 2) R)
15 calc
16 ((((F u) ^ x * (F v) ^ y : Units (Matrix (Fin 2) (Fin 2) R)) :
17 Matrix (Fin 2) (Fin 2) R) 1 0)
18 = (((F (u ^ x * v ^ y) : Units (Matrix (Fin 2) (Fin 2) R)) :
19 Matrix (Fin 2) (Fin 2) R) 1 0) := by
20 rw [← map_zpow, ← map_zpow, ← map_mul]
21 _ = 0 := by
22 change (((Matrix.diagonal (((u ^ x) * (v ^ y) : (Fin 2 → R)ˣ) : Fin 2 → R)) :
23 Matrix (Fin 2) (Fin 2) R) 1 0) = 0
24 simp [Matrix.diagonal]
25
26theorem fib_gcd (m n : Nat) : Nat.gcd (fib m) (fib n) = fib (Nat.gcd m n) := by
27 apply Nat.dvd_antisymm
28 · rcases m.eq_zero_or_pos with rfl | hm
29 · simp [fib]
30 rcases n.eq_zero_or_pos with rfl | hn
31 · simp [fib]
32 let d := Nat.gcd (fib m) (fib n)
33 have hdm : d ∣ fib m := Nat.gcd_dvd_left (fib m) (fib n)
34 have hdn : d ∣ fib n := Nat.gcd_dvd_right (fib m) (fib n)
35 have hdet : IsUnit (Matrix.det (AMod d)) := by
36 simp [AMod, Matrix.det_fin_two_of]
37 let uA : (Matrix (Fin 2) (Fin 2) (ZMod d))ˣ := Matrix.nonsingInvUnit (AMod d) hdet
38 let F : (Fin 2 → ZMod d)ˣ →* (Matrix (Fin 2) (Fin 2) (ZMod d))ˣ :=
39 Units.map (Matrix.diagonalRingHom (Fin 2) (ZMod d))
40 have hm0 : (fib m : ZMod d) = 0 := (ZMod.natCast_eq_zero_iff (fib m) d).2 hdm
41 have hn0 : (fib n : ZMod d) = 0 := (ZMod.natCast_eq_zero_iff (fib n) d).2 hdn
42 have hdiagm_box : AMod d ^ m = !![(fib (m + 1) : ZMod d), 0; 0, fib (m - 1)] := by
43 simpa [hm0] using fib_matrix_pow_mod (d := d) hm
44 have hdiagn_box : AMod d ^ n = !![(fib (n + 1) : ZMod d), 0; 0, fib (n - 1)] := by
45 simpa [hn0] using fib_matrix_pow_mod (d := d) hn
46 have hdiagm : AMod d ^ m =
47 Matrix.diagonal ![(fib (m + 1) : ZMod d), fib (m - 1)] := by
48 calc
49 AMod d ^ m = !![(fib (m + 1) : ZMod d), 0; 0, fib (m - 1)] := hdiagm_box
50 _ = Matrix.diagonal ![(fib (m + 1) : ZMod d), fib (m - 1)] := by
51 ext i j
52 fin_cases i
53 · fin_cases j <;> simp [Matrix.diagonal]
54 · fin_cases j <;> simp [Matrix.diagonal]
55 have hdiagn : AMod d ^ n =
56 Matrix.diagonal ![(fib (n + 1) : ZMod d), fib (n - 1)] := by
57 calc
58 AMod d ^ n = !![(fib (n + 1) : ZMod d), 0; 0, fib (n - 1)] := hdiagn_box
59 _ = Matrix.diagonal ![(fib (n + 1) : ZMod d), fib (n - 1)] := by
60 ext i j
61 fin_cases i
62 · fin_cases j <;> simp [Matrix.diagonal]
63 · fin_cases j <;> simp [Matrix.diagonal]
64 have hum_raw :
65 (((uA ^ m : Units (Matrix (Fin 2) (Fin 2) (ZMod d))) :
66 Matrix (Fin 2) (Fin 2) (ZMod d))) =
67 Matrix.diagonal ![(fib (m + 1) : ZMod d), fib (m - 1)] := by
68 simpa [uA, Matrix.nonsingInvUnit] using hdiagm
69 have hun_raw :
70 (((uA ^ n : Units (Matrix (Fin 2) (Fin 2) (ZMod d))) :
71 Matrix (Fin 2) (Fin 2) (ZMod d))) =
72 Matrix.diagonal ![(fib (n + 1) : ZMod d), fib (n - 1)] := by
73 simpa [uA, Matrix.nonsingInvUnit] using hdiagn
74 let um : (Fin 2 → ZMod d)ˣ :=
75 (Matrix.isUnit_diagonal.mp (hum_raw ▸ (uA ^ m).isUnit)).unit
76 let un : (Fin 2 → ZMod d)ˣ :=
77 (Matrix.isUnit_diagonal.mp (hun_raw ▸ (uA ^ n).isUnit)).unit
78 have hum : uA ^ m = F um := by
79 apply Units.ext
80 calc
81 (((uA ^ m : Units (Matrix (Fin 2) (Fin 2) (ZMod d))) :
82 Matrix (Fin 2) (Fin 2) (ZMod d))) =
83 Matrix.diagonal ![(fib (m + 1) : ZMod d), fib (m - 1)] := hum_raw
84 _ = ((F um : Units (Matrix (Fin 2) (Fin 2) (ZMod d))) :
85 Matrix (Fin 2) (Fin 2) (ZMod d)) := by
86 simp [F, um, Matrix.diagonal]
87 have hun : uA ^ n = F un := by
88 apply Units.ext
89 calc
90 (((uA ^ n : Units (Matrix (Fin 2) (Fin 2) (ZMod d))) :
91 Matrix (Fin 2) (Fin 2) (ZMod d))) =
92 Matrix.diagonal ![(fib (n + 1) : ZMod d), fib (n - 1)] := hun_raw
93 _ = ((F un : Units (Matrix (Fin 2) (Fin 2) (ZMod d))) :
94 Matrix (Fin 2) (Fin 2) (ZMod d)) := by
95 simp [F, un, Matrix.diagonal]
96 obtain ⟨x, y, hbez⟩ := bezout m n
97 have hg0 :
98 (((uA ^ (Nat.gcd m n : Int) : Units (Matrix (Fin 2) (Fin 2) (ZMod d))) :
99 Matrix (Fin 2) (Fin 2) (ZMod d)) 1 0) = 0 := by
100 rw [hbez, _root_.zpow_add, _root_.zpow_mul', _root_.zpow_mul',
101 _root_.zpow_natCast, _root_.zpow_natCast, hum, hun]
102 simpa [F] using lowerLeft_diagonal_unit_combo_zero (R := ZMod d) um un x y
103 have hgpos : 0 < Nat.gcd m n := Nat.gcd_pos_of_pos_left n hm
104 have hentry :
105 (((uA ^ (Nat.gcd m n : Int) : Units (Matrix (Fin 2) (Fin 2) (ZMod d))) :
106 Matrix (Fin 2) (Fin 2) (ZMod d)) 1 0) =
107 (fib (Nat.gcd m n) : ZMod d) := by
108 simpa [uA] using congrArg (fun M => M 1 0) (fib_matrix_pow_mod (d := d) hgpos)
109 have hzero : (fib (Nat.gcd m n) : ZMod d) = 0 := by
110 calc
111 (fib (Nat.gcd m n) : ZMod d) =
112 (((uA ^ (Nat.gcd m n : Int) : Units (Matrix (Fin 2) (Fin 2) (ZMod d))) :
113 Matrix (Fin 2) (Fin 2) (ZMod d)) 1 0) := by symm; exact hentry
114 _ = 0 := hg0
115 exact (ZMod.natCast_eq_zero_iff (fib (Nat.gcd m n)) d).1 hzero
116 · exact Nat.dvd_gcd (fib_dvd_of_dvd (Nat.gcd_dvd_left m n)) (fib_dvd_of_dvd (Nat.gcd_dvd_right m n))- The formula for the inverse of a $2$-by-$2$ matrix $\begin{pmatrix}a&b\\ c&d\end{pmatrix}$ is $\frac{1}{ad-bc}\begin{pmatrix}d&-b\\ -c&a\end{pmatrix}$, which can be verified by direct computation. ↩︎