Zulip Chat Archive
Stream: new members
Topic: 2 beginner problems with an induction proof
rzeta0 (Dec 29 2024 at 21:41):
The following defines a recursively defined triangle number function, and then tries to prove a closed form for it.
import Mathlib.Tactic
def T: ℕ → ℚ
| 0 => 0
| n + 1 => n + 1 + T n
#eval T 4
example {n: ℕ} : T n = n * (n + 1) / 2 := by
induction n with
| zero =>
calc
T 0 = 0 := by rw [T]
_ = 0 * (0 + 1) / 2 := by norm_num
| succ n ih =>
calc
T (n + 1) = n + 1 + T n := by rw [T]
_ = n + 1 + n * (n + 1) / 2 := by rw [ih]
_ = (n + 1) * (n + 1 + 1) / 2 := by ring
Question 1: The proof doesn't work with def T: ℕ → ℕ
, the last ring
fails. Why is this? I assume it is because of the division by 2. Is it good practice to leave it as a rational number, or do people make efforts to "fix it"?
Question 2: Lean is telling me the inductive step is not resolved. Why? The following is the message which I don't know how to interpret - something about upcasting?
⊢ (↑n + 1) * (↑n + 1 + 1) / 2 = ↑(n + 1) * (↑(n + 1) + 1) / 2
Ilmārs Cīrulis (Dec 29 2024 at 21:46):
Solution with Nat -> Nat
definition of T
. For me triangle numbers feel more natural this way, but that's subjective, probably.
import Mathlib.Tactic
def T: ℕ → ℕ
| 0 => 0
| n + 1 => n + 1 + T n
#eval T 4
example {n: ℕ} : T n = n * (n + 1) / 2 := by
induction n with
| zero =>
calc
T 0 = 0 := by rw [T]
_ = 0 * (0 + 1) / 2 := by norm_num
| succ n ih =>
calc
T (n + 1) = n + 1 + T n := by rw [T]
_ = n + 1 + n * (n + 1) / 2 := by rw [ih]
_ = (n + 1) * (n + 1 + 1) / 2 := by ring_nf; omega
And no, I don't know why exactly omega
works here. :|
Ilmārs Cīrulis (Dec 29 2024 at 21:51):
import Mathlib.Tactic
def T: ℕ → ℚ
| 0 => 0
| n + 1 => n + 1 + T n
#eval T 4
example {n: ℕ} : T n = n * (n + 1) / 2 := by
induction n with
| zero =>
calc
T 0 = 0 := by rw [T]
_ = 0 * (0 + 1) / 2 := by norm_num
| succ n ih =>
calc
T (n + 1) = n + 1 + T n := by rw [T]
_ = n + 1 + n * (n + 1) / 2 := by rw [ih]
_ = (n + 1) * (n + 1 + 1) / 2 := by ring
norm_cast
I will have to look up what exactly norm_cast
does, but my primitive current understanding is that it "normalizes" coercions or smth like that.
Ilmārs Cīrulis (Dec 29 2024 at 21:53):
⊢ (↑n + 1) * (↑n + 1 + 1) / 2 = ↑(n + 1) * (↑(n + 1) + 1) / 2
- this means that you still have goal to prove. As one can see, left side isn't equal to right side.
rzeta0 (Dec 29 2024 at 21:55):
I don't understand why Lean thinks the LHS and RHS are different.
Ilmārs Cīrulis (Dec 29 2024 at 22:00):
For example, (↑n + 1)
and ↑(n + 1)
are different.
The first is n
converted to Q and then with rational number 1 added.
The second is n
as natural number with natural number 1 added and then the sum converted to Q.
rzeta0 (Dec 29 2024 at 22:07):
The only thing that comes to mind is to use brackets to get Lean to think of (n+1)
as a unit. I tried that and it didn't work.
I also tried things like ((n + 1):ℚ)
which also didn't work.
rzeta0 (Dec 29 2024 at 22:08):
But in any case, I feel doing coercions is probably the wrong approach.
Yaël Dillies (Dec 29 2024 at 22:09):
(↑(n + 1) : ℚ)
will do what you expect
Dan Velleman (Dec 29 2024 at 22:10):
The theorem Nat.cast_succ
proves that ↑(n + 1) = (↑n + 1)
.
Dan Velleman (Dec 29 2024 at 22:13):
I suspect that you defined T n
to be a rational number because your theorem was going to involve division by 2, and division is easier in Q than in N. But by using Q, you were forced to deal with coercions. An alternative would be to state your theorem as 2 * (T n) = n * (n + 1)
. Now there's no division, so no reason to use Q rather than N.
rzeta0 (Dec 29 2024 at 22:14):
Thanks Ilmārs, Yaël and Dan.
Overall I feel that casting is an ugly "hack" - I will go away and think if there is a better way for what I had hoped would be a nice simple educational example.
If I can get the definition to be ℕ→ℕ
that would avoid the casting, but then I need to solve ring
failing.
Dan Velleman (Dec 29 2024 at 22:14):
And once you've proven the theorem in this new form, you could divide both sides by 2 to get the theorem you really wanted.
rzeta0 (Dec 29 2024 at 22:16):
Hi @Dan Velleman - yes, 2*T
is the approach I used when I previously defined Triangle
as a proposition and that was ok - but I was hoping to avoid it at this later stage of the learning journey.
https://leanfirststeps.blogspot.com/2024/11/19-our-own-definition.html
rzeta0 (Dec 29 2024 at 22:16):
Dan Velleman said:
And once you've proven the theorem in this new form, you could divide both sides by 2 to get the theorem you really wanted.
Interesting - I've never seen this done before.
(except MoPs cancel 2 at h
)
Dan Velleman (Dec 29 2024 at 22:41):
For example, you could do this:
import Mathlib.Tactic
def T: ℕ → ℕ
| 0 => 0
| n + 1 => n + 1 + T n
lemma two_T {n: ℕ} : 2 * T n = n * (n + 1) := sorry
example {n : ℕ} : T n = n * (n + 1) / 2 :=
calc T n
_ = 2 * T n / 2 := by field_simp
_ = n * (n + 1) / 2 := by rw [two_T]
Now you can fill in the sorry
with an induction proof.
Zhang Qinchuan (Dec 30 2024 at 03:02):
You can first prove 2 * T n = n * (n + 1)
and then divide both side by 2
. This is exactly how mathlib handle Gauss' summation formula.
See docs#Finset.sum_range_id_mul_two and docs#Finset.sum_range_id.
Last updated: May 02 2025 at 03:31 UTC