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