Zulip Chat Archive

Stream: new members

Topic: How to do 1 + 1 = 2


jakub (Oct 31 2024 at 14:24):

Within a larger proof, how can I convert 1 + 1 to 2?

Edward van de Meent (Oct 31 2024 at 14:29):

norm_num

Edward van de Meent (Oct 31 2024 at 14:29):

(likely)

Edward van de Meent (Oct 31 2024 at 14:29):

if this is not actual integers, it might be a bit more complicated.

jakub (Oct 31 2024 at 14:30):

Edward van de Meent said:

if this is not actual integers, it might be a bit more complicated.

Yeah it is integers. So do I have to introduce an intermediary theorem with the have statement?

Edward van de Meent (Oct 31 2024 at 14:32):

no, i think you can do without introducing this as a local lemma

Edward van de Meent (Oct 31 2024 at 14:33):

the norm_num tactic should be able to simplify the term in most hypotheses and goals, afaik

Edward van de Meent (Oct 31 2024 at 14:34):

it might take norm_num at some_hypothesis if it's not the goal you're simplifying though

jakub (Oct 31 2024 at 14:36):

It doesn't even appear to be working within a calc, unless I misunderstood the syntax...

calc
      n * (n + 1) / 2 + (n + 1) = (n^2 + n) / 2 + (n + 1) := by norm_num

jakub (Oct 31 2024 at 14:37):

I'm struggling to do general arithmetic in lean

Yaël Dillies (Oct 31 2024 at 14:37):

I don't see any 1 + 1 in your calc. Did you accidentally copy the wrong line?

jakub (Oct 31 2024 at 14:41):

Yaël Dillies said:

I don't see any 1 + 1 in your calc. Did you accidentally copy the wrong line?

Nono what I'm referring to here is this

the norm_num tactic should be able to simplify the term in most hypotheses and goals, afaik

I understand this as norm_num being able to multiply and add things, (a more complicated example than 1 + 1 = 2 in this case,) but perhaps it can only add?

Eric Wieser (Oct 31 2024 at 14:42):

The claim was "if 1 + 1 appears somewhere in your goal, norm_num will replace it with 2"

Eric Wieser (Oct 31 2024 at 14:42):

The issue with your example is that norm_num does calculations on numeric literals, and n is not a literal

jakub (Oct 31 2024 at 14:45):

Either way it doesn't work as intended
For some reason this use of norm_num does not affect the goal

example (n : ) :  i in range n, (i + 1) = n * (n+1)/2 := by
  induction n
  case zero =>
    rw [sum_range_zero]

    rw [Nat.zero_mul]
    rw [Nat.zero_div]
  case succ n ih =>
    rw [sum_range_succ]
    rw [ih]
    norm_num

While doing it this way works:

example (n : ) :  i in range n, (i + 1) = n * (n+1)/2 := by
  induction n
  case zero =>
    rw [sum_range_zero]
    rw [Nat.zero_mul]
    rw [Nat.zero_div]
  case succ n ih =>
    rw [sum_range_succ]
    rw [ih]
    have h₁ : n + 1 + 1 = n + 2 := by norm_num
    rw [h₁]

Ruben Van de Velde (Oct 31 2024 at 14:54):

I'd probably recommend that anyway. Though note that somehow you ended up with unexpected spaces (non-breaking ones?) in your code, so the button to run in the live viewer doesn't work

jakub (Oct 31 2024 at 15:29):

Ruben Van de Velde said:

I'd probably recommend that anyway. Though note that somehow you ended up with unexpected spaces (non-breaking ones?) in your code, so the button to run in the live viewer doesn't work

How can I perform even more simplifications incrementally like the expansion one above?

Eric Wieser (Oct 31 2024 at 15:30):

I agree the behavior above is surprising; here's a minimized example:

import Mathlib

open scoped Nat

example : (n + 1 + 1) = (n + 2) := by norm_num
example : (n + 1 + 1)! = (n + 2)! := by norm_num
example : (n + 1 + 1) / 2 = (n + 2) / 2 := by norm_num  -- fails

Shreyas Srinivas (Oct 31 2024 at 15:41):

Btw, while others figure out why norm_num fails, omega is surprisingly versatile (no non-linearities allowed though):

