Zulip Chat Archive
Stream: lean4
Topic: Importing Log breaks termination proof
Jeremy Salwen (Jan 08 2023 at 01:54):
I have been having difficulty with a termination proof. I am seeing weird behavior which seems bug-like. The following MWE works fine:
import Std.Data.List.Basic
open Lean
def examp [DecidableEq α] (delim: List α) (l:List α) (h: delim ≠ []): List α :=
match h₀: l with
| [] => []
| head::tail =>
match List.getRest l delim with
| none => examp delim tail h
| some rest =>
have _: sizeOf rest < sizeOf l := by sorry
examp delim rest h
decreasing_by decreasing_tactic
But if I add the import import Mathlib.Data.Nat.Log
at the top of the file, then I get the "failed to prove termination" error message. Is this a bug, or is something else going on?
James Gallicchio (Jan 08 2023 at 04:08):
If I had to guess, it's because of the (transitively) imported mathlib simp lemmas. Could also be that mathlib extends decreasing_trivial
. Investigating!
James Gallicchio (Jan 08 2023 at 04:36):
I think simp lemmas are the culprit; looks like it's actually not proving for the first recursive call, not the second
James Gallicchio (Jan 08 2023 at 04:46):
New MWE:
import Mathlib.Data.Nat.Order.Basic
example (delim : List α) (h: delim ≠ []) (head : α) (tail : List α)
: (invImage (β := Nat) (fun (a : (_ : List α) ×' delim ≠ []) => PSigma.casesOn a fun x _ => sizeOf x) instWellFoundedRelation).1 { fst := tail, snd := h }
{ fst := head :: tail, snd := h }
:= by decreasing_with simp (config := { arith := true })
Mario Carneiro (Jan 08 2023 at 04:47):
what does set_option trace.Meta.simp.rewrite true
show in each case?
James Gallicchio (Jan 08 2023 at 04:48):
Oh, a useful trace option! Let me see...
Jeremy Salwen (Jan 08 2023 at 04:49):
The weird thing is that it doesn't even present me with any unsolved goals, just that it failed. Let me try that option.
Jeremy Salwen (Jan 08 2023 at 04:49):
James Gallicchio (Jan 08 2023 at 04:50):
Oh, wait a minute
James Gallicchio (Jan 08 2023 at 04:50):
import Mathlib.Data.Nat.Order.Basic
--set_option trace.Meta.Tactic.simp.rewrite true
example (delim : List α) (h: delim ≠ []) (head : α) (tail : List α)
: (invImage (β := Nat) (fun (a : (_ : List α) ×' delim ≠ []) => PSigma.casesOn a fun x _ => sizeOf x) instWellFoundedRelation).1 { fst := tail, snd := h }
{ fst := head :: tail, snd := h }
:= by
simp_wf
--simp (config := { arith := true })
James Gallicchio (Jan 08 2023 at 04:50):
so simp_wf
is changing :)
James Gallicchio (Jan 08 2023 at 04:51):
Wondering if decreasing_tactic
should add an alternative in its first
to simply not try the given tactic...
edit: probably a bad idea
Mario Carneiro (Jan 08 2023 at 04:52):
what does it output? (lean is not in front of me)
Jeremy Salwen (Jan 08 2023 at 04:52):
Paste the whole thing here?
Jeremy Salwen (Jan 08 2023 at 04:55):
[Meta.Tactic.simp.rewrite] unfold InvImage, InvImage WellFoundedRelation.rel (fun a => PSigma.casesOn a fun x x_1 => sizeOf x)
{ fst := tail, snd := h }
{ fst := head :: tail,
snd :=
h } ==> WellFoundedRelation.rel ((fun a => PSigma.casesOn a fun x x_1 => sizeOf x) { fst := tail, snd := h })
((fun a => PSigma.casesOn a fun x x_1 => sizeOf x) { fst := head :: tail, snd := h })
[Meta.Tactic.simp.rewrite] unfold InvImage, InvImage WellFoundedRelation.rel sizeOf
((fun a => PSigma.casesOn a fun x x_1 => sizeOf x) { fst := tail, snd := h })
((fun a => PSigma.casesOn a fun x x_1 => sizeOf x)
{ fst := head :: tail,
snd :=
h }) ==> WellFoundedRelation.rel
(sizeOf ((fun a => PSigma.casesOn a fun x x_1 => sizeOf x) { fst := tail, snd := h }))
(sizeOf ((fun a => PSigma.casesOn a fun x x_1 => sizeOf x) { fst := head :: tail, snd := h }))
[Meta.Tactic.simp.rewrite] unfold Nat.lt_wfRel, Nat.lt_wfRel ==> { rel := Nat.lt, wf := Nat.lt_wfRel.proof_1 }
[Meta.Tactic.simp.rewrite] sizeOf_nat:1000, sizeOf (sizeOf tail) ==> sizeOf tail
[Meta.Tactic.simp.rewrite] @List.cons.sizeOf_spec:1000, sizeOf (head :: tail) ==> 1 + sizeOf head + sizeOf tail
[Meta.Tactic.simp.rewrite] @sizeOf_default:1000, sizeOf head ==> 0
[Meta.Tactic.simp.rewrite] @add_zero:1000, 1 + 0 ==> 1
[Meta.Tactic.simp.rewrite] sizeOf_nat:1000, sizeOf (1 + sizeOf tail) ==> 1 + sizeOf tail
[Meta.Tactic.simp.rewrite] @Nat.lt_eq:1000, Nat.lt (sizeOf tail) (1 + sizeOf tail) ==> sizeOf tail < 1 + sizeOf tail
[Meta.Tactic.simp.rewrite] @lt_add_iff_pos_left:1000, sizeOf tail < 1 + sizeOf tail ==> 0 < 1
James Gallicchio (Jan 08 2023 at 04:56):
simp_wf
is declared as
macro "simp_wf" : tactic =>
`(tactic| simp [invImage, InvImage, Prod.lex, sizeOfWFRel,
measure, Nat.lt_wfRel, WellFoundedRelation.rel])
It's a bit odd to me that it isn't a simp only
; the goal is closed by a simp lemma that can immediately be applied after the wf lemmas
Mario Carneiro (Jan 08 2023 at 04:57):
@Jeremy Salwen is that with or without the mathlib simp lemmas?
Mario Carneiro (Jan 08 2023 at 04:58):
@James Gallicchio It is intentional that it is not simp only
, you should be able to mark your own functions as simp to improve the termination checker locally
Jeremy Salwen (Jan 08 2023 at 04:58):
That is with them:
import Mathlib.Data.Nat.Order.Basic
import Mathlib.Data.Nat.Log
set_option trace.Meta.Tactic.simp.rewrite true
example (delim : List α) (h: delim ≠ []) (head : α) (tail : List α)
: (invImage (β := Nat) (fun (a : (_ : List α) ×' delim ≠ []) => PSigma.casesOn a fun x _ => sizeOf x) instWellFoundedRelation).1 { fst := tail, snd := h }
{ fst := head :: tail, snd := h }
:= by
simp_wf
-- simp (config := { arith := true })
Mario Carneiro (Jan 08 2023 at 04:59):
it looks like it's closing the goal, so what's the problem?
Jeremy Salwen (Jan 08 2023 at 04:59):
But actually for me James's MWE works fine.
Jeremy Salwen (Jan 08 2023 at 04:59):
Yeah ok, let me post from my original MWE
Jeremy Salwen (Jan 08 2023 at 05:01):
Here is the output from the failing original MWE:
[Meta.Tactic.simp.rewrite] unfold InvImage, InvImage WellFoundedRelation.rel fun a =>
PSigma.casesOn a fun x snd =>
sizeOf
x ==> fun a₁ a₂ =>
WellFoundedRelation.rel ((fun a => PSigma.casesOn a fun x snd => sizeOf x) a₁)
((fun a => PSigma.casesOn a fun x snd => sizeOf x) a₂)
[Meta.Tactic.simp.rewrite] @ne_eq:1000, delim ≠ [] ==> ¬delim = []
[Meta.Tactic.simp.rewrite] @ne_eq:1000, delim ≠ [] ==> ¬delim = []
[Meta.Tactic.simp.rewrite] unfold InvImage, InvImage WellFoundedRelation.rel sizeOf ((fun a => PSigma.casesOn a fun x snd => sizeOf x) a₁)
((fun a => PSigma.casesOn a fun x snd => sizeOf x)
a₂) ==> WellFoundedRelation.rel (sizeOf ((fun a => PSigma.casesOn a fun x snd => sizeOf x) a₁))
(sizeOf ((fun a => PSigma.casesOn a fun x snd => sizeOf x) a₂))
[Meta.Tactic.simp.rewrite] unfold Nat.lt_wfRel, Nat.lt_wfRel ==> { rel := Nat.lt, wf := Nat.lt_wfRel.proof_1 }
[Meta.Tactic.simp.rewrite] sizeOf_nat:1000, sizeOf (sizeOf a₁.fst) ==> sizeOf a₁.fst
[Meta.Tactic.simp.rewrite] sizeOf_nat:1000, sizeOf (sizeOf a₂.fst) ==> sizeOf a₂.fst
[Meta.Tactic.simp.rewrite] @Nat.lt_eq:1000, Nat.lt (sizeOf a₁.fst) (sizeOf a₂.fst) ==> sizeOf a₁.fst < sizeOf a₂.fst
[Meta.Tactic.simp.rewrite] @ne_eq:1000, delim ≠ [] ==> ¬delim = []
[Meta.Tactic.simp.rewrite] @ne_eq:1000, delim ≠ [] ==> ¬delim = []
[Meta.Tactic.simp.rewrite] @gt_iff_lt:1000, (fun a₁ a₂ => sizeOf a₁.fst < sizeOf a₂.fst) { fst := tail, snd := h }
{ fst := head :: tail,
snd := h } ==> sizeOf { fst := tail, snd := h }.fst < sizeOf { fst := head :: tail, snd := h }.fst
[Meta.Tactic.simp.rewrite] @List.cons.sizeOf_spec:1000, sizeOf (head :: tail) ==> 1 + sizeOf head + sizeOf tail
[Meta.Tactic.simp.rewrite] @sizeOf_default:1000, sizeOf head ==> 0
[Meta.Tactic.simp.rewrite] @add_zero:1000, 1 + 0 ==> 1
[Meta.Tactic.simp.rewrite] @lt_add_iff_pos_left:1000, sizeOf tail < 1 + sizeOf tail ==> 0 < 1
[Meta.Tactic.simp.rewrite] unfold InvImage, InvImage WellFoundedRelation.rel fun a =>
PSigma.casesOn a fun x snd =>
sizeOf
x ==> fun a₁ a₂ =>
WellFoundedRelation.rel ((fun a => PSigma.casesOn a fun x snd => sizeOf x) a₁)
((fun a => PSigma.casesOn a fun x snd => sizeOf x) a₂)
[Meta.Tactic.simp.rewrite] @ne_eq:1000, delim ≠ [] ==> ¬delim = []
[Meta.Tactic.simp.rewrite] @ne_eq:1000, delim ≠ [] ==> ¬delim = []
[Meta.Tactic.simp.rewrite] unfold InvImage, InvImage WellFoundedRelation.rel sizeOf ((fun a => PSigma.casesOn a fun x snd => sizeOf x) a₁)
((fun a => PSigma.casesOn a fun x snd => sizeOf x)
a₂) ==> WellFoundedRelation.rel (sizeOf ((fun a => PSigma.casesOn a fun x snd => sizeOf x) a₁))
(sizeOf ((fun a => PSigma.casesOn a fun x snd => sizeOf x) a₂))
[Meta.Tactic.simp.rewrite] unfold Nat.lt_wfRel, Nat.lt_wfRel ==> { rel := Nat.lt, wf := Nat.lt_wfRel.proof_1 }
[Meta.Tactic.simp.rewrite] sizeOf_nat:1000, sizeOf (sizeOf a₁.fst) ==> sizeOf a₁.fst
[Meta.Tactic.simp.rewrite] sizeOf_nat:1000, sizeOf (sizeOf a₂.fst) ==> sizeOf a₂.fst
[Meta.Tactic.simp.rewrite] @Nat.lt_eq:1000, Nat.lt (sizeOf a₁.fst) (sizeOf a₂.fst) ==> sizeOf a₁.fst < sizeOf a₂.fst
[Meta.Tactic.simp.rewrite] @ne_eq:1000, delim ≠ [] ==> ¬delim = []
[Meta.Tactic.simp.rewrite] @ne_eq:1000, delim ≠ [] ==> ¬delim = []
[Meta.Tactic.simp.rewrite] @gt_iff_lt:1000, (fun a₁ a₂ => sizeOf a₁.fst < sizeOf a₂.fst) { fst := rest, snd := h }
{ fst := head :: tail,
snd := h } ==> sizeOf { fst := rest, snd := h }.fst < sizeOf { fst := head :: tail, snd := h }.fst
[Meta.Tactic.simp.rewrite] @List.cons.sizeOf_spec:1000, sizeOf (head :: tail) ==> 1 + sizeOf head + sizeOf tail
[Meta.Tactic.simp.rewrite] @sizeOf_default:1000, sizeOf head ==> 0
[Meta.Tactic.simp.rewrite] @add_zero:1000, 1 + 0 ==> 1
Jeremy Salwen (Jan 08 2023 at 05:01):
As I mentioned before the orignal MWE did not display any unsolved goal, just the error message that it couldn't prove termination, as I showed in the screenshot.
Mario Carneiro (Jan 08 2023 at 05:06):
Jeremy Salwen said:
As I mentioned before the orignal MWE did not display any unsolved goal, just the error message that it couldn't prove termination, as I showed in the screenshot.
This is an error reporting issue. I believe it is leaving goals, but they aren't being reported
Mario Carneiro (Jan 08 2023 at 05:06):
one trick you can do is termination_by decreasing_tactic; trace_state
(untested)
Jeremy Salwen (Jan 08 2023 at 05:07):
Hmm, trace_state
seems to only output an empty message :(
James Gallicchio (Jan 08 2023 at 05:09):
aside from the error reporting, I think decreasing_tactic
should probably be modified include a done
alternative. if you add
macro_rules | `(tactic| decreasing_trivial) => `(tactic| done)
to the original MWE it compiles with/without mathlib :)
James Gallicchio (Jan 08 2023 at 05:10):
definitely a bug from user perspective, if simp_wf
is actually intended to apply all simp lemmas
Mario Carneiro (Jan 08 2023 at 05:11):
Here's a workaround by inlining stuff:
import Std.Data.List.Basic
import Mathlib.Data.Nat.Log
open Lean
def examp [DecidableEq α] (delim: List α) (l:List α) (h: delim ≠ []): List α :=
match h₀: l with
| [] => []
| head::tail =>
match List.getRest l delim with
| none => examp delim tail h
| some rest =>
have _x: sizeOf rest < sizeOf l := by sorry
examp delim rest h
decreasing_by
simp_wf
repeat (first | apply Prod.Lex.right | apply Prod.Lex.left)
repeat (first | apply PSigma.Lex.right | apply PSigma.Lex.left)
which shows an error at examp delim rest h
(so it was the second goal after all):
unsolved goals
deliml: List α
h: delim ≠ []
head: α
tail: List α
h₀: l = head :: tail
rest: List α
_x: sizeOf rest < sizeOf l
⊢ sizeOf rest < 1 + sizeOf tail
James Gallicchio (Jan 08 2023 at 05:12):
the first goal is closed by simp_wf
alone. the second goal is closed by decreasing_tactic
.
James Gallicchio (Jan 08 2023 at 05:13):
the issue is decreasing_tactic
doesn't close something that simp_wf
closes by itself :)
Mario Carneiro (Jan 08 2023 at 05:13):
oh I see, adding try decreasing_tactic
at the end makes it pass
James Gallicchio (Jan 08 2023 at 05:14):
i partly suspect simp_wf
should be simp only
, since decreasing_tactic
has a simp
call anyways...
(is there a reasonable way I can test changes to simp_wf
? editing the lean-toolchain's file and reloading doesn't seem to do anything)
Jeremy Salwen (Jan 08 2023 at 05:14):
If I introduce have _: sizeOf rest < 1 + sizeOf tail := by sorry
at the second recursion, it still fails for me.
Mario Carneiro (Jan 08 2023 at 05:15):
not for me, that works (with try decreasing_tactic
)
Jeremy Salwen (Jan 08 2023 at 05:16):
import Std.Data.List.Basic
import Mathlib.Data.Nat.Log
open Lean
def examp [DecidableEq α] (delim: List α) (l:List α) (h: delim ≠ []): List α :=
match h₀: l with
| [] => []
| head::tail =>
match List.getRest l delim with
| none => examp delim tail h
| some rest =>
have _: sizeOf rest < 1 + sizeOf tail := by sorry
have _: sizeOf rest < sizeOf l := by sorry
examp delim rest h
decreasing_by try decreasing_tactic
is what fails for me.
James Gallicchio (Jan 08 2023 at 05:16):
Jeremy Salwen said:
If I introduce
have _: sizeOf rest < 1 + sizeOf tail := by sorry
at the second recursion, it still fails for me.
(I think the first recursive call fails in your MWE, not the second. decreasing_tactic
should succeed on both, but there's a bug that causes it to break with poor error messages)
Jeremy Salwen (Jan 08 2023 at 05:17):
ahh ok
Jeremy Salwen (Jan 08 2023 at 05:17):
decreasing_by try simp_wf; try decreasing_tactic
works
James Gallicchio (Jan 08 2023 at 05:18):
in particular, it does a simp_wf
which closes the goal for rec call 1, but then does another simp
which fails. there's many ways to resolve this bug, I'm not sure what is the right one...
Jeremy Salwen (Jan 08 2023 at 05:18):
Do you know what the bug is?
Mario Carneiro (Jan 08 2023 at 05:25):
I think decreasing_with
should look more like
`(tactic|
(simp_wf
repeat (first | apply Prod.Lex.right | apply Prod.Lex.left)
repeat (first | apply PSigma.Lex.right | apply PSigma.Lex.left)
first
| done
| $ts
| fail "failed to prove termination, possible solutions: ...")
James Gallicchio (Jan 08 2023 at 05:30):
Yeah, that's definitely an option too. I'm sorta of the mind that decreasing_with
should guarantee that it applies the tactic you pass (which would imply that simp_wf
shouldn't be closing goals on its own)
Mario Carneiro (Jan 08 2023 at 05:31):
I think the tactic should try to be monotonic with respect to strengthening tactics
James Gallicchio (Jan 08 2023 at 05:31):
But then again, even simp only
could close a goal :thinking:
Mario Carneiro (Jan 08 2023 at 05:31):
exactly
James Gallicchio (Jan 08 2023 at 05:32):
Then yeah, would support adding it there. we could also make decreasing_trivial
elaborate to done
, but I don't know if trivial implies done...
Mario Carneiro (Jan 08 2023 at 05:32):
it doesn't
Mario Carneiro (Jan 08 2023 at 05:32):
if it did we wouldn't have this error
Mario Carneiro (Jan 08 2023 at 05:32):
none of the decreasing_trivial
tactics succeed on no goals
James Gallicchio (Jan 08 2023 at 05:33):
Yeah. I assume lean3's trivial
also fails on empty context, and we should keep that property...
James Gallicchio (Jan 08 2023 at 05:34):
(are you working on a PR for this? or should I?)
Mario Carneiro (Jan 08 2023 at 05:36):
go for it
Mario Carneiro (Jan 08 2023 at 05:36):
don't forget to add a test
Jeremy Salwen (Jan 08 2023 at 05:55):
Thanks for helping with this! :heart:
James Gallicchio (Jan 08 2023 at 05:57):
Thank you for finding the bug! I'm quite surprised this edge case hasn't come up before, honestly
Last updated: Dec 20 2023 at 11:08 UTC