Zulip Chat Archive

Stream: general

Topic: How to work on base representations and lists?


Ryan Lahfa (Jan 29 2021 at 15:34):

I'm trying to work out with integers base representations using data.nat.digits and here is a MWE where I am completely stuck:

import data.nat.digits


lemma mwe
  (b n: ):
  list.sum ((nat.digits b n).map_with_index (λ i a, a * b ^ i))
  = n :=
  begin
    conv {
      to_rhs,
      rw  nat.of_digits_digits b n,
    },
    rw nat.of_digits_eq_foldr b,
    -- how to carry on?
    sorry
  end

I have the feeling the list API is not really enough and unsure if I should switch to something like finset and use only finset in my code but as the base repr API provides me only with list, I'm not sure.

I guess what should I do is to develop adhoc lemmas to show that foldr and the map_with_index coincide in the way I want using induction or something like this.

Jannis Limperg (Jan 29 2021 at 16:09):

Seems like there is indeed a map_with_index-shaped hole in the mathlib API -- quick grep didn't find anything. For this particular application, you need the lemma that map_with_index is equal to a foldr; after that you should be able to use foldr fusion to get rid of the sum. The map_with_index lemma would probably go into data/list/indexes.lean.

Eric Wieser (Jan 29 2021 at 16:23):

Does rw map_with_index make progress? Or is docs#list.map_with_index not defined conveniently?

Edit now the link is there: Looks like it's not. Maybe it should be redefined in terms of foldr?

Jannis Limperg (Jan 29 2021 at 16:29):

This applies to a lot of the list functions. Could be that the direct recursive formulation is more efficient than the foldr one.

Eric Wieser (Jan 29 2021 at 16:33):

I think it's simpler than that - map_with_index is defined before foldr (and both are in lean core)

Yakov Pechersky (Jan 29 2021 at 17:19):

I would make a lemma that says that l.map_with_index f can be expressed as a l.enum.map (uncurry f) or something

Yakov Pechersky (Jan 29 2021 at 17:19):

And then you'll be in the clear, because enum and map have way more lemmas.

Yakov Pechersky (Jan 29 2021 at 17:20):

For example, the relevant one here could be docs#list.enum_eq_zip_range

Yakov Pechersky (Jan 29 2021 at 17:21):

And I expect there might be an inductive step here.

Ryan Lahfa (Jan 29 2021 at 17:35):

Jannis Limperg said:

Seems like there is indeed a map_with_index-shaped hole in the mathlib API -- quick grep didn't find anything. For this particular application, you need the lemma that map_with_index is equal to a foldr; after that you should be able to use foldr fusion to get rid of the sum. The map_with_index lemma would probably go into data/list/indexes.lean.

Would that be something like:

lemma list.sum_map_with_index_eq_foldr {α β: Type*} (as: list α) (f:   α  β):
  (as.map_with_index f).sum = as.foldr (something here) := sorry

?

Ryan Lahfa (Jan 29 2021 at 20:22):

example {α β: Type*}
  (f :   α  β)
  (x :  × α) :
  function.uncurry (λ (i : ) (a : α), f (i + 1) a) x =
    (function.uncurry f  prod.map (has_add.add 1) id) x :=
begin
   simp, -- why is it simplified to a single arg function on the lhs?
end

Curious case, I encountered while trying the path @Yakov Pechersky suggested ; and what would be a standard way to move forward?

Yakov Pechersky (Jan 29 2021 at 20:24):

example {α β: Type*}
  (f :   α  β)
  (x :  × α) :
  function.uncurry (λ (i : ) (a : α), f (i + 1) a) x =
    (function.uncurry f  prod.map (has_add.add 1) id) x :=
begin
  cases x,
  simp [add_comm]
end

Ryan Lahfa (Jan 29 2021 at 20:25):

Okay, indeed, that was simple, thanks !

Ryan Lahfa (Jan 29 2021 at 20:31):

While I'm on the subject, is this,

lemma list.map_with_index_core_eq_map_with_index {α β: Type*}
  (f:   α  β) (as: list α) (n: ):
  list.map_with_index_core f n as = list.map_with_index (λ i a, f (n + i) a) as
  :=
begin
  sorry,
end

something that is "in the library" or follows quitely easily from the library or should be proved?

Yakov Pechersky (Jan 29 2021 at 20:32):

lemma list.map_with_index_core_eq {α β : Type*} (l : list α) (f :   α  β) (n : ) :
  l.map_with_index_core f n = l.map_with_index (λ i a, f (i + n) a) :=
