Zulip Chat Archive

Stream: general

Topic: minimum of finite set


Reid Barton (May 06 2018 at 02:41):

I have a finset of rationals which I know is nonempty. How do I find its minimum element?

Mario Carneiro (May 06 2018 at 04:18):

I forget if there is a max operation on finsets, but you can fold with max

Sean Leather (May 06 2018 at 09:28):

I have a finset of rationals which I know is nonempty. How do I find its minimum element?

@Reid Barton See https://github.com/spl/tts/blob/master/src/data/finset/fresh.lean#L121-L154

I'd like to see something like this with max and min in mathlib. I haven't proposed it because I wasn't sure it was worth it, but, since you are also looking for it, perhaps it is.

Sean Leather (May 07 2018 at 11:58):

@Reid Barton Is this something (after replacing max with min) that would be useful to you? I can work on it next.

import data.finset

namespace finset
variables {α : Type*} [decidable_linear_order α]

def max (a : α) : finset α  α :=
  fold _root_.max a id

@[simp]
theorem max_empty {a : α} : max a ( : finset α) = a :=
  by simp [max]

@[simp]
theorem max_insert {a b : α} {s : finset α} (h : a  s)
: max b (insert a s) = _root_.max a (max b s) :=
  by simp [max, fold_insert h]

@[simp]
theorem max_singleton {a b : α} : max b {a} = _root_.max a b :=
  by simp [max]

theorem le_max_left (a : α) (s : finset α) : a  max a s :=
  finset.induction_on s
    (by rw max_empty)
    (λ b s (hnm : b  s) (ih : a  max a s),
     by rw max_insert hnm; exact le_trans ih (le_max_right b (max a s)))

theorem le_max_of_mem {a b : α} {s : finset α} (h : a  b) : a  s  a  max b s :=
  finset.induction_on s
    (λ _, by simp only [max_empty, h])
    (λ c s (hnm : c  s) (ih : a  s  a  max b s) (hins : a  insert c s),
     begin
       rw max_insert hnm,
       cases mem_insert.mp hins,
       case or.inl : p { simp only [p, _root_.le_max_left] },
       case or.inr : p { exact le_trans (ih p) (le_max_right c (max b s)) }
     end)

end finset

Patrick Massot (May 07 2018 at 12:09):

What's this {a} syntax in max b {a}?

Sean Leather (May 07 2018 at 12:11):

It's notation for a singleton.

Sean Leather (May 07 2018 at 12:13):

Actually, to correct myself, {a} is a singleton, but the notation comes from init/core.lean, I think:

/- Type class used to implement the notation { a ∈ c | p a } -/
class has_sep (α : out_param $ Type u) (γ : Type v) :=
(sep : (α  Prop)  γ  γ)

Patrick Massot (May 07 2018 at 12:13):

Oh

Patrick Massot (May 07 2018 at 12:14):

I was confused, I thought it was an implicit parameter right of colon

Patrick Massot (May 07 2018 at 12:14):

The theorem even has "singleton" in its name...

Sean Leather (May 07 2018 at 12:16):

Hmm, maybe it's not from has_sep. I thought I knew, but I don't know anymore.

Sean Leather (May 07 2018 at 12:19):

Ah, right, it's this one:

/- Type classes has_emptyc and has_insert are
   used to implement polymorphic notation for collections.
   Example: {a, b, c}. -/
class has_emptyc   (α : Type u) := (emptyc : α)
class has_insert   (α : out_param $ Type u) (γ : Type v) := (insert : α  γ  γ)

Sean Leather (May 07 2018 at 12:19):

So, if your type has has_emptyc and has_insert instances, you can use the {..., ...} notation.

Sean Leather (May 07 2018 at 12:20):

I agree that it can be confusing.

Patrick Massot (May 07 2018 at 12:23):

It's my fault, I wasn't paying enough attention. I shouldn't be watching Zulip while grading Sage notebooks (@William Stein when will we get auto-grading Sage notebooks so that we can focus on understanding Lean code?).

Sean Leather (May 07 2018 at 12:27):

I don't think it's a great idea to overload {...} in this way, even if it isn't ambiguous to the parser. But it is notationally short, which makes it convenient.

Patrick Massot (May 07 2018 at 12:28):

And it's the maths notations, so it should win over any other interpretation

Sean Leather (May 07 2018 at 12:30):

True, but it doesn't see as much usage as implicit parameters, and it would be better to reserve short notation for things you type/read a lot.

Sean Leather (May 07 2018 at 12:30):

You pretty much only use this notation for singletons or examples.

Sean Leather (May 07 2018 at 13:00):

PR for finset.max and finset.min: https://github.com/leanprover/mathlib/pull/133