import Mathlib

open scoped Nat

example : (n + 1 + 1) = (n + 2) := by norm_num
example : (n + 1 + 1)! = (n + 2)! := by norm_num
example : (n + 1 + 1) / 2 = (n + 2) / 2 := by norm_num  -- fails
example : (n + 1 + 1) / 2 = (n + 2) / 2 := by omega  -- succeeds

Shreyas Srinivas (Oct 31 2024 at 15:43):

as is nlinarith sometimes when no subtraction stuff is involved:

import Mathlib

open scoped Nat

example : (n + 1 + 1)! = (n + 2)! := by nlinarith

Heather Macbeth (Oct 31 2024 at 15:47):

The point here is that it's not norm_num's core operation doing the work, it's simp:

example : (n + 1 + 1) = (n + 2) := by simp -- works
example : (n + 1 + 1)! = (n + 2)! := by simp -- works

example : (n + 1 + 1) = (n + 2) := by norm_num1 -- fails
example : (n + 1 + 1)! = (n + 2)! := by norm_num1 -- fails

Eric Wieser (Oct 31 2024 at 15:53):

Aha!

import Mathlib

open scoped Nat

example : (n + 1 + 1) = (n + 2) := by norm_num only
example : (n + 1 + 1)! = (n + 2)! := by norm_num only
example : (n + 1 + 1) / 2 = (n + 2) / 2 := by norm_num only -- works

Eric Wieser (Oct 31 2024 at 15:53):

So simp is getting in the way of norm_num here

Edward van de Meent (Oct 31 2024 at 15:57):

actually, it doesnt:

import Mathlib

open scoped Nat

example : (n + 1 + 1) = 0 := by
  norm_num only
  sorry
example : (n + 1 + 1)! = 0 := by
  norm_num
  sorry
example : (n + 1 + 1) / 2 = 0 := by
  norm_num only -- works
  sorry

check the goals at sorry; i think the trick is that norm_num only tries rfl

Edward van de Meent (Oct 31 2024 at 15:58):

it doesn't actually change n + 1 + 1 into n + 2

Heather Macbeth (Oct 31 2024 at 17:01):

Observe the result of

example : (n + 1 + 1) / 2 = (n + 2) / 2 := by simp?

Namely, there are two simp-lemmas here which send simp (and by extension norm_num) down the wrong path: Nat.ofNat_pos, Nat.add_div_right.

Heather Macbeth (Oct 31 2024 at 17:02):

This works:

attribute [-simp] Nat.ofNat_pos Nat.add_div_right

example : (n + 1 + 1) / 2 = (n + 2) / 2 := by simp

Heather Macbeth (Oct 31 2024 at 17:02):

As does

example : (n + 1 + 1) / 2 = (n + 2) / 2 := by simp only

Heather Macbeth (Oct 31 2024 at 17:08):

Anyway, in my opinion the "correct" way to solve all three problems is ring_nf:

example : (n + 1 + 1) = (n + 2) := by ring_nf
example : (n + 1 + 1)! = (n + 2)! := by ring_nf
example : (n + 1 + 1) / 2 = (n + 2) / 2 := by ring_nf

Heather Macbeth (Oct 31 2024 at 17:08):

This is robust to variants like

example : (1 + n + 1) = (n + 2) := by ring_nf
example : (1 + n + 1)! = (n + 2)! := by ring_nf
example : (1 + n + 1) / 2 = (n + 2) / 2 := by ring_nf

Heather Macbeth (Oct 31 2024 at 17:09):

And ring_nf subsumes norm_num1.

jakub (Nov 01 2024 at 11:28):

Why is doing arithmetic so complicated in Lean?

Edward van de Meent (Nov 01 2024 at 11:36):

because arithmetic notation is secretly complicated

Riccardo Brasca (Nov 01 2024 at 11:38):

Because formalized mathematics is intrinsically difficult. We are working hard to create tactics to help, and indeed you first example 1+1=2 is not difficult (norm_num can do it).
Note that we immediately asked "which 1 are you talking about?" Normally one can ignore the difference between the natural number 1 and the real number 1, but in Lean everything must be 100% precise, and this difference has to be taken into account.

Edward van de Meent (Nov 01 2024 at 11:38):