begin
  induction l with hd tl hl generalizing f n,
  { simp [list.map_with_index, list.map_with_index_core] },
  { rw [list.map_with_index],
    simp [list.map_with_index_core, hl, add_left_comm, add_assoc, add_comm] }
end

Yakov Pechersky (Jan 29 2021 at 20:32):

I had to prove this, it wasn't in the library

Ryan Lahfa (Jan 29 2021 at 20:36):

Thanks a lot @Yakov Pechersky !

Yakov Pechersky (Jan 29 2021 at 20:49):

Full proof:

Yakov Pechersky (Jan 29 2021 at 20:49):

Feel free to PR any of these to mathlib.

Yakov Pechersky (Jan 29 2021 at 20:51):

The crucial part was to just consider nat.digits b n as some random list. For that, your work to use nat.of_digits_digits and nat.of_digits_eq_foldr was crucial.

Yakov Pechersky (Jan 29 2021 at 20:52):

Everything else is just juggling the various list operations. And writing a distrib lemma.

Ryan Lahfa (Jan 29 2021 at 21:17):

Yakov Pechersky said:

Everything else is just juggling the various list operations. And writing a distrib lemma.

Indeed, but the proofs you wrote are a lot nicer than mine which were a bit tedious and slow I guess, thank you for the example, I learnt a lot from this!

Ryan Lahfa (Jan 29 2021 at 21:17):

Yakov Pechersky said:

Feel free to PR any of these to mathlib.

I will try to open a PR in the next hours with those :)

Yakov Pechersky (Jan 29 2021 at 21:19):

I think it's really neat that a lot of the proofs are just:

  induction l with hd tl hl generalizing l',
  { simp },
  { cases l' with hd' tl',
    { simp },
    { simp [hl] } }

Yakov Pechersky (Jan 29 2021 at 21:19):

That means that there are good simp lemmas elsewhere, and that the list operation makes sense.

Yakov Pechersky (Jan 29 2021 at 21:20):

There can probably be some general list.zip_with congr lemma that expects hypotheses that would prove goals of this type.

Ryan Lahfa (Jan 29 2021 at 22:31):

Here's a first PR: https://github.com/leanprover-community/mathlib/pull/5963@Yakov Pechersky Let me know how do you want to be credited for the proofs :)

Ryan Lahfa (Jan 29 2021 at 22:55):

And here's the second one: https://github.com/leanprover-community/mathlib/pull/5964

Ryan Lahfa (Jan 29 2021 at 23:59):

Follow-up question @Yakov Pechersky — how would you simplify the proof of:

@[simp] lemma map_of_map_with_index_eq_map {α β γ: Type*} (g: β  γ) (f:   α  β) (L: list α):
  list.map g (list.map_with_index f L) = list.map_with_index (λ i a, g (f i a)) L :=
begin
  induction L with hd tl hl generalizing f,
  { simp [list.map_with_index, list.map_with_index_core] },
  {
    rw [list.map_with_index, list.map_with_index,
      list.map_with_index_core, list.map_with_index_core,
      list.map_with_index_core_eq, list.map_with_index_core_eq],
    simp [hl],
  }
end

It seems like I cannot rewrite both sides directly with one application of rw, unsure of a simpler path?

Ryan Lahfa (Jan 30 2021 at 00:01):

Also I suppose there's no easy way to write down the composition as g \circ f and that would still be a currified function?

Ryan Lahfa (Jan 30 2021 at 00:44):

Also, more difficult question I guess about casts, I have a cast in front of some map_with_index which I want to be lifted inside the function, it appears to me that norm_cast does not work out of the box, so I supposed there was some coercion theorem missing here, I tried to write down this:

@[norm_cast] lemma cast_map_index_sum {α β γ}
[add_monoid β] [add_monoid γ]
[has_lift_t β γ] (f:   α  β) (l: list α):
  ((list.map_with_index f l).sum) = ((list.map_with_index (λ x, (f x)) l).sum: γ) :=
  begin
   induction l with hd tl hl,
   unfold list.map_with_index,
   unfold list.map_with_index_core,
   rw list.sum_nil,
   rw list.sum_nil,
   sorry,
   sorry,
  end

but unsure if I'm going through the right way, also I do not see how to clear out coe 0 = 0 — surely, a coercion of 0 is still 0?

Ryan Lahfa (Jan 30 2021 at 00:49):