Patrick Massot (May 07 2018 at 13:45):

Nice. I will probably use it to build new normed spaces out of finitely many old ones.

Reid Barton (May 07 2018 at 17:00):

@Sean Leather I think this approach with a starting element (a) would be fine for my current application, since I have a particular element which I know belongs to the set. From a general math perspective, though, it's odd not to be able to talk about the minimum of a nonempty set, without first choosing an element of the set.

Reid Barton (May 07 2018 at 17:03):

I've been considering an approach that starts from fold1of an associative, commutative operation on a nonempty multiset, though this fold1 was quite a challenge to define.

Reid Barton (May 07 2018 at 17:07):

But fold1 seems like something one ought to have anyways.

Reid Barton (May 07 2018 at 17:07):

A friend suggested that I could just sort the list (finset.sort) and take the first element

Sean Leather (May 08 2018 at 06:41):

My thinking is that you can write different variations using max [decidable_linear_order α] : α → finset α → α. I believe this definition of max is the most general and least prescriptive since only [decidable_linear_order α] is required.

Sean Leather (May 08 2018 at 06:42):

@Johannes Hölzl suggested max [decidable_linear_order α] [inhabited α] : finset α → α, but I'm not sure if that's better (my response).

Mario Carneiro (May 08 2018 at 06:50):

It is not true that max as suggested by Johannes is the same as max_inhabited that you wrote. If s is nonempty, then max (default A) s is the max of default A and the elements of s, while Johannes's max is the max of the elements of s only. It is closer to Reid's suggested max operation, except that the nonempty constraint is replaced by a default value.

Sean Leather (May 08 2018 at 06:52):

Right. See the bottom of my response.

Mario Carneiro (May 08 2018 at 06:52):

Given johannes's max function, you could recover your max function as sean_max a s := @johannes_max A _ <a> (insert a s)

Sean Leather (May 08 2018 at 06:53):

So they are equivalent?

Mario Carneiro (May 08 2018 at 06:53):

Johannes's definition is more complicated since it requires casing on whether the list is empty

Sean Leather (May 08 2018 at 06:54):

What is the definition?

Mario Carneiro (May 08 2018 at 06:54):

there is not an easy expression for johannes_max using sean_max, you would need another quot.lift

Mario Carneiro (May 08 2018 at 06:54):

You have to start from list, define foldl1 and work your way up

Sean Leather (May 08 2018 at 06:55):

Ah, something like that.

Mario Carneiro (May 08 2018 at 06:55):

Alternatively, you could sean_max over option A

Mario Carneiro (May 08 2018 at 06:55):

where none has the appropriate interpretation as a neutral element for max

Sean Leather (May 08 2018 at 06:56):

You mean map/image some over the finset?

Mario Carneiro (May 08 2018 at 06:56):

