Zulip Chat Archive

Stream: general

Topic: Proofs that should be automatic


Oliver Nash (Aug 16 2022 at 13:21):

I recently decided that I'm far too tolerant when Lean asks me to prove trivialities. Today I decided I'd start collecting a list of stark examples of proofs that:

  • Lean seems unable to do automatically
  • Lean really should be able to do automatically

Oliver Nash (Aug 16 2022 at 13:22):

I just encountered my first potential example:

import data.set.basic

open set

example {α : Type*} {a : α} {U V : set α} (hUV : U  V = univ) (ha : a  U) : a  V :=
begin
  replace hUV : a  U  a  V, { rw  mem_union, simp [hUV], },
  tauto,
end

Oliver Nash (Aug 16 2022 at 13:22):

My question is: is this really an example or am I missing a tactic / trick?

Yaël Dillies (Aug 16 2022 at 13:29):

You should state U ∪ V = univ as codisjoint U V.

Eric Wieser (Aug 16 2022 at 13:29):

This also works:

example {α : Type*} {a : α} {U V : set α} (hUV : U  V = univ) (ha : a  U) : a  V :=
begin
  rw set.ext_iff at hUV,
  simpa [ha] using hUV a,
end

Oliver Nash (Aug 16 2022 at 13:34):

Yaël Dillies said:

You should state U ∪ V = univ as codisjoint U V.

Maybe this would be a better idiom but I don't think it addresses my point.

Wrenna Robson (Aug 24 2022 at 01:15):

What is the behaviour you expect?

Wrenna Robson (Aug 24 2022 at 01:18):

I'm not at a computer to be able to try it, but I would solve this via:

  • a \mem univ
  • a \mem U union V (rw hUV)
  • a \mem U or a \mem V
  • a \mem V (using ha in some suitable logic lemma)

I'm honestly not sure you could ask for more than that?

Wrenna Robson (Aug 24 2022 at 01:20):

and Eric's proof is imo proof that lean seems to be able to do it.

Wrenna Robson (Aug 24 2022 at 01:21):

do we have codisjoint U V iff forall a, a mem U or a mem V? Because it strikes me that that lemma might be useful here

Yaël Dillies (Aug 24 2022 at 05:04):

Not yet, no.

Kevin Buzzard (Aug 24 2022 at 05:12):

It's not true for things like groups

Oliver Nash (Aug 24 2022 at 07:45):

Wrenna Robson said:

What is the behaviour you expect?

I would like Lean never present me with something this trivial but if it ever did, I would like a tactic like tauto to close it.

Wrenna Robson (Aug 24 2022 at 08:20):

I don't think this is a realistic expectation.

Wrenna Robson (Aug 24 2022 at 08:21):

What if you replace hUV in your example with "a \mem U union V"?

Wrenna Robson (Aug 24 2022 at 08:21):

That feels at least more reasonable to expect to be tautologous.

Yaël Dillies (Aug 24 2022 at 08:22):

Wrenna, this would be automatic in Isabelle. Oliver's point is that we don't have enough automation in mathlib.

Wrenna Robson (Aug 24 2022 at 08:22):

hmm, fair enough

Wrenna Robson (Aug 24 2022 at 08:23):

I would have assumed an automator would trip on the fact that hUV is much stronger than needed so you need to work out what to use.

Wrenna Robson (Aug 24 2022 at 08:23):

how would Isabelle do it; what is the approach they use?

Wrenna Robson (Aug 24 2022 at 08:25):

I don't disagree by the way that we could do with more automation in mathlib.

Oliver Nash (Aug 24 2022 at 08:25):

I know almost nothing about proof automation. My hope in starting this thread was that we might assemble a corpus of test cases that some smart person writing such tactics could use.

Yaël Dillies (Aug 24 2022 at 08:26):

Isabelle has a lot (like a lot) of automation, but I expect this would be solved using sledgehammer, which is a very polyvalent self-replacing tactic. How it works is that you write your proof forwardly and automation tries to fill in the step every time. If it doesn't get it, you give it more hints and try again.

Yaël Dillies (Aug 24 2022 at 08:26):

Someone could try this example in Isabelle, but I expect it needs at most one hint.

Wrenna Robson (Aug 24 2022 at 08:29):

right I would expect it to be able to do it with a single hint! but not from nothing.

Wrenna Robson (Aug 24 2022 at 08:29):

Eric's solution is very nearly that.

Wrenna Robson (Aug 24 2022 at 08:31):