Here's a MWE of what I'm trying to do:

import data.real.basic
import analysis.special_functions.pow
import data.nat.digits

lemma of_digits_eq_sum_map_with_index (b: ) (L: list ):
  nat.of_digits b L = list.sum (L.map_with_index (λ (i: ) (a: ), a * b ^ i)) := sorry

lemma mwe (b: ) (l: list ): abs ((nat.of_digits b l): ) = abs (list.sum (l.map_with_index (λ i a, a * (b: ) ^ i))) :=
by { rw_mod_cast of_digits_eq_sum_map_with_index, norm_cast }

Mario Carneiro (Jan 30 2021 at 00:56):

If you just assume has_lift_t β γ, that just means you have a coe from beta to gamma that could do anything

Mario Carneiro (Jan 30 2021 at 01:09):

lemma list.map_map_with_index_core {α β γ} (f :   α  β) (g : β  γ) :  (l : list α) n,
  (l.map_with_index_core f n).map g = l.map_with_index_core (λ i a, g (f i a)) n :=
by intros; induction l generalizing n; simp [*, list.map_with_index_core]

lemma list.map_map_with_index {α β γ} (f :   α  β) (g : β  γ) (l : list α) :
  (l.map_with_index f).map g = l.map_with_index (λ i a, g (f i a)) :=
list.map_map_with_index_core _ _ _ _

lemma of_digits_eq_sum_map_with_index (b: ) (L: list ):
  nat.of_digits b L =
  list.sum (L.map_with_index (λ (i: ) (a: ), a * b ^ i)) := sorry

lemma mwe (b: ) (l: list ): abs ((nat.of_digits b l): ) = abs (list.sum (l.map_with_index (λ i a, a * (b: ) ^ i))) :=
begin
  congr,
  rw_mod_cast of_digits_eq_sum_map_with_index,
  have := list.sum_hom _ (nat.cast_add_monoid_hom ),
  simp at this, rw [ this, list.map_map_with_index]
end

Ryan Lahfa (Jan 30 2021 at 21:28):

In the continuity of list lemmas that I'm not sure they exist or should exist, I'm looking now at:

lemma list.le_map_with_index {α β} (f g:   α  β) [canonically_linear_ordered_add_monoid β] (l: list α):
  ( (i: ) (i_le: i < l.length), f i (list.nth_le l i i_le)  g i (list.nth_le l i i_le))  (l.map_with_index f).sum  (l.map_with_index g).sum :=
begin
  intro h_f_le,
  induction l with hd tl hl generalizing f g,
  { simp [list.map_with_index, list.map_with_index_core] },
  { simp [list.map_with_index, list.map_with_index_core],
    rw [list.map_with_index_core_eq, list.map_with_index_core_eq],
    apply add_le_add,
    convert h_f_le 0 _,
    simp,
    convert hl _ _ _,
    intros i hi,
    convert h_f_le (i + 1) _,
    simp [hi],
   },
end

I was able to produce the proof by myself, but I would be interested into feedback regarding whether this is a good statement, and would there be a more natural way to prove it?

Ryan Lahfa (Jan 30 2021 at 22:15):

And another question also:

lemma mwe (b: ) (l: list ):
  list.sum (l.map_with_index (λ (i a: ), abs ((a: ) * (b: ) ^ i))) = list.sum (l.map_with_index (λ (i a: ), (abs a) * (abs b) ^ i)) :=
  begin
    congr,
    ext,
    simp [is_absolute_value.abv_mul, is_absolute_value.abv_pow],
    norm_cast,
    apply mul_nonneg,
    exact nat.zero_le _,
    apply pow_nonneg,
    exact nat.zero_le _,
  end

Is there a way to avoid proving this by proving equality of functions, it seems like a simple rewrite cannot "enter the function" and assume generic parameters to perform its rewrite?

Hanting Zhang (Jan 30 2021 at 23:26):

Btw, your mwe is not a true mwe; you're missing imports. Adding import data.real.basic makes the code error-free. (It's not a huge deal but figuring out what imports need to be added is annoying.)

Ryan Lahfa (Jan 30 2021 at 23:34):

Hanting Zhang said:

Btw, your mwe is not a true mwe; you're missing imports. Adding import data.real.basic makes the code error-free. (It's not a huge deal but figuring out what imports need to be added is annoying.)

Sorry for that, I'll change it

Mario Carneiro (Jan 30 2021 at 23:35):

simp can enter binders like this