and that would give you both the partial max function and the inhabited max function (Johannes's max)

Mario Carneiro (May 08 2018 at 06:56):

Any semigroup operation extends to a monoid if you add a neutral element

Mario Carneiro (May 08 2018 at 06:57):

That means that you can take a semigroup operation like max and extend it to option_max : option A -> option A -> option A that is a monoid operation

Mario Carneiro (May 08 2018 at 06:58):

and then you can finset.prod over this

Mario Carneiro (May 08 2018 at 06:58):

This is essentially the same as adjoining a -inf element to the set

Sean Leather (May 08 2018 at 06:59):

Can you not finset.image some?

Mario Carneiro (May 08 2018 at 06:59):

That would be the input to the fold, yes

Sean Leather (May 08 2018 at 06:59):

Right, okay.

Mario Carneiro (May 08 2018 at 07:00):

The nice thing about this approach is that it naturally handles partiality, you don't need the inhabited thing; but it's easy to implement the inhabited version using option.iget

Sean Leather (May 08 2018 at 07:02):

Personally, I'm not a big fan of inhabited. I haven't found a need for it in anything I've done, and I feel like, if you need an inhabited type, why not use option in the first place?

Sean Leather (May 08 2018 at 07:03):

But I'm happy to see some version of finset.max and finset.min go into mathlib, so I don't feel that strongly about which one... as long as it works with nat. :simple_smile:

Mario Carneiro (May 08 2018 at 07:10):

I have a similar antipathy to inhabited, it's a somewhat lazy way to totalize functions like division on nonempty domains.

Mario Carneiro (May 08 2018 at 07:11):

Arguments in favor would say that composing them is a lot cleaner than the monad stuff

Mario Carneiro (May 08 2018 at 07:11):

i.e. (x + 2) / (y + 2 / z) is easier to read than do a <- 2 / z, (x + 2) / (y + a) or some such

Mario Carneiro (May 08 2018 at 07:13):

The best situation is if you are literally working in a sup_bot semilattice, in which case you don't have to cheat and get a proper function

Johannes Hölzl (May 08 2018 at 08:03):

Working on sup_bot_semilattice is indeed very nice, e.g. https://github.com/johoelzl/mason-stother/blob/master/Sup_fin.lean But we don't have a lot of ordered types having this structure, I guess nat, fin, and ennreal.

Johannes Hölzl (May 08 2018 at 08:08):

I prefer the inhabited version over option. With inhabited you get not only a nicer syntax, but a lot of cases etc can be easily done in the proofs, while for option we are often forced to do it on the term itself.

Sean Leather (May 08 2018 at 13:50):

Just thinking about it a little more... It seems like there are two goals here: one for a max with no requirements other than decidable_linear_order (me) and one for a max that does not need the extra a : α (Johannes and Reid). In the latter case, which is more useful: an inhabited α predicate or a non-empty predicate (e.g. s ≠ ∅ or ∃ a : α, a ∈ s)? I'm guessing the non-empty predicate is more useful because inhabited α doesn't tell you anything about the finset itself, and if you use the inhabited α version and want to know that you are not getting the inhabited.default, you need to know if the finset is empty anyway.

Johannes Hölzl (May 08 2018 at 13:58):

It is the other way round, the inhabited α is more useful from a user perspective. We don't need to give max more information than necessary. For max it is enough to show that α is inhabited, it is not necessary to show that s is inhabited (from which at least nonempty α follows). It is very annoying when the user always needs to provide a prove that s is not empty, this is exactly what I want to avoid.
After all, we are in a theorem prover! Carrying around this information explicitly is not necessary, as usually we can show it in a proof.

Reid Barton (May 08 2018 at 17:12):

My only worry about inhabited is that as, on the one hand, it is not a Prop, and on the other hand, we may not always have a canonical inhabited instance at hand and need to construct one from the knowledge that s is nonempty, we might end up in a situation where Lean thinks max s depends on the choice of inhabited instance, even though we know it doesn't really.

Reid Barton (May 08 2018 at 17:14):

On the other hand, taking a nonempty α instance would make max noncomputable, which is maybe sort of okay for a theorem prover but not very attractive for programming use.

Reid Barton (May 08 2018 at 17:15):

(As it turns out, the theorem I really need has the conclusion ∃x ∈ s, ∀y ∈ s, f y ≤ f x, and for this, we need s to be nonempty anyways.)

Reid Barton (May 08 2018 at 17:17):

The advantage of s ≠ ∅ here is that it is a Prop, but also sufficient to define max computably

Reid Barton (May 08 2018 at 17:36):

I'm kind of tempted to make s ≠ ∅ into a type class argument, actually, but maybe that is going too far?

Johannes Hölzl (May 08 2018 at 17:41):

Of course a theorem like s ≠ ∅ -> ∃x ∈ s, ∀y ∈ s, f y ≤ f x is a nice fit for max with such an assumption. I'm worried about complicated constructions, where you have an elaborate proof that s is not empty. Putting s ≠ ∅ in a type class argument makes also some constructions very unpleasent, like using finset.filter

Reid Barton (May 09 2018 at 16:16):

Maybe the version returning option is most flexible then. If you want the version that uses inhabited, you can apply option.iget.

Sean Leather (May 09 2018 at 19:43):

@Reid Barton Are you referring to max [decidable_linear_order α] : finset α → option α. that returns none for an empty finset and some a for a non-empty finset? That would be fine with me.

Reid Barton (May 09 2018 at 20:20):

Yes. Or more generally, fold1 f : finset α → option α for an associative and commutative operation f.

Sean Leather (May 22 2018 at 12:36):

For those not following along on GitHub, leanprove/mathlib#133 has been updated with new versions of the definitions under discussion:

finset.max [decidable_linear_order α] : finset α  option α
finset.min [decidable_linear_order α] : finset α  option α

I think this proposal is better than the original. Thanks to everyone for the discussion and collaboration. Feedback welcome.

Patrick Massot (May 23 2018 at 15:30):

So, how do we use this new toy? Say I want to talk about the function "max of coordinates on ℝ^n", assuming n is a British natural number, so I get a well defined function from ℝ^n to ℝ

Patrick Massot (May 23 2018 at 15:57):

I can't even find the lemma saying the image of finset is a finset

Johannes Hölzl (May 23 2018 at 16:38):

Its already in the type of finset.image.
To do what you want: λx, (univ.image (λn, x n)).max.iget


Last updated: Dec 20 2023 at 11:08 UTC