like that simpa is doing a reasonable degree of automation

Yaël Dillies (Aug 24 2022 at 08:42):

But Eric still had to come up with it! I'm sorry to break it to you, but Eric is not your tactic.

Eric Rodriguez (Aug 24 2022 at 08:54):

what about yael_search? :b

On a serious note, these sort of proofs maybe need a self-replacing tactic a-la polyrith, or (i)tauto needs to be able to expand common definitions (at least to do with sets/finsets). simp is very flexible but having to come up with a neat simp(a) proof for everything is not only annoying, but distracting from what you're working towards

Yaël Dillies (Aug 24 2022 at 08:55):

Here, I would expect automation to use docs#set.eq_univ_iff_forall.

Eric Rodriguez (Aug 24 2022 at 08:55):

I recently spent a nontrivial amount of time on the goal (x : Z) (x : N) : x - 1 - k = x - k.succ which is not right

Yaël Dillies (Aug 24 2022 at 08:58):

rw [nat.succ_eq_add_one, int.coe_nat_add, ←sub_sub, sub_right_comm]?

Eric Rodriguez (Aug 24 2022 at 08:58):

again, yael_search is not a tactic. it's clear that they can be done, but why do I need to hassle with this?

Eric Rodriguez (Aug 24 2022 at 08:59):

and sure, maybe succ is an implementation detail and if I rw [succ_eq_add_one] then maybe ring or push_cast, ring will work. but if implementation details keep appearing in goals for whatever reasons then it doesn't really matter this idealism, because it affects the day-to-day

Eric Rodriguez (Aug 24 2022 at 09:02):

(and it means I have to do have : ... := by { ... } instead of have ... := by tactic or ideally even inlining it)

Moritz Doll (Aug 24 2022 at 09:11):

not really a single proof, but I spent recently too much time on finding the correct version of lemmas like docs#mul_le_mul_of_nonneg_left
maybe a variant of congr for inequalities would be nice. So a (b+d) < a (c + d) gets reduced to b < c and a bunch of stuff that positivity can prove

Ruben Van de Velde (Aug 24 2022 at 09:12):

Isn't that tactic#mono?

Yaël Dillies (Aug 24 2022 at 09:12):

I would really love having Coq's generalized rewriting, if only for .

Moritz Doll (Aug 24 2022 at 09:14):

Ruben Van de Velde said:

Isn't that tactic#mono?

I have never heard of that tactic, I will have a look at it

Wrenna Robson (Aug 24 2022 at 09:19):

Eric Rodriguez said:

I recently spent a nontrivial amount of time on the goal (x : Z) (x : N) : x - 1 - k = x - k.succ which is not right

Yes this feels like a really good example.

Wrenna Robson (Aug 24 2022 at 09:44):

By the way - why does mem_union_eq exist? I was expecting mem_union to be the simp lemma here...

Yaël Dillies (Aug 24 2022 at 09:46):

= lemmas and are not quite treated the same. Eric Wieser fixed some of this, but I'm not sure the issue is fully solved.

Wrenna Robson (Aug 24 2022 at 09:47):

example {α : Type*} {a : α} {U V : set α} (hUV : a  U  V) (ha : a  U) : a  V :=
by { simp only [mem_union_eq, or_iff_not_imp_left, hUV] at hUV, tauto }

Wrenna Robson (Aug 24 2022 at 09:47):

This is the best I've got for that, btw

Wrenna Robson (Aug 24 2022 at 09:47):

Or if you allow non-terminal simps:

example {α : Type*} {a : α} {U V : set α} (hUV : a  U  V) (ha : a  U) : a  V :=
by { simp at hUV, tauto }

Kevin Buzzard (Aug 24 2022 at 09:47):

An example which came up yesterday on the discord: given a finset of five integers each of which is at most 50, prove that the sum is at most 50+49+48+47+46. This does not need a proof in a classroom situation but was pretty nasty to do in Lean

Wrenna Robson (Aug 24 2022 at 09:47):

(But I changed hUV, so I cheated.)

Yaël Dillies (Aug 24 2022 at 09:49):

Wrenna Robson said:

Or if you allow non-terminal simps:

example {α : Type*} {a : α} {U V : set α} (hUV : a  U  V) (ha : a  U) : a  V :=
by { simp [or_iff_not_imp_left, hUV] at hUV, tauto }

This is a fine simp, because tauto is a fancy tactic too.

Wrenna Robson (Aug 24 2022 at 09:49):