Mario Carneiro (Jan 30 2021 at 23:39):

The issue here seems to be that you are using a lemma that isn't suitable for simp; with abs_mul it works fine

lemma mwe (b: ) (l: list ):
  list.sum (l.map_with_index (λ (i a: ), abs ((a: ) * (b: ) ^ i))) = list.sum (l.map_with_index (λ (i a: ), (abs a) * (abs b) ^ i)) :=
by simp [abs_mul, abs_pow]

Ryan Lahfa (Jan 30 2021 at 23:46):

Mario Carneiro said:

The issue here seems to be that you are using a lemma that isn't suitable for simp; with abs_mul it works fine

lemma mwe (b: ) (l: list ):
  list.sum (l.map_with_index (λ (i a: ), abs ((a: ) * (b: ) ^ i))) = list.sum (l.map_with_index (λ (i a: ), (abs a) * (abs b) ^ i)) :=
by simp [abs_mul, abs_pow]

Alright, makes sense

Ryan Lahfa (Jan 30 2021 at 23:50):

@Mario Carneiro what does it mean for a lemma to be suitable for simp ?
e.g. what makes abs_mul more suitable than abv_mul ?

Mario Carneiro (Jan 30 2021 at 23:52):

the lhs of abv_mul starts with a variable, so there is no key

Ryan Lahfa (Jan 30 2021 at 23:52):

Ah alright, indeed, adding explicitly the absolute value makes it work in general context, thanks!

Mario Carneiro (Jan 30 2021 at 23:53):

the fact that abs_mul is about the constant abs rather than an arbitrary absolute value like thing is what makes the difference

Ryan Lahfa (Jan 31 2021 at 22:44):

Hello again, I'm stuck on a le lemmas for lists, here's a MWE:

import data.real.cau_seq
import data.real.basic
import analysis.special_functions.pow


lemma list.le_map_with_index {α β} {f g:   α  β} [canonically_linear_ordered_add_monoid β] {l: list α}:
  ( (i: ) (i_le: i < l.length), f i (list.nth_le l i i_le)  g i (list.nth_le l i i_le))  (l.map_with_index f).sum  (l.map_with_index g).sum := sorry

theorem mwe (b: ) (l: list ) (h_abs:  (a: ), a  l  abs (a: )  1):
list.sum (l.map_with_index (λ (i a: ), (abs (a: )) * ((b: ) ^ i)))
   list.sum (l.map_with_index (λ (i a: ), (b: ) ^ i)) :=
begin
  sorry,
end

I have an (ugly) proof of the le_map_with_index lemma, but it looks like for some reason (I suppose casts/coercions), I cannot apply it very well to this situation, I can convert from h_abs to the proper required hypothesis easily (and can add it), but a simple apply does not even work with an unification error

Yakov Pechersky (Jan 31 2021 at 22:45):

I would try to convert map_with_index to zip_with and then write some lemmas about how separable functions of zip_with are the same as some combination of zip_with of mapped lists

Ryan Lahfa (Jan 31 2021 at 22:53):

Yakov Pechersky said:

I would try to convert map_with_index to zip_with and then write some lemmas about how separable functions of zip_with are the same as some combination of zip_with of mapped lists

Got it, I will try something

Yakov Pechersky (Jan 31 2021 at 23:59):

Almost there! What's the lemma you would use to solve that le-quality?

Yakov Pechersky (Jan 31 2021 at 23:59):

That's on your branch#raito-mapwithindex-list

Ryan Lahfa (Feb 01 2021 at 00:03):

Wouldn't le_abs_self be the right lemma?

Ryan Lahfa (Feb 01 2021 at 00:05):

Something like, le_trans + le_abs_self + mul_le_mul_left and some simplification of *1

Ryan Lahfa (Feb 01 2021 at 00:23):

Hmm, annoyingly it will require to do case work on b pos with zero, unsure if there is a faster way

Ryan Lahfa (Feb 01 2021 at 00:23):

Thanks a lot though @Yakov Pechersky !

Yakov Pechersky (Feb 01 2021 at 00:25):

Also, if you notice, your h_abs can be simplified to a \le 1, because of course a \le abs a

Ryan Lahfa (Feb 01 2021 at 00:27):

Indeed, because those are natural integers

Yakov Pechersky (Feb 01 2021 at 00:29):

I mean, that's true for anything abs is defined on: docs#le_abs_self

Ryan Lahfa (Feb 01 2021 at 00:31):