you just don't notice because you're smart enough to get used to it and notice patterns of what you can/can't do. but lean doesn't learn those unless you tell them.

Edward van de Meent (Nov 01 2024 at 11:39):

and writing a lemma for each of those possible computations is sort of impractical

Riccardo Brasca (Nov 01 2024 at 11:40):

Yes, the point is that we (humans beings) are quite clever, even if computers are faster than us.

jakub (Nov 01 2024 at 11:41):

So what would be the best way of proving
n * (n + 1) / 2 + (n + 1) = (n + 1) * (n + 1 + 1) / 2
where n belongs to Nat without making a mess

Riccardo Brasca (Nov 01 2024 at 11:43):

Well, that statement is a mess. This is because the last operation / 2 is not what you think it is.

Riccardo Brasca (Nov 01 2024 at 11:44):

My suggestion is to simply avoid division of natural numbers, so to prove something like

import Mathlib

example (n : ) : (n : ) * (n + 1) / 2 + (n + 1) = (n + 1) * (n + 1 + 1) / 2 := by
  sorry

Riccardo Brasca (Nov 01 2024 at 11:45):

And then

import Mathlib

example (n : ) : (n : ) * (n + 1) / 2 + (n + 1) = (n + 1) * (n + 1 + 1) / 2 := by
  field_simp
  ring

works

jakub (Nov 01 2024 at 11:46):

Riccardo Brasca said:

Well, that statement is a mess. This is because the last operation / 2 is not what you think it is.

And what would that be? Is it like integer division in C (result in Nat)?

Riccardo Brasca (Nov 01 2024 at 11:47):

Yes, it is truncated division, so 1/2 = 0.

Riccardo Brasca (Nov 01 2024 at 11:49):

Note that what you want is true, but this is because n(n+1) and (n+1)(n+1+1) are even, and this step cannot be avoided in the proof.

jakub (Nov 01 2024 at 11:51):

Riccardo Brasca said:

And then

import Mathlib

example (n : ) : (n : ) * (n + 1) / 2 + (n + 1) = (n + 1) * (n + 1 + 1) / 2 := by
  field_simp
  ring

works

Is there a way to do that in even more incremental steps, so that it's more readable? Like break up the field_simpinto multiple simplifications?

Riccardo Brasca (Nov 01 2024 at 11:53):

Well, you can of course write down a calc proof that does every step explicitly, but these two tactics are not designed to write down a more readable proof.

Riccardo Brasca (Nov 01 2024 at 11:55):

But the full proof will probably be quite noisy, for example you are dividing by something (in now), so you surely have to prove that this something (that is 2) is not zero. This is "obvious", but Lean will ask you a proof (norm_num can do it).

jakub (Nov 01 2024 at 11:57):

Riccardo Brasca said:

And then

import Mathlib

example (n : ) : (n : ) * (n + 1) / 2 + (n + 1) = (n + 1) * (n + 1 + 1) / 2 := by
  field_simp
  ring

works

How could I convert n to Q when this goal is the final step in a larger proof?

Riccardo Brasca (Nov 01 2024 at 11:58):

import Mathlib

example (n : ) : n * (n + 1) / 2 + (n + 1) = (n + 1) * (n + 1 + 1) / 2 := by
  have h₁ : 2  (n : ) * (n + 1) := by
    obtain k, hk := Nat.even_mul_succ_self n
    use k
    rw [ two_mul] at hk
    exact mod_cast hk
  have h₂ : 2  ((n : ) + 1) * (n + 1 + 1) := by
    obtain k, hk := Nat.even_mul_succ_self (n+1)
    use k
    rw [ two_mul] at hk
    exact mod_cast hk
  qify [h₁, h₂]
  field_simp
  ring

Riccardo Brasca (Nov 01 2024 at 11:59):

this is a proof of your initial result. I use the qify tactic to move to , where the same as above works. But note that I have to prove by hand that 2 ∣ (n : ℤ) * (n + 1), otherwise qify is not smart enough to understand that (n*(n+1)/2 : ℚ) = (n:ℚ)*(n+1)/2.

Riccardo Brasca (Nov 01 2024 at 12:04):

This may look over complicated, but the point is that truncated division is not well behaved, so if you really want to use it you have to work a little harder.