Incidentally, an example of these is some stuff which SMT solvers can discharge easily, that I was talking about with some of the lean-crypto people.

Wrenna Robson (Aug 24 2022 at 09:52):

example {α : Type*} {a : α} {U V : set α} (hUV : U  V = univ) (ha : a  U) : a  V :=
by { simp [eq_univ_iff_forall] at hUV, specialize hUV a, tauto }

Does this fufill the "one hint" requirement? I would say maybe. Should eq_univ_iff_forall be a simp lemma in general?

Eric Rodriguez (Aug 24 2022 at 09:54):

Kevin Buzzard said:

An example which came up yesterday on the discord: given a finset of five integers each of which is at most 50, prove that the sum is at most 50+49+48+47+46. This does not need a proof in a classroom situation but was pretty nasty to do in Lean

I'd argue that that proof by induction really is the "morally" right thing to do. Some of the subgoal proofs were pretty absurd, though...

Wrenna Robson (Aug 24 2022 at 09:56):

Oh:

example {α : Type*} {a : α} {U V : set α} (hUV : U  V = univ) (ha : a  U) : a  V :=
by { simp [eq_univ_iff_forall] at hUV, finish }

Wrenna Robson (Aug 24 2022 at 09:56):

I mean this is pretty close to full automation I would argue. Arguably it's only not because of the form of hUV.

Wrenna Robson (Aug 24 2022 at 10:00):

Kevin Buzzard said:

An example which came up yesterday on the discord: given a finset of five integers each of which is at most 50, prove that the sum is at most 50+49+48+47+46. This does not need a proof in a classroom situation but was pretty nasty to do in Lean

So this is equivalent to a <= 50, b <= 50, c <= 50, d <= 50, e <= 50, a < b < c < d < e, show that a + b + c + d + e < 240, right?

Wrenna Robson (Aug 24 2022 at 10:00):

Can Lean do it in that form?

Wrenna Robson (Aug 24 2022 at 10:07):

Because this is an easy SMT solve:

; Variable declarations
(declare-fun a () Int)
(declare-fun b () Int)
(declare-fun c () Int)
(declare-fun d () Int)
(declare-fun e () Int)

; Constraints
(assert (<= a 50))
(assert (<= b 50))
(assert (<= c 50))
(assert (<= d 50))
(assert (<= e 50))

(assert (< a b))
(assert (< b c))
(assert (< c d))
(assert (< d e))
(assert (> (+ (+ (+ (+ a b) c) d) e) 240 ))

; Solve
(check-sat)
(get-model)

Wrenna Robson (Aug 24 2022 at 10:07):

https://compsys-tools.ens-lyon.fr/z3/index.php

Wrenna Robson (Aug 24 2022 at 10:10):

Right and indeed:

example {a b c d e : } (ha : a  50) (hb : b  50) (hc : c  50) (hd : d  50) (he : e  50)
(hab : a < b) (hbc : b < c) (hcd : c < d) (hde : d < e) :
  a + b + c + d + e  46 + 47 + 48 + 49 + 50 := by linarith

Johan Commelin (Aug 24 2022 at 10:12):

So we are just 1 wlog away from the general statement.

Johan Commelin (Aug 24 2022 at 10:13):

But I guess wlog struggles with 5!.

Wrenna Robson (Aug 24 2022 at 10:17):

yeah and this wouldn't work if you wanted to prove it for all n, I suppose.

Moritz Doll (Aug 24 2022 at 22:29):

Ruben Van de Velde said:

Isn't that tactic#mono?

thank you so much Ruben. Even though the proofs are now longer by lines, they are so much more readable:

-    refine mul_le_mul_of_nonneg_left _ (inv_nonneg.mpr fac_nonneg),
-    refine pow_le_pow_of_le_left (by positivity) _ _,
-    -- We use linarith to prove |x - y| ≤ |b - a|:
-    exact abs_le_abs (by linarith [hx.2, hy.1]) (by linarith [hx.1, hy.2]),
+    mono*,
+    any_goals { positivity },
+    { exact hx.2 },
+    { exact hy.1 },
+    { linarith [hx.1, hy.2] },

and

-  refine mul_le_mul_of_nonneg_left _ hSup,
-  refine mul_le_mul_of_nonneg_left _ (pow_nonneg (sub_pos.mpr h).le _),
-  exact sub_le_sub_right hx.2 _,
+  mono* with 0  (b - a) ^ n,
+  any_goals { positivity },
+  exact hx.2,

Moritz Doll (Aug 24 2022 at 22:51):