Hmm, then the simplification is not always possible, right?
I mean, a \le 1 does not imply abs a \le 1 in general under the theorem: a \le abs a, or am I too tired? :D

Ryan Lahfa (Feb 01 2021 at 00:32):

(counterexample: -5 \le 1, though 5 \ge 1 even though -5 \le 5, right?)

Yakov Pechersky (Feb 01 2021 at 00:48):

I mean, here, you're always coercing from nat. So abs (a : \R) must be (a : \R)

Yakov Pechersky (Feb 01 2021 at 00:48):

So here is the final proof:

theorem mwe (b: ) (l: list ) (hle :  (a: ), a  l  a  1):
list.sum (l.map_with_index (λ (i a: ), (abs (a: )) * ((b: ) ^ i)))
   list.sum (l.map_with_index (λ (i a: ), (b: ) ^ i)) :=
begin
  have : (λ (i a : ), abs (a : ) * (b : ) ^ i) = (λ (i a : ), (b : ) ^ i * abs (a : )),
    { ext, rw mul_comm },
  rw this, clear this,
  simp [map_with_index_eq_enum_map_uncurry, enum_eq_zip_range],
  rw [zip_with_separable],
  have : take l.length = take (map (pow (b : )) (range l.length)).length,
    { congr' 1, simp },
  rw [this, take_length], clear this,
  refine sum_ext_le (by simp) _,
  intros i hi,
  let k :  := l.nth_le i (by simpa using hi),
  suffices : (b : ) ^ i * k  b ^ i * 1,
    { simpa using this },
  refine mul_le_mul (le_refl _) _ (by simp) (by simp),
  simpa using hle k (nth_le_mem _ _ _)
end

Yakov Pechersky (Feb 01 2021 at 00:49):

As you can see, I changed the h_abs to hle

Ryan Lahfa (Feb 01 2021 at 00:50):

Yakov Pechersky said:

I mean, here, you're always coercing from nat. So abs (a : \R) must be (a : \R)

Ah! I completely agree, yes

Yakov Pechersky (Feb 01 2021 at 00:51):

So even in the l.map_with_index lambda, you don't need the abs.

Ryan Lahfa (Feb 01 2021 at 00:56):

Indeed, but this is only true in this "minimum" example, in general, I'm using arbitrary absolute values on which abs a != a even for nat, so I can only bound them through le_abv_self if I'm not wrong

Yakov Pechersky (Feb 01 2021 at 00:58):

Again, feel free to PR any of the lemmas. What was crucial here was to prove some general lemmas about sums over lists, or about zip_with. The zip_with_separable allowed us to get it to a form where we can throw away oen of the lists. But it can't be a simp lemma because we have (in your branch, at least) map_uncurry_zip_eq_zip_with as a simp lemma. And Jannis's comment here: https://github.com/leanprover-community/mathlib/pull/5974#discussion_r567509308 indicates that it probably shouldn't be! Additionally, he also came up with zip_with_separable, but in reverse:
https://github.com/leanprover-community/mathlib/pull/5974#discussion_r567508963

Ryan Lahfa (Feb 01 2021 at 01:00):

Yakov Pechersky said:

Again, feel free to PR any of the lemmas. What was crucial here was to prove some general lemmas about sums over lists, or about zip_with. The zip_with_separable allowed us to get it to a form where we can throw away oen of the lists. But it can't be a simp lemma because we have (in your branch, at least) map_uncurry_zip_eq_zip_with as a simp lemma. And Jannis's comment here: https://github.com/leanprover-community/mathlib/pull/5974#discussion_r567509308 indicates that it probably shouldn't be! Additionally, he also came up with zip_with_separable, but in reverse:
https://github.com/leanprover-community/mathlib/pull/5974#discussion_r567508963

Sure thing! Thanks for the comments and insights, I missed the fact that Jannis reviewed the PR, will address those comments tomorrow hopefully

Ryan Lahfa (Feb 01 2021 at 13:38):

Therefore, as we removed the simp attribute on map-uncurry lemma, should I add the simp on zip_with_map @Yakov Pechersky @Jannis Limperg ?

Jannis Limperg (Feb 01 2021 at 15:17):

I'd say yes. It's a bit specialised, but zip_with is not a common symbol so we won't run into performance issues.

Ryan Lahfa (Feb 01 2021 at 15:18):

Alright, will do the change!

Ryan Lahfa (Feb 01 2021 at 15:19):

Done :)


Last updated: Dec 20 2023 at 11:08 UTC