Zulip Chat Archive
Stream: Is there code for X?
Topic: bUnion for set
Marcus Rossel (Aug 30 2021 at 14:53):
Is there something like docs#finset.bUnion for set
?
Eric Wieser (Aug 30 2021 at 14:56):
Eric Wieser (Aug 30 2021 at 14:57):
Certainly ⋃ (x ∈ s), t x
will do what you want
Kyle Miller (Aug 30 2021 at 15:40):
set.Union
does both unbounded and bounded unions, and theorems about specifically ⋃ (x ∈ s), t x
tend to have bUnion
in the name. This would be the notation that finset.bUnion
would use if it had any.
Internal detail that you should be able to ignore: the notation means
set.Union (λ (x : α), set.Union (λ (H : x ∈ s), t x))
which might not be be quite what you'd expect -- it's interesting how doing the union over x ∈ s
is a way to conditionally control whether the set t x
is included in the union.
Kyle Miller (Aug 30 2021 at 17:44):
Here's another attempt at making a unified notation for set.Union
and finset.bUnion
. Some features:
⋃ x, f x
will tryfinset.univ.bUnion f
if[fintype x]
.⋃ (x ∈ s), f x
will trys.bUnion f
.- These can be mixed, like in
⋃ (x ∈ s) y, f x y
. ⋃ (i < 5), f i
, for example, will tryfinset.univ.bUnion (λ (x : {i | i < 5}), f x)
when the property as a set is afintype
.
All the usual things still work for set
unions.
Kyle Miller (Aug 30 2021 at 17:44):
import data.set.basic
import data.set.lattice
import data.finset.basic
import data.fintype.basic
import data.set.finite
class has_Union {α : Sort*} (x : α) (β : out_param $ Sort*) :=
(Union : β)
local notation `⋃` binders `, ` r:(scoped f, has_Union.Union f) := r
instance set_Union (ι : Sort*) (α : Type*) (f : ι → set α) : has_Union f (set α) :=
{Union := set.Union f}
/-- Ideally this instance would cause an error if it ever were actually used.
It's here to help typeclass inference along -- we need every union in a chain
of unions to have a definition. -/
@[priority 0]
instance finset_Union_default_error (ι : Sort*) (α : Type*) (f : ι → finset α) :
has_Union f (finset α) :=
{Union := ∅}
instance finset_pred (ι : Type*) (p : ι → Prop) [fintype {x | p x}] (α : Type*) [decidable_eq α] (f : ι → finset α) :
has_Union (λ x, has_Union.Union (λ (h : p x), f x)) (finset α) :=
{Union := finset.univ.bUnion (λ (y : {x | p x}), f y)}
instance finset_Union (ι : Type*) [fintype ι] (α : Type*) [decidable_eq α] (f : ι → finset α) :
has_Union f (finset α) :=
{Union := finset.univ.bUnion f}
instance finset_attach_bUnion (ι : Type*) (s : finset ι) (α : Type*) [decidable_eq α] (f : Π (x : ι), x ∈ s → finset α) :
has_Union (λ x, has_Union.Union (λ (h : x ∈ s), f x h)) (finset α) :=
{Union := s.attach.bUnion (λ p, f p p.property)}
instance finset_bUnion (ι : Type*) (s : finset ι) (α : Type*) [decidable_eq α] (f : ι → finset α) :
has_Union (λ x, has_Union.Union (λ (h : x ∈ s), f x)) (finset α) :=
{Union := s.bUnion f}
example (ι : Type*) (s : finset ι) (α : Type*) [decidable_eq α] (f : ι → finset α) :
(⋃ (x ∈ s), f x) = s.bUnion f := rfl
example (ι : Type*) [fintype ι] (α : Type*) [decidable_eq α] (f : ι → finset α) :
(⋃ x, f x) = finset.univ.bUnion f := rfl
example (ι ι' : Type*) (s : finset ι) (s' : finset ι') (α : Type*) [decidable_eq α] (f : ι → ι' → finset α) :
(⋃ (x ∈ s) (y ∈ s'), f x y) = s.bUnion (λ x, s'.bUnion (f x)) := rfl
example (ι ι' : Type*) (s : finset ι) [fintype ι'] (α : Type*) [decidable_eq α] (f : ι → ι' → finset α) :
(⋃ (x ∈ s) y, f x y) = s.bUnion (λ x, finset.univ.bUnion (f x)) := rfl
example (ι : Type*) (α : Type*) [decidable_eq α] (s : finset ι) (f : s → finset α) :
(⋃ x (h : x ∈ s), f ⟨x, h⟩) = s.attach.bUnion f :=
begin
ext, simp [has_Union.Union],
end
example (ι ι' α : Type*) (f : ι → ι' → set α) :
(⋃ x y, f x y) = {z | ∃ x y, z ∈ f x y} :=
begin
ext, simp [has_Union.Union],
end
example (ι α : Type*) (s : set ι) (f : ι → set α) :
(⋃ (x ∈ s), f x) = {z | ∃ x, x ∈ s ∧ z ∈ f x} :=
begin
ext, simp [has_Union.Union],
end
example (α : Type*) (f : ℕ → set α) :
(⋃ (i < 5), f i) = {z | ∃ i, i < 5 ∧ z ∈ f i} :=
begin
ext, simp [has_Union.Union],
end
example (α : Type*) [decidable_eq α] (f : ℕ → finset α) :
(⋃ (i < 5), f i) = (finset.range 5).bUnion f :=
begin
ext, simp [has_Union.Union],
end
Kyle Miller (Aug 30 2021 at 17:45):
There seems to be just one wart: if typeclass inference can't properly resolve has_Union
for finset
-valued unions, the union is the empty set. It would be nice if there were a way to make it be an error if this instance were to ever be used. (sorry
is one possibility.)
Kyle Miller (Aug 30 2021 at 17:46):
This has_Union
typeclass could be easily modified for sums, products, and intersections.
Eric Wieser (Aug 30 2021 at 18:56):
Didn't you post this before? Can you find the old thread?
Kyle Miller (Aug 30 2021 at 19:10):
@Eric Wieser I rewrote it from scratch, and I was too lazy to dig up the old thread.
Kyle Miller (Aug 30 2021 at 19:12):
Maybe I should have, because I apparently went through exactly the same exercise with exactly the same results... Not sure exactly what that suggests, but I'll go with maybe it's a workable idea.
Kyle Miller (Aug 30 2021 at 19:13):
Kyle Miller (Jan 18 2022 at 18:25):
I had tried getting this generalized union class to work a while back, but the dependent types made it completely unworkable in practice, which is why it's never seemed to go anywhere.
Kyle Miller (Jan 18 2022 at 18:25):
I took another crack at it this morning with a new approach, and it seems like it might actually work.
Kyle Miller (Jan 18 2022 at 18:26):
import data.set.basic
import data.set.lattice
import data.finset.basic
import data.fintype.basic
import data.set.finite
@[reducible]
def Union_binder (α : Sort*) : Sort* := α
@[inline, reducible]
def Union_binder.mk {α : Sort*} (x : α) : Union_binder α := x
@[simp] lemma Union_binder.mk_eq {α : Sort*} (x : α) : Union_binder.mk x = x := rfl
class has_Union (α : Sort*) (β : out_param $ Sort*) :=
(Union : α → β)
local notation `⋃` binders `, ` r:(scoped f, Union_binder.mk f) := has_Union.Union r
instance set_Union (ι : Sort*) (α : Type*) : has_Union (Union_binder $ ι → set α) (set α) :=
{ Union := set.Union }
instance set_bUnion (ι : Sort*) (p : ι → Prop) (α : Type*) :
has_Union (Union_binder $ Π (x : ι), Union_binder $ p x → set α) (set α) :=
{ Union := λ f, set.Union (λ x, set.Union (λ h, f x h)) }
instance finset_Union (ι : Type*) [fintype ι] (α : Type*) [decidable_eq α] :
has_Union (Union_binder $ ι → finset α) (finset α) :=
{ Union := finset.univ.bUnion }
instance finset_pred (ι : Type*) (p : ι → Prop) [fintype {x | p x}] (α : Type*) [decidable_eq α] :
has_Union (Union_binder $ Π (x : ι), Union_binder $ p x → finset α) (finset α) :=
{ Union := λ f, ({x | p x}).to_finset.attach.bUnion (λ x, f x (by { convert x.property, simp })) }
instance finset_attach_bUnion (ι : Type*) (s : finset ι) (α : Type*) [decidable_eq α] :
has_Union (Union_binder $ Π (x : ι), Union_binder $ x ∈ s → finset α) (finset α) :=
{Union := λ f, s.attach.bUnion (λ p, f p p.property) }
example (ι : Type*) (s : finset ι) (α : Type*) [decidable_eq α] (f : ι → finset α) :
(⋃ (x ∈ s), f x) = s.bUnion f :=
by { ext, simp [has_Union.Union] }
example (ι : Type*) [fintype ι] (α : Type*) [decidable_eq α] (f : ι → finset α) :
(⋃ x, f x) = finset.univ.bUnion f := rfl
example (ι ι' : Type*) (s : finset ι) (s' : finset ι') (α : Type*) [decidable_eq α] (f : ι → ι' → finset α) :
(⋃ (x ∈ s), ⋃ (y ∈ s'), f x y) = s.bUnion (λ x, s'.bUnion (f x)) :=
by { ext, simp [has_Union.Union] }
example (ι ι' : Type*) (s : finset ι) [fintype ι'] (α : Type*) [decidable_eq α] (f : ι → ι' → finset α) :
(⋃ (x ∈ s), ⋃ y, f x y) = s.bUnion (λ x, finset.univ.bUnion (f x)) :=
by { ext, simp [has_Union.Union] }
example (ι : Type*) (α : Type*) [decidable_eq α] (s : finset ι) (f : s → finset α) :
(⋃ x (h : x ∈ s), f ⟨x, h⟩) = s.attach.bUnion f :=
by { ext, simp [has_Union.Union] }
--set_option pp.notation false
example (ι α : Type*) (f : ι → set α) :
(⋃ x, f x) = {z | ∃ x, z ∈ f x} :=
by { ext, simp [has_Union.Union] }
-- Cannot combine unions into a single union with this approach:
example (ι ι' α : Type*) (f : ι → ι' → set α) :
-- (⋃ x y, f x y) = {z | ∃ x y, z ∈ f x y} :=
(⋃ x, ⋃ y, f x y) = {z | ∃ x y, z ∈ f x y} :=
by { ext, simp [has_Union.Union] }
example (ι α : Type*) (s : set ι) (f : ι → set α) :
(⋃ (x ∈ s), f x) = {z | ∃ x, x ∈ s ∧ z ∈ f x} :=
by { ext, simp [has_Union.Union] }
example (α : Type*) (f : ℕ → set α) :
(⋃ (i < 5), f i) = {z | ∃ i, i < 5 ∧ z ∈ f i} :=
by { ext, simp [has_Union.Union] }
example (α : Type*) [decidable_eq α] (f : ℕ → finset α) :
(⋃ (i < 5), f i) = (finset.range 5).bUnion f :=
by { ext, simp [has_Union.Union] }
-- rewrite works:
example (ι α : Type*) (s t : set ι) (f g : ι → set α) (h1 : s = t) (h2 : f = g) :
(⋃ (x ∈ s), f x) = (⋃ (y ∈ t), g y) :=
begin
rw [h1, h2],
end
-- rewrite works:
example (ι α : Type*) [decidable_eq α] (s t : finset ι) (f g : ι → finset α) (h1 : s = t) (h2 : f = g) :
(⋃ (x ∈ s), f x) = (⋃ (y ∈ t), g y) :=
begin
rw [h1, h2],
end
theorem Union_diff {ι β : Type*} (s : set β) (t : ι → set β) :
(⋃ i, t i) \ s = ⋃ i, t i \ s :=
by { ext, simp [has_Union.Union] }
Kyle Miller (Jan 18 2022 at 18:27):
This code demonstrates a has_Union
class that works for both set
and finset
. The only tradeoff seems to be that, like big_operators
, you can only have one binder per union. So rather than ⋃ x y, f x y
, you have to write ⋃ x, ⋃ y, f x y
.
Kyle Miller (Jan 18 2022 at 18:28):
It also works for bounded quantification, like ⋃ (x ∈ s), f x
. To pull this off, there is a special identity function called Union_binder
to help typeclass search know which arguments are meant to be binders.
Kyle Miller (Jan 18 2022 at 18:29):
You can also do ⋃ (i < 5), f i
as a finset
union, which is fun.
Kyle Miller (Jan 18 2022 at 18:31):
I haven't done any large tests of whether there are any further problems when it comes to rewrites -- this version does have the complication that to do a Union for finset
, it has to do s.attach.bUnion
rather than just s.bUnion
universally, since it's not able to peer into the term to decide whether the union is dependent on the proof of finset membership.
Kyle Miller (Jan 18 2022 at 18:34):
In principle, this technique seems like it should work for big_operators
, too. That means we'd be able to write ∑ (x ∈ s), f x
instead of ∑ x in s, f x
. However, that's with the caveat that the sum would be dependent on x ∈ s
... The ∑ (x : α), f x
notation would have the same meaning as before.
Kyle Miller (Jan 18 2022 at 19:02):
I'm not sure exactly why this additional instance would be necessary yet, but if you have
instance finset_set (ι : Type*) (s : set ι) [fintype s] (α : Type*) [decidable_eq α] :
has_Union (Union_binder $ Π (x : ι), Union_binder $ x ∈ s → finset α) (finset α) :=
{ Union := λ f, s.to_finset.attach.bUnion (λ x, f x (by simpa using x.property)) }
then you can do finset
unions over finite set
s:
example (ι α : Type*) [decidable_eq α] (s : set ι) [fintype s] (f : ι → finset α) :
(⋃ (x ∈ s), f x) = s.to_finset.bUnion f :=
by { ext, simp [has_Union.Union] }
(similarly, we'd be able to do sums over finite set
s)
Yaël Dillies (Jan 18 2022 at 19:13):
How does that interact with #11516?
Kyle Miller (Jan 18 2022 at 20:00):
@Yaël Dillies Oh good, you're already refactoring these things. I was going to ask you, actually, if you wanted to take this up and investigate whether we can switch over this (or something like it).
Kyle Miller (Jan 18 2022 at 20:02):
For set
, I am hoping that the only problem is that this setup doesn't have multi-index unions -- do you think that's a big loss? There's probably some way to add them back in, but it's sort of brittle.
Yaël Dillies (Jan 18 2022 at 20:30):
What exactly are you expecting from this typeclass? I can think of a few things in this area:
- Homogeneize between
set
andfinset
. To me that's low priority, and I've actually worked against a bit as I'm trying to phase docs#finset.bUnion out of existence in favor of docs#finset.sup. An alternative scenario would be to actually extend the⨆
/⨅
to finsets, in which case havingfinset.bUnion
/finset.bInter
could make sense, at least in notation. - Make bounded operators the primitive thing. Again, I've worked against that. #11516 is literally about ensuring the library really treats bounded operators as nested operators and not as things of their own.
- Fix the conditional supremum/infimum. This, to me, is high priority, because currently bounded conditional supremums do not do what you expect.
Yaël Dillies (Jan 18 2022 at 20:39):
I can already see a few problems with your implementation:
- It breaks the heuristic of how nested infinitary operators work. Well, big operators already do.
- Your
set_bUnion
instance hasp : ι → Prop
rather thanp : ι → Sort*
. Easily fixable, I think? finset.bUnion
doesn't translate to your notation. It is propeq to it but not defeq. And if it translates, where will the coercion inexample (s : finset ι) (f : ι → finset α) : set α := ⋃ i ∈ s, f i
be inserted?- It is using docs#finset.range rather than docs#finset.Iio. That should be easily fixable.
- I expect it to be generally less robust than the current simpler implementation.
Kyle Miller (Jan 18 2022 at 20:40):
I'd like there to be a uniform style of interface for various fold-like operations on sets, finsets, potentially multisets, and so on.
Unions/intersections are one test for this, but of course Sup/Inf would be great, too. Sums and products are another target, to give more freedom for what we are summing over (we could sum over a multiset with multiplicity).
Kyle Miller (Jan 18 2022 at 20:57):
- It breaks the heuristic of how nested infinitary operators work. Well, big operators already do.
Yes, when I said "this setup doesn't have multi-index unions" that's what I was referring to -- do you think this is a big loss?
- Your
set_bUnion
instance hasp : ι → Prop
rather thanp : ι → Sort*
. Easily fixable, I think?
Why would it be Sort*
-valued? I mean, it could be, but the point of that instance is for bounded unions.
finset.bUnion
doesn't translate to your notation. It is propeq to it but not defeq. And if it translates, where will the coercion inexample (s : finset ι) (f : ι → finset α) : set α := ⋃ i ∈ s, f i
be inserted?
I didn't think it was high-priority for it to be finset.bUnion
, so I'm not sure non-defeq is a problem here (as you said, you're wanting to phase out finset.bUnion
).
For your example, you need to choose the interpretation with a type ascription with the way the code currently is written:
example {α ι : Type*} [decidable_eq α] (s : finset ι) (f : ι → finset α) : set α := ⋃ i ∈ s, (f i : set α)
example {α ι : Type*} [decidable_eq α] (s : finset ι) (f : ι → finset α) : set α := (⋃ i ∈ s, f i : finset α)
- It is using docs#finset.range rather than docs#finset.Iio. That should be easily fixable.
That's the sort of thing why I was going to ask you whether you'd look into this -- you've been looking at lattices and have done a lot with intervals. This is only a proof-of-concept.
- I expect it to be generally less robust than the current simpler implementation.
Please get back with examples where it's less robust, and hopefully they can be fixed. In any case, maybe even if it might be less robust, having a uniform interface for these things will help with ergonomics/usability across mathlib, so on balance it might still be better than what we have.
Last updated: Dec 20 2023 at 11:08 UTC