it feels like mono*, any_goals { positivity } is exactly the thing I wanted to have

Bolton Bailey (Sep 06 2022 at 19:01):

What do people think of this as a proof that should be automatic?

lemma nat.min_add_sub (a b : ) : min a b + (a - b) = a := sorry

It feels like if only there was a way to get lean to realize that the definition of min is somehow essentially casework, then this could be solved by tidy or something.

Johan Commelin (Sep 06 2022 at 19:03):

It also needs to understand truncated subtraction.

Johan Commelin (Sep 06 2022 at 19:04):

My first reaction was: "That looks very wrong! The symmetry is broken!" -- And then "Oooooh wait. ℕ... Never mind."

Eric Wieser (Sep 06 2022 at 19:38):

Perhaps a general mechanism to indicate "to prove stuff about foo x y you should case on some_lemma x y" would help with that

Eric Wieser (Sep 06 2022 at 19:39):

We have things like docs#finset.sup_induction already, but those are too strong if your goal also includes a bare s as well as s.sup f

Bolton Bailey (Sep 06 2022 at 19:59):

Eric Wieser said:

Perhaps a general mechanism to indicate "to prove stuff about foo x y you should case on some_lemma x y" would help with that

Maybe a @[case_simp] tag for lemmas of the form a = if b then c else d where c and d are both simpler than a

Heather Macbeth (Sep 06 2022 at 20:48):

Moritz Doll said:

not really a single proof, but I spent recently too much time on finding the correct version of lemmas like docs#mul_le_mul_of_nonneg_left
maybe a variant of congr for inequalities would be nice. So a (b+d) < a (c + d) gets reduced to b < c and a bunch of stuff that positivity can prove

@Moritz Doll I totally agree, I think this "inequality-congr" is the basic mental tactic we use to prove inequalities on paper. I actually have a work-in-progress version of this tactic for teaching, see
https://hrmacbeth.github.io/math2001/01_Proofs_by_Calculation.html#proving-inequalities

It needs more polishing before joining mathlib, though.

Heather Macbeth (Sep 06 2022 at 20:49):

This was actually my motivation for writing positivity.

Yaël Dillies (Sep 06 2022 at 21:24):

Oh this is also the tactic idea I was telling you in DMs, right? What I got from discussing it with Jannis is that we even needed generalised rewriting.

Heather Macbeth (Sep 07 2022 at 02:22):