jakub (Nov 01 2024 at 12:04):

Riccardo Brasca said:

this is a proof of your initial result. I use the qify tactic to move to , where the same as above works. But note that I have to prove by hand that 2 ∣ (n : ℤ) * (n + 1), otherwise qify is not smart enough to understand that (n*(n+1)/2 : ℚ) = (n:ℚ)*(n+1)/2.

Could you not instead say, "this is true for all n belongs to Q" and "N is a subset of Q" so therefore it holds for all N?

Riccardo Brasca (Nov 01 2024 at 12:05):

This is what the tactic does, but if a and b are natural numbers, it is not true that a/b (that is a natural number!) viewed as a rational number is the same as (a : ℚ)/(b : ℚ).

Riccardo Brasca (Nov 01 2024 at 12:06):

I mean, 1/2=0 in , but (1: ℚ)/(2:ℚ) = 0.5 ≠ 0

jakub (Nov 01 2024 at 12:11):

Yeah that's fair enough. Thanks for explaining.

jakub (Nov 01 2024 at 14:58):

@Riccardo Brasca I followed your suggestion to avoid integer division and tried making it divide properly by making the divisor a member of Q, however I don't understand why the following code doesn't work. (I have replaced Nat.zero_mul and Nat.zero_div with Rat.zero_mul and Rat.zero_div, respectively.)

example (n : ) :  i in range n, (i + 1) = n * (n+1)/(2: ) := by
  induction n
  case zero =>
    rw [sum_range_zero]
    rw [Rat.zero_mul]  -- error
    rw [Rat.zero_div]
  case succ n ih =>
    sorry
tactic 'rewrite' failed, did not find instance of the pattern in the target expression
  0 * ?a
 0 = 0 * (0 + 1) / 2

Riccardo Brasca (Nov 01 2024 at 15:01):

I am with the phone and I cannot check, but try to make the very first n a rational. Lean will make everything else also a rational automatically

jakub (Nov 01 2024 at 15:02):

That appears to be giving me the same error.

Riccardo Brasca (Nov 01 2024 at 15:04):

You probably need the fact that the coercion of zero is zero, maybe it is coe_zero. Anyway simp will surely do it

Kyle Miller (Nov 01 2024 at 15:04):

(In case it helps here and elsewhere, set_option pp.numericTypes true causes Lean to print the types of numerals)

Kyle Miller (Nov 01 2024 at 15:05):

norm_cast is helpful for getting rid of casts too

Kyle Miller (Nov 01 2024 at 15:06):

I might expect norm_num to be able to prove this too

Kevin Buzzard (Nov 01 2024 at 15:16):

The error message says it all : there's no 0, there's only a ↑0.

Kevin Buzzard (Nov 01 2024 at 15:18):

Note that case zero => simp works. You noted above that formalization is hard, and indeed this is true. We have built tactics to do all the grunt work, and you're just noting that if you don't use these tactics then reasoning formally can be very tedious. If you use simp? you'll be able to see exactly which lemmas are being used.

Kevin Buzzard (Nov 01 2024 at 15:22):

import Mathlib

open Finset

example (n : ) :  i in range n, (i + 1) = n * (n+1)/(2: ) := by
  induction n
  case zero => simp
  case succ n ih =>
    push_cast at * -- deals with all the up-arrow issues
    simp [ih, sum_range_succ] -- applies the inductive hypothesis
    ring -- does the remaining algebra

Kevin Buzzard (Nov 01 2024 at 15:26):

On the other hand, especially if you are just beginning to explore in this area and really want to see everything from first principles, then I think you would have a lot more fun doing an example which does not involve subtraction or division, because then you can stay purely within the realm of the natural numbers and you won't run into all these annoyances with coercions. For example you might enjoy playing with this example, where a first principles approach will be far less tedious:

import Mathlib

open Finset

example (n : ) :  i in range n, (2 * i + 1) = n * n := by
  induction n
  case zero =>
    sorry
  case succ n ih =>
    sorry

Kevin Buzzard (Nov 01 2024 at 15:28):

When I was in your situation in 2017, wanting to see how everything fitted together, not only was the subtraction/division annoyance still there, but we didn't have the tools to do everything automatically so the tedious approach was the only approach. It was at that time that I started realising that avoiding natural division made things much more fun.

