Zulip Chat Archive

Stream: new members

Topic: Commutativity of Recursive Application


Marcus Rossel (Jan 20 2021 at 17:38):

I have a function that recursively applies a commutative operation and would like to show that this recursive application is also commutative.
Here's an analogous example:

structure bucket := (content : )

def pour (c : ) (b : bucket) : bucket := {content := b.content + c}

lemma pour_comm (c c' : ) (b : bucket) :
pour c (pour c' b) = pour c' (pour c b) :=
sorry

def pour_list : list   bucket  bucket
| [] b := b
| (hd :: tl) b := pour_list tl (pour hd b)

theorem pour_list_comm (cs cs' : list ) (b : bucket) :
pour_list cs (pour_list cs' b) = pour_list cs' (pour_list cs b) :=
begin
  --?
end

The target of the operation is a bucket. Doing a single pour is proven to be commutative. I would like to show that pour_list is also commutative.
Is there a neat way of proving this?
Thanks

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

structure bucket := (content : )

def pour (c : ) (b : bucket) : bucket := {content := b.content + c}

lemma pour_comm (c c' : ) (b : bucket) :
  pour c (pour c' b) = pour c' (pour c b) :=
sorry

def pour_list : list   bucket  bucket
| [] b := b
| (hd :: tl) b := pour_list tl (pour hd b)

lemma pour_list_comm_singleton (cs : list ) (c : ) (b : bucket) :
  pour_list cs (pour c b) = pour c (pour_list cs b) :=
begin
  induction cs with hd tl hl generalizing b,
  { simp [pour_list] },
  { simp [pour_list, hl, pour_comm] }
end

theorem pour_list_comm (cs cs' : list ) (b : bucket) :
  pour_list cs (pour_list cs' b) = pour_list cs' (pour_list cs b) :=
begin
  induction cs' with hd tl hl generalizing cs b,
  { simp [pour_list] },
  { simp [pour_list, pour_list_comm_singleton, hl] }
end

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

I had to prove the simpler pour_list_comm_singleton first.

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

You might like the is_commutative Prop

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

Although that operation is limited to things that are all in the same type. I think Kevin remarked on this at some point (or for is_associative).

Marcus Rossel (Jan 20 2021 at 18:07):

Thanks for your feedback @Yakov Pechersky! Unfortunately I noticed that the example I gave above was actually a bit weaker than what I'm trying to prove. What I actually want to prove is that if I pass two lists to pour_list where one is just a permutation of the other, the results stay the same. So:

import data.list.perm

structure bucket := (content : )

def pour (c : ) (b : bucket) : bucket := {content := b.content + c}

lemma pour_comm (c c' : ) (b : bucket) :
pour c (pour c' b) = pour c' (pour c b) :=
sorry

def pour_list : list   bucket  bucket
| [] b := b
| (hd :: tl) b := pour_list tl (pour hd b)

theorem pour_list_comm (cs cs' : list ) (h : cs ~ cs') (b : bucket) :
pour_list cs b = pour_list cs' b :=
begin
  --?
end

Yakov Pechersky (Jan 20 2021 at 18:09):

Do you know that your pour is associative too?

Marcus Rossel (Jan 20 2021 at 18:09):

Yakov Pechersky said:

Although that operation is limited to things that are all in the same type. I think Kevin remarked on this at some point (or for is_associative).

Yeah, there's right_commutative and left_commutative to work around that.

Marcus Rossel (Jan 20 2021 at 18:10):

Yakov Pechersky said:

Do you know that your pour is associative too?

Yes it is :)

Yakov Pechersky (Jan 20 2021 at 18:11):

Can you show that?

Marcus Rossel (Jan 20 2021 at 18:11):

Yakov Pechersky said:

Can you show that?

Yeah, I think I could come up with a proof of that.

Marcus Rossel (Jan 20 2021 at 18:16):

Ok, I guess I got ahead of myself there. How would associativity work for a function whose output type is not the same as its input type?

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

Right.

Kyle Miller (Jan 20 2021 at 18:20):

Here's a sort of cheating way to prove pour_list_comm:

Marcus Rossel (Jan 20 2021 at 18:20):

I've gotten to the point in the proof where the remaining goal is:

 pour_list (pour b hd) (cs'.erase hd) = pour_list b cs'

(where hd is the head of cs') but I don't know if I took the right path, or how to continue from here.

Eric Wieser (Jan 20 2021 at 18:20):

Can you just use multiset.foldr directly here?

Eric Wieser (Jan 20 2021 at 18:21):

If you've proven docs#left_commutative then it will do the rest of the work for you

Marcus Rossel (Jan 20 2021 at 18:21):

Eric Wieser said:

Can you just use multiset.foldr directly here?

You mean instead of the inductive definition over a list?

Eric Wieser (Jan 20 2021 at 18:21):

Yes

Marcus Rossel (Jan 20 2021 at 18:22):

Yeah, I could do that. How would I get the commutativity of foldr then?

Eric Wieser (Jan 20 2021 at 18:22):

Ah, you want docs#list.perm.foldr_eq

Eric Wieser (Jan 20 2021 at 18:22):

multiset not needed

Marcus Rossel (Jan 20 2021 at 18:23):

wow, list has fold too :D

Eric Wieser (Jan 20 2021 at 18:23):

If you insist on your inductive definition, then you could hopefully prove very easily that it's equal to a fold

Marcus Rossel (Jan 20 2021 at 18:23):

Nah, fold is perfect.
Thanks everybody! I think I'll be able to get it from here :)

Yakov Pechersky (Jan 20 2021 at 18:25):

Yeah, pour_list is best defined as a foldr

Marcus Rossel (Jan 20 2021 at 20:09):

Marcus Rossel said:

Nah, fold is perfect.
Thanks everybody! I think I'll be able to get it from here :)

Well bummer, I'm not able to get it from here ^^
There's a constraint on the commutativity of my pour function, that's giving me a hard time extending the commutativity to pour_list.

Let's say the commutativity of pour depends on a belongs_to property:

import data.list.perm

structure bucket := (content : )

def pour (c : ) (b : bucket) : bucket := {content := b.content + c}

def belongs_to (c : ) (b : bucket) : Prop := sorry

lemma pour_comm (c c' : ) (b : bucket) (h : belongs_to c b) (h' : belongs_to c' b) :
pour c (pour c' b) = pour c' (pour c b) :=
sorry

def pour_list : bucket  list   bucket := list.foldr pour

theorem pour_list_comm (cs cs' : list ) (h : cs ~ cs') (b : bucket) (h_b :  x  cs, belongs_to x b) (h_b' :  x'  cs', belongs_to x' b) :
pour_list b cs' = pour_list b cs' :=
begin
  -- Can't just use `list.perm.foldr_eq` here ...
end

Using list.perm.foldr_eq or list.perm.foldr_eq' in the proof of the theorem doesn't work anymore, because the commutativity of pour doesn't hold for all cs. But assuming that all of the elements in cs and cs' do have that property (required by h_b and h_b'), is there a way to show that pour_list is the same for permutations?

Mario Carneiro (Jan 20 2021 at 20:53):

Do you know belongs_to c (pour c' b)? Otherwise that theorem looks difficult to apply

Mario Carneiro (Jan 20 2021 at 20:53):

in fact I'm not even sure it's true for three elements

Mario Carneiro (Jan 20 2021 at 21:04):

import data.list.perm
inductive bucket
| zero
| one
| two
| three (c : )

def pour (c : ) : bucket  bucket
| bucket.zero := bucket.one
| bucket.one := bucket.two
| _ := bucket.three c

def belongs_to (c : ) (b : bucket) := b = bucket.zero

lemma pour_comm (c c' : ) (b : bucket) (h : belongs_to c b) (h' : belongs_to c' b) :
  pour c (pour c' b) = pour c' (pour c b) :=
by {cases h, cases h', refl}

def pour_list : bucket  list   bucket := list.foldr pour

theorem pour_list_comm : ¬  (cs cs' : list ) (h : cs ~ cs') (b : bucket)
  (h_b :  x  cs, belongs_to x b) (h_b' :  x'  cs', belongs_to x' b),
  pour_list b cs = pour_list b cs' :=
begin
  intro H,
  cases H [1, 0, 0] [0, 1, 0] dec_trivial bucket.zero (λ _ _, rfl) (λ _ _, rfl),
end

Mario Carneiro (Jan 20 2021 at 21:36):

However if you assume that belongs_to is closed under pour then it is true:

import data.list.perm
def bucket : Type := sorry
def pour :  (c : ), bucket  bucket := sorry
def belongs_to :  (c : ) (b : bucket), Prop := sorry
lemma belongs_to.pour {c c' : } {b : bucket} :
  belongs_to c b  belongs_to c' b  belongs_to c (pour c' b) := sorry

lemma pour_comm {c c' : } {b : bucket} (h : belongs_to c b) (h' : belongs_to c' b) :
  pour c (pour c' b) = pour c' (pour c b) := sorry

def pour_list : bucket  list   bucket := list.foldr pour

theorem belongs_to.pour_list {c} {cs : list } {b : bucket}
  (hc : belongs_to c b) (hcs :  x'  cs, belongs_to x' b) :
  belongs_to c (pour_list b cs) :=
begin
  induction cs generalizing c,
  { exact hc },
  { cases list.forall_mem_cons.1 hcs with h1 h2,
    exact belongs_to.pour (cs_ih h2 hc) (cs_ih h2 h1) }
end

theorem pour_list_comm (cs cs' : list ) (h : cs ~ cs') (b : bucket)
  (h_b :  x  cs, belongs_to x b) (h_b' :  x'  cs', belongs_to x' b) :
  pour_list b cs = pour_list b cs' :=
begin
  simp [pour_list],
  induction h,
  { refl },
  case list.perm.cons : _ _ _ _ IH {
    simp [list.foldr], rw IH (list.forall_mem_cons.1 h_b).2 (list.forall_mem_cons.1 h_b').2 },
  case list.perm.swap {
    simp [list.foldr],
    rw [list.forall_mem_cons, list.forall_mem_cons] at h_b,
    rcases h_b with h1, h2, h3⟩,
    exact pour_comm (belongs_to.pour_list h1 h3) (belongs_to.pour_list h2 h3) },
  case list.perm.trans : l1 l2 l3 p1 p2 IH1 IH2 {
    have hc := λ x h, h_b x (p1.mem_iff.2 h),
    exact (IH1 h_b hc).trans (IH2 hc h_b') }
end

Marcus Rossel (Jan 21 2021 at 18:09):

Wow, thanks @Mario Carneiro! The "belongs_to is closed under pour"-part and the special induction for list.perm was what I was missing.


Last updated: Dec 20 2023 at 11:08 UTC