@Yaël Dillies I think this is easier than generalized rewriting, and more akin to congr than to rw (it's interesting to me that both Moritz and I used congr as our mental analogy).

As I understand it, generalized-rewriting by h : a ≤ b would let you, e.g., reduce a goal ⊢ 3 * a ≤ 5 to a goal ⊢ 3 * b ≤ 5.

By contrast, what I have written is a finishing tactic which would, e.g., prove the goal ⊢ 3 * a ≤ 3 * b.

Heather Macbeth (Sep 07 2022 at 02:39):

It's very much in the same vein as the tactic mono* (discussed upthread), but designed to be "greedy". Compare their behaviour on the goal ⊢ a * x ≤ b * x.

  • the tactic I wrote will go ahead and reduce this to the goals a ≤ b and 0 ≤ x.
  • the tactic mono will tell you that it could reduce this to (x ≤ 0 and b ≤ a) or to (0 ≤ x and a ≤ b), and ask you to disambiguate.

In my experience, paper proofs are usually written in such a way that the greedy behaviour is correct, so it's a more efficient user experience if you don't have to disambiguate (especially when you're using the recursive form mono* and would have disambiguate several times in a single proof).

But they are close enough that it would probably be possible to re-implement my tactic as a variant of mono.

Moritz Doll (Sep 07 2022 at 04:15):

concerning mono* I had a slight issue, where mono produced more positivity-goals than what I had when wrote the proof by hand. This might be related to the "non-greedyness" of mono.

Yaël Dillies (Sep 07 2022 at 07:24):

Ah yes, so this is not quite the tactic I want/need. But still interesting!

Yaël Dillies (Sep 07 2022 at 07:25):

SRL has those huge calc blocks that would become much more readable if I could rewrite along inequalities, but I can't use your tactic there because most calls would be non finishing.

Jannis Limperg (Sep 08 2022 at 09:53):

Bolton Bailey said:

lemma nat.min_add_sub (a b : ) : min a b + (a - b) = a := sorry

Aesop will be able to do this, assuming a good simp set:

import Aesop

@[simp]
theorem Nat.not_le_le (a b : Nat) : ¬ a  b  b  a := sorry

@[simp]
theorem Nat.sub_eq_zero (h : a  b) : a - b = 0 := sorry

@[simp]
theorem Nat.add_sub {a b : Nat} (h : b  a) : b + (a - b) = a := sorry

theorem Nat.min_add_sub (a b : Nat) : min a b + (a - b) = a := by
  aesop (add norm unfold min)

Aesop recognises that the goal, after unfolding min, contains an if, and splits along its condition. (It essentially runs split_ifs eagerly, which imo is usually the right thing to do.) simp does the rest.

Damiano Testa (Sep 08 2022 at 11:09):

I had forgotten about this example. On branch#adomani_remove_subs, this also is a proof:

import tactic.remove_subs

example (a b : ) : min a b + (a - b) = a :=
by remove_subs; simp; assumption

Damiano Testa (Sep 08 2022 at 11:10):

@Jannis Limperg should Nat.not_le_le really be Nat.not_le_lt, with a strict inequality on one side?

Jannis Limperg (Sep 08 2022 at 11:59):

Hehe, yes, of course.

Damiano Testa (Sep 08 2022 at 17:46):

With that lemma, Aesop should be able to prove much more than Nat.min_add_sub :upside_down:

Damiano Testa (Sep 09 2022 at 08:38):

In the spirit of automatization, the nat-subtraction discussion and the example with max/min that @Bolton Bailey and @Eric Wieser mentioned above seem to suggest support for a tactic similar to split_ifs that would look for stuff on which to do cases and actually do it.

Would this be something desirable?

Damiano Testa (Sep 09 2022 at 08:38):

The tactic remove_subs could be easily modified to at least split over max/min but probably even more generally, to fit with some user-flag that tells it what to do cases on.

Eric Rodriguez (Sep 18 2022 at 14:38):

example {α β} {f : α → β} {b : β} : (∃ a, f a = b) ∨ ∀ a, f a ≠ b

finish can do this but we're encouraged not to use it, afair. tauto! and itauto! failing on this is unexplicable (and it's not ne, the ¬ version fails as well)

Michael Stoll (Sep 18 2022 at 14:41):

I suspect tauto does not know about moving negations out of quantifiers?

Bolton Bailey (Sep 18 2022 at 16:44):

To generalize what I was saying before in the context of this example, I feel there should be more emphasis on automatic tactics breaking down logical connectives. Just as tidy calls split when it sees an and and intro when it sees a -> I feel like it should call something likedecidable.or_iff_not_imp_left when it sees the goal is a disjunction (and indeed classical, rw decidable.or_iff_not_imp_left, tidy, closes the goal).

Kevin Buzzard (Sep 18 2022 at 17:37):

@Timothee Lacroix 's AI can solve this one! I just kept selecting the top option. Maybe it got lucky but still!

Mario Carneiro (Sep 18 2022 at 18:36):

itauto doesn't know anything about predicate logic

Wrenna Robson (Sep 19 2022 at 10:10):

Anyone know a way to make this automatic (assuming we're in a classical context):
lemma blahj {a b : Prop} (h : ¬a ↔ ¬b) : a ↔ b := sorry

Wrenna Robson (Sep 19 2022 at 10:11):

It's essentially just one direction of not_iff_not but we don't actually seem to have it.

Yaël Dillies (Sep 19 2022 at 10:11):

Could be called iff.of_not

Mario Carneiro (Sep 19 2022 at 10:23):

not_iff_not.1

Wrenna Robson (Sep 19 2022 at 10:29):

Aye, I mean, that's what I did, but none of the automated tools could find it...

Mario Carneiro (Sep 19 2022 at 10:32):

library search should find one directional versions of iff lemmas

Mario Carneiro (Sep 19 2022 at 10:33):

it might be underpowered in lean 4

Wrenna Robson (Sep 19 2022 at 10:34):

It fails in Lean 3.
lemma blahj {a b : Prop} (h : ¬a ↔ ¬b) : a ↔ b := by library_search!
lemma blahj {a b : Prop} (h : ¬a ↔ ¬b) : a ↔ b := by library_search

both fail.

Scott Morrison (Sep 19 2022 at 10:49):

That's unfortunate, and perhaps counts as a library_search bug...

Michael Stoll (Sep 19 2022 at 11:01):

lemma blahj {a b : Prop} (h : ¬a  ¬b) : a  b := by tauto!

Last updated: Dec 20 2023 at 11:08 UTC