Zulip Chat Archive
Stream: general
Topic: Efficient modular fibonacci
Neil Strickland (Mar 24 2019 at 17:03):
I want to prove that the 44th Fibonacci number is divisible by 89. I wrote:
import tactic.norm_num def FP_step : ℕ × ℕ → ℕ × ℕ := λ ⟨a,b⟩, ⟨b,(a + b) % 89⟩ def FP : ℕ → ℕ × ℕ | 0 := ⟨0,1⟩ | (n + 1) := FP_step (FP n) def F (n : ℕ) : ℕ := (FP n).fst #eval F 44 lemma L : F 44 = 0 := sorry
The #eval
gives 0 with no visible delay. If I try to prove L
by rfl
then I get a deterministic timeout, which I find surprising; I am only asking for 44 additions and 44 reductions mod 89 of numbers that are < 2 * 89, which does not seem too bad even in Peano-world. It also seems that norm_num
is not useful here (unless there is some non-obvious way to invoke it). Am I misunderstanding something about the count of operations? Is there something different that I should be doing?
Chris Hughes (Mar 24 2019 at 17:16):
This works. Takes a little while though.
lemma L : F 44 = 0 := by unfold F FP FP_step; norm_num
Neil Strickland (Mar 24 2019 at 17:23):
Thanks, that's much better.
Kevin Buzzard (Mar 24 2019 at 18:03):
Related: https://github.com/kbuzzard/lean-squares-in-fibonacci
Kevin Buzzard (Mar 24 2019 at 18:03):
Unfinished proof that the largest square in the Fibonacci sequence is 144.
Kevin Buzzard (Mar 24 2019 at 18:08):
The
#eval
gives 0 with no visible delay.
Of course it's #reduce
that is the problem...
Kevin Buzzard (Mar 24 2019 at 18:10):
import tactic.norm_num def FP_step : ℕ × ℕ → ℕ × ℕ := λ ⟨a,b⟩, ⟨b,(a + b) % 89⟩ def FP : ℕ → ℕ × ℕ | 0 := ⟨0,1⟩ | (n + 1) := FP_step (FP n) def F (n : ℕ) : ℕ := (FP n).fst lemma L : F 44 = 0 := begin unfold F FP FP_step, -- worth putting your cursor here and inspecting the goal norm_num end
Kevin Buzzard (Mar 24 2019 at 18:11):
How do you switch those ...
's off again?
Kevin Buzzard (Mar 24 2019 at 18:11):
I think it's a set_option
Kevin Buzzard (Mar 24 2019 at 18:17):
Still some left:
import tactic.norm_num def FP_step : ℕ × ℕ → ℕ × ℕ := λ ⟨a,b⟩, ⟨b,(a + b) % 89⟩ def FP : ℕ → ℕ × ℕ | 0 := ⟨0,1⟩ | (n + 1) := FP_step (FP n) def F (n : ℕ) : ℕ := (FP n).fst set_option pp.max_depth 10000 set_option pp.max_steps 10000 set_option pp.indent 0 lemma L : F 44 = 0 := begin unfold F FP FP_step, -- worth putting your cursor here and inspecting the goal norm_num end
Kevin Buzzard (Mar 24 2019 at 18:19):
That looks to me like a few more than 44 additions...
Chris Hughes (Mar 24 2019 at 18:24):
I guess it's not evaluating it in a sensible order. After unfolding, you probably end up with F 10
a lot of times.
Chris Hughes (Mar 24 2019 at 18:27):
I wonder what the fastest proof is, or how to solve this with bigger numbers.
Mario Carneiro (Mar 24 2019 at 18:47):
You can solve it in linear time if you craft appropriate lemmas
Mario Carneiro (Mar 24 2019 at 18:48):
that's how norm_num
itself works, of course
Kevin Buzzard (Mar 24 2019 at 18:52):
The fastest proof is to do it by pure thought ;-)
Kevin Buzzard (Mar 24 2019 at 18:52):
But if you want a computational proof, just work out F n mod 89 for the first 45 naturals
Mario Carneiro (Mar 24 2019 at 18:55):
isn't that what he's doing?
Kevin Buzzard (Mar 24 2019 at 18:57):
It didn't look like it from the prettyprinter output.
Chris Hughes (Mar 24 2019 at 18:57):
F 44 = F 42 + (F 42 + F 41)
, so you end up computing F 42
twice, and it get worse the smaller you get.
Kevin Buzzard (Mar 24 2019 at 18:57):
^^^
Mario Carneiro (Mar 24 2019 at 18:59):
but that's not how it was defined... I don't think this has exponential blowup
Kevin Buzzard (Mar 24 2019 at 19:02):
My 16 year old son says that he once saw some way of defining Fibonacci numbers in a functional language which didn't suffer from this blow-up.
Kevin Buzzard (Mar 24 2019 at 19:03):
You're right though, this isn't like what Chris says, this looks like a more sensible implementation.
Kevin Buzzard (Mar 24 2019 at 19:04):
The point somehow seems to be that each time you iterate, the term gets about 1.5 times as complicated
Kevin Buzzard (Mar 24 2019 at 19:04):
and Lean never simplifies anything until the end
Mario Carneiro (Mar 24 2019 at 19:05):
I have a solution but I'm waiting on lean to compile to test it -_-
Kevin Buzzard (Mar 24 2019 at 19:05):
F 8
looks like this:
⊢ (((((0 + 1) % 89 + (1 + (0 + 1) % 89) % 89) % 89 + ((1 + (0 + 1) % 89) % 89 + ((0 + 1) % 89 + (1 + (0 + 1) % 89) % 89) % 89) % 89) % 89 + (((1 + (0 + 1) % 89) % 89 + ((0 + 1) % 89 + (1 + (0 + 1) % 89) % 89) % 89) % 89 + (((0 + 1) % 89 + (1 + (0 + 1) % 89) % 89) % 89 + ((1 + (0 + 1) % 89) % 89 + ((0 + 1) % 89 + (1 + (0 + 1) % 89) % 89) % 89) % 89) % 89) % 89) % 89, ((((1 + (0 + 1) % 89) % 89 + ((0 + 1) % 89 + (1 + (0 + 1) % 89) % 89) % 89) % 89 + (((0 + 1) % 89 + (1 + (0 + 1) % 89) % 89) % 89 + ((1 + (0 + 1) % 89) % 89 + ((0 + 1) % 89 + (1 + (0 + 1) % 89) % 89) % 89) % 89) % 89) % 89 + ((((0 + 1) % 89 + (1 + (0 + 1) % 89) % 89) % 89 + ((1 + (0 + 1) % 89) % 89 + ((0 + 1) % 89 + (1 + (0 + 1) % 89) % 89) % 89) % 89) % 89 + (((1 + (0 + 1) % 89) % 89 + ((0 + 1) % 89 + (1 + (0 + 1) % 89) % 89) % 89) % 89 + (((0 + 1) % 89 + (1 + (0 + 1) % 89) % 89) % 89 + ((1 + (0 + 1) % 89) % 89 + ((0 + 1) % 89 + (1 + (0 + 1) % 89) % 89) % 89) % 89) % 89) % 89) % 89) % 89).fst
Kevin Buzzard (Mar 24 2019 at 19:06):
FP 8
looks like a mess because FP 7
is a mess and Lean doesn't tidy it up before making FP 8
Chris Hughes (Mar 24 2019 at 19:07):
So it is only ever adding 0 and 1
. 1.5 times bigger each time is about right for the really bad implementation given that the golden ratio is ~1.6.
Kevin Buzzard (Mar 24 2019 at 19:07):
Right, each term is about 1.6 times bigger than the one before in terms of e.g. number of additions mentioned in the term.
Kevin Buzzard (Mar 24 2019 at 19:09):
Similarly for the number of % 89
's. If a mentions X of them and b mentions Y, then (a+b)%89 mentions X+Y+1, and this is growing like Fibonacci
Mario Carneiro (Mar 24 2019 at 19:10):
this works:
lemma FP_succ {n a b c : ℕ} (h : FP n = (a, b)) (h2 : (a + b) % 89 = c) : FP (nat.succ n) = (b, c) := by rw [← h2, FP, h]; refl lemma L : F 44 = 0 := begin have : FP 0 = (0, 1) := rfl, iterate 44 { replace := FP_succ this (by norm_num; refl) }, exact congr_arg prod.fst this end
Kevin Buzzard (Mar 24 2019 at 19:21):
iterate 44 { replace := FP_succ this (by norm_num; refl) }, -- put your cursor here for some goal fun
Mario Carneiro (Mar 24 2019 at 19:22):
it's possible to reduce the succ's but it's going to generate 44 add-one's anyway so I didn't bother
Neil Strickland (Mar 24 2019 at 20:07):
Thanks, that's interesting. I see roughly how it works but will need to digest a bit further.
Kevin Buzzard (Mar 24 2019 at 20:13):
I guess replace := ...
is shorthand for replace this := ...
which means "replace the hypothesis this
with a new hypothesis also called this
.
Kevin Buzzard (Mar 24 2019 at 20:14):
lemma L : F 44 = 0 := begin have this : FP 0 = (0, 1) := rfl, iterate 5 { replace this := FP_succ this (by norm_num; refl) }, -- this : FP (nat.succ (nat.succ (nat.succ (nat.succ 1)))) = (5, 8) sorry end
Kevin Buzzard (Mar 24 2019 at 20:14):
A hypothesis without a name defaults to this
and Mario is abusing this a bit :-)
Kevin Buzzard (Mar 24 2019 at 20:15):
But replace
is essential to the method because we don't have the machinery to make 44 names without going into meta land (as far as I know)
Kevin Buzzard (Mar 24 2019 at 20:17):
This works:
lemma L : F 44 = 0 := begin have H : FP 0 = (0, 1) := rfl, iterate 44 { have H := FP_succ H (by norm_num; refl) }, exact congr_arg prod.fst H, end
but you end up with 44 hypotheses all called H
. The reason it works is that Lean uses the most recently generated one at each stage.
Kenny Lau (Mar 24 2019 at 21:56):
I feel compulsory to note that although this modified algorithm is O(n), which is better than the naive algorithm of O(2^n), there is an algorithm of O(log n)
Mario Carneiro (Mar 24 2019 at 22:45):
reduce mod 44?
Kenny Lau (Mar 24 2019 at 22:56):
no, Fibonacci
Kenny Lau (Mar 24 2019 at 23:20):
it only dawned on me that F 11 = 89 which would imply that F 44 is divisible by 89
Kenny Lau (Mar 24 2019 at 23:24):
import tactic.norm_num constant F : ℕ → ℕ axiom F_0 : F 0 = 0 axiom F_1 : F 1 = 1 axiom F_2 : F 2 = 1 axiom F_succ_succ {n : ℕ} : F (n.succ.succ) = (F n + F n.succ) % 89 axiom F_bit0 {n} : F (bit0 n) = ((F n * F n.succ) % 89 * 2 % 89 + 89 - (F n * F n) % 89) % 89 axiom F_bit1 {n} : F (bit1 n) = ((F n * F n) % 89 + (F n.succ * F n.succ) % 89) % 89 section variables {n : ℕ} (m : ℕ) (H : n.succ = m) theorem F_bit0' : F (bit0 n) = ((F n * F m) % 89 * 2 % 89 + 89 - (F n * F n) % 89) % 89 := H ▸ F_bit0 theorem F_bit1' : F (bit1 n) = ((F n * F n) % 89 + (F m * F m) % 89) % 89 := H ▸ F_bit1 end theorem F_succ_succ' (n m k : ℕ) (H1 : n.succ = m) (H2 : m.succ = k) : F k = (F n + F m) % 89 := H2 ▸ H1 ▸ F_succ_succ -- (44, 45) -> (22, 23) -> (10, 11) -> (4, 5) -> (2, 3) -> (0, 1) theorem F0 : F 0 = 0 := F_0 theorem F1 : F 1 = 1 := F_1 theorem F2 : F 2 = 1 := F_2 theorem F3 : F 3 = 2 := by rw [F_bit1' 2 rfl, F1, F2]; refl theorem F4 : F 4 = 3 := by rw [F_bit0' 3 rfl, F2, F3]; norm_num theorem F5 : F 5 = 5 := by rw [F_bit1' 3 rfl, F2, F3]; refl theorem F6 : F 6 = 8 := by rw [F_succ_succ' 4 5 6 rfl rfl, F4, F5]; refl theorem F10 : F 10 = 55 := by rw [F_bit0' 6 rfl, F5, F6]; norm_num theorem F11 : F 11 = 0 := by rw [F_bit1' 6 rfl, F5, F6]; norm_num theorem F12 : F 12 = 55 := by rw [F_succ_succ' 10 11 12 rfl, F10, F11]; norm_num theorem F22 : F 22 = 0 := by rw [F_bit0' 12 rfl, F11, F12]; norm_num theorem F23 : F 23 = 88 := by rw [F_bit1' 12 rfl, F11, F12]; norm_num theorem F44 : F 44 = 0 := by rw [F_bit0' 23 rfl, F22, F23]; norm_num theorem F45 : F 45 = 1 := by rw [F_bit1' 23 rfl, F22, F23]; norm_num
Kenny Lau (Mar 24 2019 at 23:24):
O(log 2) algorithm ^
Kevin Buzzard (Mar 25 2019 at 00:33):
The fastest proof is to do it by pure thought ;-)
Periods of Fibonacci numbers modulo primes are well understood.
Kenny Lau (Mar 25 2019 at 00:36):
is it?
Kevin Buzzard (Mar 25 2019 at 00:37):
A slight shortcut in what you did Kenny -- you can show that is a multiple of saving you from having to work out and
Kevin Buzzard (Mar 25 2019 at 00:37):
It's just about the order of modulo right?
Kevin Buzzard (Mar 25 2019 at 00:38):
and we're done, right?
Kevin Buzzard (Mar 25 2019 at 00:41):
The pure thought proof says that the order divides (note that is a square mod ) so is automatically 0 mod , and will be mod if is a quadratic residue mod , which it is when .
Kevin Buzzard (Mar 25 2019 at 00:43):
I'm also using that mod 4 here, because then it follows that is also a QR
Kevin Buzzard (Mar 25 2019 at 00:43):
so their 44th powers are both 1.
Kenny Lau (Mar 25 2019 at 01:40):
well I want F45 also to show that the period is 44
Kenny Lau (Mar 25 2019 at 01:40):
but interesting observation
Last updated: Dec 20 2023 at 11:08 UTC