jakub (Nov 02 2024 at 20:58):

Kevin Buzzard said:

On the other hand, especially if you are just beginning to explore in this area and really want to see everything from first principles, then I think you would have a lot more fun doing an example which does not involve subtraction or division, because then you can stay purely within the realm of the natural numbers and you won't run into all these annoyances with coercions. For example you might enjoy playing with this example, where a first principles approach will be far less tedious:

import Mathlib

open Finset

example (n : ) :  i in range n, (2 * i + 1) = n * n := by
  induction n
  case zero =>
    sorry
  case succ n ih =>
    sorry

I proved the theorem you suggested above :smile:
Do you know if you could point me to some resources that teach coercions in lean? I'm having trouble finding some but would love to understand how the proof of my original theorem with division and coercions functions

jakub (Nov 04 2024 at 12:04):

Of course this also applies to anyone else who might have resources covering coercions :smile:

Yan Yablonovskiy (Nov 04 2024 at 13:54):

jakub said:

Why is doing arithmetic so complicated in Lean?

As someone that is also new to it, i think it seems more that way because we might be used to 'programming' generally doing something like this easily.

But in the sense of a proof assistant, 'proving' 1+1 = 2 isn't the same as calculating it generally. So just from the way i've reconciled it -- is that when a calculator does 1+1=2 it carries very little actual logical information to what this even means.

For a proof context however, it comes with all sorts of implications tied to what '1' is and what it does in other contexts, how '+' might work in the most abstract setting , whether univalence is involved, how the new object '2' might 'relate' to 1 ( 2 > 1) etc.

In this way too, the ring tactic carries the least auxiliary information; it may be more flexible in some specialised contexts.

Just my 2 cents though, not sure if that is helpful.

Kevin Buzzard (Nov 04 2024 at 16:55):

I don't know what "teach coercions in lean" means. The thing you need to know are the norm_cast and push_cast tactics, and you can read their docstrings to see what they do. In a nutshell, the natural number 37 and the integer 37 and the rational number 37 and etc etc are all different objects in lean, and they're all related by maps which don't appear in the textbooks (ie they're invisible) but in lean they're all visible and all have the same notation which is up-arrow.

Edward van de Meent (Nov 04 2024 at 17:16):

even in textbooks, they aren't technically the same... the construction of 37 : Real in ZFC, for example, is a different set from 37 : Nat, 37 : Int or 37 : Rat.

Edward van de Meent (Nov 04 2024 at 17:18):

to the point that stuff like (37:Rat) ∈ (2074 : Real) is a true statement that makes sense in ZFC :vomit:

Edward van de Meent (Nov 04 2024 at 17:22):

(i like type theory, if you hadn't noticed)

jakub (Nov 04 2024 at 19:33):

Kevin Buzzard said:

I don't know what "teach coercions in lean" means. The thing you need to know are thenorm_cast and push_cast tactics, and you can read their docstrings to see what they do. In a nutshell, the natural number 37 and the integer 37 and the rational number 37 and etc etc are all different objects in lean, and they're all related by maps which don't appear in the textbooks (ie they're invisible) but in lean they're all visible and all have the same notation which is up-arrow.

If you're giving me the nutshell here, what I'm looking for is the nut :big_smile:
That's what I meant by "teaching coercions", i.e. I'm trying to find a resource that goes more into detail into how to work with coercions in lean. And I'm sure there's more to it than just norm_cast and push_cast.

Luigi Massacci (Nov 04 2024 at 21:13):

Edward van de Meent said:

to the point that stuff like (37:Rat) ∈ (2074 : Real) is a true statement that makes sense in ZFC :vomit:

wait, at the risk of derailing the conversation, while I agree that is a well formed statement in ZFC, I am rather skeptical that it is true (at least in the usual cauchy seq. encoding of reals and with kuratowski pairs)

Niels Voss (Nov 04 2024 at 22:53):

He might be talking about Dedekind cuts, in which a real number is identified with the set of rational numbers less than it.

Luigi Massacci (Nov 05 2024 at 09:57):

Ah, yes makese sense


Last updated: May 02 2025 at 03:31 UTC