Zulip Chat Archive
Stream: mathlib4
Topic: finishing up ofScientific in norm_num
Thomas Murrills (Mar 17 2023 at 04:27):
@Mario Carneiro , I'm finally finishing this up now that Rat.cast
is reducible and I can get rid of LawfulOfScientific
.
-
I noticed that we're using
assumeInstancesCommute
instead ofassertInstancesCommute
and I'm assuming the same philosophy applies here: that is, the lemmas are going to have an argument saying that the given DivisionRing OfScientific instance is equal toRat.ofScientific
followed byRat.cast
, and we just supply this asEq.refl
and let it fail in the kernel if it must (which it almost surely won't). Just wanted to run it by you in case we actually wanted a defeq check here for some reason. -
Is it better performance-wise for those lemmas to have a hypothesis stating that the instance per se is equal to a canonical version, or one saying that for all arguments, its field applied to arguments is equal to the canonical one applied to arguments? (Or, that the single field of the instance is equal to the canonical function as a function?) Not sure if this difference matters or not.
-
Likewise, does it matter (performance-wise) whether the argument to
Eq.refl
is the canonical ofScientific or the division-ring one? Does it affect what we unfold and how?
Mario Carneiro (Mar 17 2023 at 04:43):
Yes to (1). What is "those lemmas" in (2)?
Mario Carneiro (Mar 17 2023 at 04:43):
the lemmas should not have any arguments asserting equality of instances
Mario Carneiro (Mar 17 2023 at 04:44):
Qq has the =Q
type to represent definitional equalities that should hold. There is no equivalent expression in object-level lean
Thomas Murrills (Mar 17 2023 at 04:49):
Mario Carneiro said:
... What is "those lemmas" in (2)?
the lemmas should not have any arguments asserting equality of instances
These are the IsRat_ofScientific_of_true
and isNat_ofScientific_of_false
lemmas, which need one of the following as a hypothesis
(assert equality of instances)σα = ⟨fun m s e ↦ (Rat.ofScientific m s e : α)⟩
OfScientific.ofScientific (α := α) = fun m s e ↦ (Rat.ofScientific m s e : α)
∀ m s e, OfScientific.ofScientific (α := α) m s e = (Rat.ofScientific m s e : α)
Thomas Murrills (Mar 17 2023 at 04:50):
(Ignore the current commits, I pushed right before I saw your message, lol)
Mario Carneiro (Mar 17 2023 at 04:51):
link?
Thomas Murrills (Mar 17 2023 at 04:51):
Mario Carneiro (Mar 17 2023 at 04:53):
theorem isRat_ofScientific_of_true [DivisionRing α] [σα : OfScientific α] : {m e : ℕ} → {n : ℤ} → {d : ℕ} → σα = ⟨fun m s e ↦ (Rat.ofScientific m s e : α)⟩ → IsRat (mkRat m (10 ^ e) : α) n d → IsRat (OfScientific.ofScientific (α := α) m true e) n d | _, _, _, _, lh, ⟨_, eq⟩ => ⟨_, by simp only [lh, if_true, Rat.ofScientific, Rat.normalize_eq_mkRat] exact eq⟩
You should not be taking a OfScientific α
hypothesis in this theorem, it should be the one derived from the division ring
Thomas Murrills (Mar 17 2023 at 04:53):
Right, true
Thomas Murrills (Mar 17 2023 at 05:00):
Hmm, using option 2, it doesn't like it when I infer an OfScientific instance anymore. Is this...an ok type for the hypothesis in the evaluator? Looks atypical. A little unsure of using explicit projections here. Q((($dα).toOfScientific).ofScientific = fun m s e ↦ (Rat.ofScientific m s e : $α))
Thomas Murrills (Mar 17 2023 at 05:06):
Alright, should be fixed, using option 2. (Can switch to 3 easily, I think)
Thomas Murrills (Mar 17 2023 at 05:08):
...Nevermind, the tests don't work anymore. :sweat_smile:
Thomas Murrills (Mar 17 2023 at 05:09):
(Oh! because Qq was "turned off" for the Eq.refl's. Just forgot to fix those.)
Thomas Murrills (Mar 17 2023 at 05:13):
Hmm, it doesn't like that. "(kernel) deep recursion detected"
Mario Carneiro (Mar 17 2023 at 05:13):
that Q(...) does indeed look atypical
Mario Carneiro (Mar 17 2023 at 05:13):
you should not need anything like that
Mario Carneiro (Mar 17 2023 at 05:14):
I don't really understand what you are trying though
Thomas Murrills (Mar 17 2023 at 05:14):
is an inferScientificOf dα
def the way to go?
Thomas Murrills (Mar 17 2023 at 05:14):
What do you mean?
Mario Carneiro (Mar 17 2023 at 05:15):
I mean you are speaking without context
Mario Carneiro (Mar 17 2023 at 05:15):
you say "it doesn't work" before saying what "it" is
Thomas Murrills (Mar 17 2023 at 05:16):
Right, fair. This test gives the deep recursion error
example : (3.14159 : ℚ) = 314159/100000 := by norm_num1
Mario Carneiro (Mar 17 2023 at 05:17):
that could mean that the term doesn't typecheck
Thomas Murrills (Mar 17 2023 at 05:17):
I think it does? The other tests work
Mario Carneiro (Mar 17 2023 at 05:18):
what did you try? The test itself doesn't tell me much
Mario Carneiro (Mar 17 2023 at 05:19):
Thomas Murrills said:
I think it does? The other tests work
Also, if it's a good test it won't be testing exactly the same code paths as previous tests, so it is possible that norm_num only generates a bad term in this case
Thomas Murrills (Mar 17 2023 at 05:23):
When I was first implementing OfScientific
, there were problems similar to this—I forget what exactly was different with the code to cause them, though. Terms with only a couple digits would work, but terms with more digits would cause a stack overflow. So, there are some tests that just have more digits, and it seems that was illuminating...
Mario Carneiro (Mar 17 2023 at 05:24):
remember that in norm_num type errors don't always manifest as a failure, they can also manifest as unfolding hell. Especially since the theorems being asserted are usually true, if you just normalize everything you often get a proof anyway, but with unacceptable performance and possibly a stack overflow or timeout
Mario Carneiro (Mar 17 2023 at 05:25):
you should check the defeq trace to see if anything surprising is being unfolded
Thomas Murrills (Mar 17 2023 at 05:27):
It's weird. having the extra instances around (as arguments to the lemmas used) seemed to work just fine, even though I'm sure this was abusing something somehow. I'll check that trace...
Thomas Murrills (Mar 17 2023 at 06:08):
Unrelated, but I noticed evalSub
used some of the nice new Qq machinery for its initial checks. Not doing it in this PR, but should the others follow suit? Is that better, performance-wise?
Thomas Murrills (Mar 17 2023 at 06:30):
Interesting. Here's what I've found:
As long as the following two things are the case, the tests work fine. (Fast, even with an absurd number of digits.)
- The lemmas take an explicit
OfScientific α
argument. This means that the type signature looks like this:
theorem isRat_ofScientific_of_true [DivisionRing α] (σα : OfScientific α) :
{m e : ℕ} → {n : ℤ} → {d : ℕ} →
@OfScientific.ofScientific α σα = (fun m s e ↦ (Rat.ofScientific m s e : α)) →
IsRat (mkRat m (10 ^ e) : α) n d → IsRat (@OfScientific.ofScientific α σα m true e) n d
- We use
let σα ← inferOfScientificOfDivisionRing dα
instead of something like(($dα).toOfScientific).ofScientific
orhave σα : Q(OfScientific α) := q(($dα).ofScientific
.
If we use the obvious alternative to either of those—namely just a DivisionRing
argument in the lemmas or a projection to obtain the instance (in any spot, such as the have
)—we end up with (kernel) deep recursion detected
for sufficiently long digit strings.
Thomas Murrills (Mar 17 2023 at 06:48):
I don't know why any of this happens, and I couldn't read a broader message from the defeq traces. For now, though, the version that works is up.
Mario Carneiro (Mar 17 2023 at 07:10):
Here's a minimized version:
theorem isRat_ofScientific_of_true' [DivisionRing α] :
{m e : ℕ} → {n : ℤ} → {d : ℕ} →
IsRat (mkRat m (10 ^ e) : α) n d → IsRat (OfScientific.ofScientific (α := α) m true e) n d
| _, _, _, _, ⟨_, eq⟩ => ⟨_, by
simp only [OfScientific.ofScientific, if_true, Rat.ofScientific, Rat.normalize_eq_mkRat]
exact eq⟩
example : @IsRat ℚ (@DivisionRing.toRing ℚ Rat.divisionRing) 3.14159 (Int.ofNat 314159) 100000 :=
@isRat_ofScientific_of_true' ℚ Rat.divisionRing 314159 5 (Int.ofNat 314159) 100000 sorry
-- (kernel) deep recursion detected
Thomas Murrills (Mar 17 2023 at 07:20):
Minimized (depending on your metrics) further: it's (also?) a mkRat
issue.
theorem foo [DivisionRing α] :
{m e : ℕ} → {n : ℤ} → {d : ℕ} →
IsRat (mkRat m (10 ^ e) : α) n d → IsRat (mkRat m (10 ^ e) : α) n d
| _, _, _, _, h => h
example : @IsRat ℚ (@DivisionRing.toRing ℚ Rat.divisionRing) (mkRat 314159 (10 ^ 5)) (Int.ofNat 314159) 100000 :=
@foo ℚ Rat.divisionRing 314159 5 (Int.ofNat 314159) 100000 sorry
-- (kernel) deep recursion detected
Mario Carneiro (Mar 17 2023 at 07:26):
Minimized some more:
import Mathlib.Data.Rat.Basic
set_option trace.Meta.isDefEq true
example :
@OfScientific.ofScientific Rat Rat.instOfScientificRat 314159 true 5 =
@OfScientific.ofScientific Rat (@DivisionRing.toOfScientific Rat Rat.divisionRing) 314159 true 5 :=
rfl
[Meta.isDefEq] ✅ OfScientific.ofScientific 314159 true 5 =
OfScientific.ofScientific 314159 true
5 =?= OfScientific.ofScientific 314159 true 5 = OfScientific.ofScientific 314159 true 5 ▼
[] ✅ OfScientific.ofScientific 314159 true 5 =?= OfScientific.ofScientific 314159 true 5
[] ✅ OfScientific.ofScientific 314159 true 5 =?= OfScientific.ofScientific 314159 true 5 ▼
[] ✅ Rat.instOfScientificRat.1 314159 true 5 =?= DivisionRing.toOfScientific.1 314159 true 5 ▼
[] ✅ Rat.ofScientific 314159 true 5 =?= ↑(Rat.ofScientific 314159 true 5) ▼
[] ✅ Rat.ofScientific 314159 true 5 =?= RatCast.ratCast (Rat.ofScientific 314159 true 5) ▼
[] ✅ if true = true then Rat.normalize (↑314159) (10 ^ 5)
else ↑(Int.ofNat (314159 * 10 ^ 5)) =?= RatCast.ratCast (Rat.ofScientific 314159 true 5) ▼
[] ✅ Decidable.casesOn (instDecidableEqBool true true) (fun x => ↑(Int.ofNat (314159 * 10 ^ 5))) fun x =>
Rat.normalize (↑314159) (10 ^ 5) =?= RatCast.ratCast (Rat.ofScientific 314159 true 5) ▶
cc: @Gabriel Ebner
Mario Carneiro (Mar 17 2023 at 07:27):
It appears that ratCast
is less unfold-worthy than Rat.ofScientific
Mario Carneiro (Mar 17 2023 at 07:28):
this seems to make the work on making OfScientific
diamonds commute worthless
Thomas Murrills (Mar 17 2023 at 07:28):
Is something similar true for mkRat
vs. ratCast
, then, given the above example which doesn't involve any sort of OfScientific
?
Thomas Murrills (Mar 17 2023 at 07:30):
Mario Carneiro said:
this seems to make the work on making
OfScientific
diamonds commute worthless
well, at least the Eq.refl
proof still succeeds now thanks to Rat.cast
reducibility
Mario Carneiro (Mar 17 2023 at 07:30):
yes, I mean it's the same issue, <big term> = RatCast.ratCast <big term>
should try to unfold ratCast
, not <big term>
Mario Carneiro (Mar 17 2023 at 07:31):
that will only happen if <big term>
can be normalized (e.g. it is a variable). If not lean will go crazy unfolding it
Mario Carneiro (Mar 17 2023 at 07:33):
making Rat.ofScientific
opaque here will probably also work
Thomas Murrills (Mar 17 2023 at 07:35):
does it...make any sense to make RatCast.ratCast
reducible? if it's something we want to unfold in most places? (can you even make structure fields reducible in this specific sense of the word?)
Thomas Murrills (Mar 17 2023 at 07:48):
(also, how would you unfold Rat.ofScientific
to prove anything about it if it were opaque?)
Mario Carneiro (Mar 17 2023 at 07:54):
If it is @[irreducible]
then you can still prove things about it
Mario Carneiro (Mar 17 2023 at 07:54):
and if it is irreducible_def
then it is literally an opaque
but there is an equality to the definition that you can use to prove things
Thomas Murrills (Mar 17 2023 at 07:56):
ah, ok
Mario Carneiro (Mar 17 2023 at 07:56):
more precisely it's not ratCast
itself we want to unfold (it is a projection so this doesn't make sense) but rather @RatCast.ratCast.{0} Rat (@DivisionRing.toRatCast.{0} Rat Rat.divisionRing)
Thomas Murrills (Mar 17 2023 at 07:57):
Yeah, hmmm. It would be neat if we could somehow use the head of an expression to give lean a hint that it ought to readily unfold a specified application.
Or, honestly, just the ability to give structure projections in particular an attribute that makes complete applications of the projections readily unfold. Maybe that's what being a reducible
structure projection should mean.
Thomas Murrills (Mar 17 2023 at 07:59):
(But I can also imagine that having complicated ways to mess with that kind of thing could cause some really unexpected behavior down the line...)
Thomas Murrills (Mar 17 2023 at 08:28):
Anyway, should I really go ahead and make Rat.ofScientific
irreducible? It seems like a strange reason to adjust that definition—it seems like it's "not Rat.ofScientific
's responsibility", so to speak—but I guess this definition is highly specific in its usage, so adjusting it so it can be used this way might make sense.
Though, is it better or worse than the current "non-instance OfScientific α
argument" setup which seems to circumvent this unfolding problem anyway? (Esp. given that the theorems isRat_ofScientific_of_true
, isNat_ofScientific_of_false
are even more niche than Rat.ofScientific
?)
Heather Macbeth (Mar 18 2023 at 10:32):
Looks like !4#1707 finally passes CI after two months of work from @Thomas Murrills. Could I ping this for review from someone who is confident with meta code? I'm looking forward to having this in mathlib!
Thomas Murrills (Mar 18 2023 at 19:05):
Let me give it just ooone more once-over today and then I’ll put it out for review! 😁
Thomas Murrills (Mar 18 2023 at 19:07):
Depending, of course, on what we want to do about this unfolding business.
@Mario Carneiro, should we make the round trip to std4 first and make Rat.ofScientific irreducible, or is letting these niche lemmas have a slightly weird type signature ok? (Or is there a better solution?)
Thomas Murrills (Mar 18 2023 at 23:41):
I've put it out for review because this is technically a review question, and we can always refactor if needed—this certainly won't be the last time NormNum.Basic is touched. :) And/or it can be addressed during review.
Gabriel Ebner (Mar 21 2023 at 21:02):
Anyway, should I really go ahead and make Rat.ofScientific irreducible?
Yes, it won't reduce anyhow since it uses gcd
.
Gabriel Ebner (Mar 21 2023 at 21:11):
See also https://github.com/leanprover/lean4/commit/4167b2466a7dbad3cf489100d7d26559c3be7e21
Thomas Murrills (Mar 21 2023 at 22:15):
Alright, here's a PR making Rat.ofScientific irreducible :) std4#109
Thomas Murrills (Mar 27 2023 at 07:54):
So, I've been fixing !4#1707 to incorporate the std bump (thanks @Heather Macbeth !) and...I've hit (kernel) deep recursion
again. That's on me—I shouldn't have assumed that the proposed solution of making Rat.ofScientific
irreducible would work. I apologize for the wasted time. (Hopefully it wasn't completely wasted, I guess: defeq checks involving Rat.ofScientific
are probably now less costly.) Lesson learned: always test a potential solution before using it.
The error
tl;dr it seems to be linked to the well-foundedness-based definition of gcd somehow.
The error is actually pretty generic. I've minimized it a lot, and I don't know if it's to do with defeq checks after all: the Meta.isDefEq
trace is very short.. (I mean, this is a (kernel)
error, so maybe that makes sense.)
Here's a version of it:
example (h : 10000 ≠ 0) : id (Rat.normalize 314159 10000 h) = Rat.normalize 314159 10000 h :=
rfl -- (kernel) deep recursion detected
Note that the id
(or some other expression equivalent to id) is required. Using a reducible version like @[reducible] def idR (a : α) := a
doesn't change the error; but using fun x => x
instead does eliminate the error. (I'm guessing because of how lambdas are handled, somehow?)
Perhaps we can blame the gcd
in Rat.normalize
:
example : id (Nat.gcd 314159 10000) = Nat.gcd 314159 10000 :=
rfl -- (kernel) deep recursion detected
In turn, this seems to be due to the well-foundedness implementation, not the external code. Here's gcd
copied over but without the extern
:
private def gcdF (x : Nat) : (∀ x₁, x₁ < x → Nat → Nat) → Nat → Nat :=
match x with
| 0 => fun _ y => y
| Nat.succ x => fun f y => f (y % Nat.succ x) (Nat.mod_lt _ (Nat.zero_lt_succ _)) (Nat.succ x)
noncomputable def gcd2 (a b : @& Nat) : Nat :=
WellFounded.fix (measure id).wf gcdF a b
example : id (gcd2 314159 10000) = gcd2 314159 10000 :=
rfl -- (kernel) deep recursion detected
(Note, in case it's informative: in the course of teasing this out, Lean soaked up a lot of memory. Quitting VS code freed up 23 GB. And according to my iStatMenus R/W widget, it continues writing to disk for minutes after any errors have been cleared from the file, and only stops when I quit VS code or killall lean
! Weird.)
(Note: making gcd2
irreducible doesn't help.)
Wrapping tricks
Oddly, the following prevents the error:
example : id (Int.ofNat (Nat.gcd 314159 10000)) = Int.ofNat (Nat.gcd 314159 10000) :=
rfl -- ok
Int.ofNat
is not special here—other expressions like e.g. casts produce the same behavior—but notably, it can't be replaced with simply id
.
I'm guessing that something equivalent to whatever's going on here might be why I can play "tricks" to make the norm_num
extension work (like passing in the instance as an argument to the lemmas).
In case it helps, note that switching the id
and the Int.ofNat
brings back the error—this makes a sort of sense, as now we're back to comparing the two expressions that caused us an issue before once we peel off Int.ofNat
.
example : Int.ofNat (id (Nat.gcd 314159 10000)) = Int.ofNat (Nat.gcd 314159 10000) :=
rfl -- (kernel) deep recursion detected
Heather Macbeth (Mar 27 2023 at 07:59):
I wonder if this could be related to the problems with the @[irreducible]
attribute (reported in lean4#2161)?
Thomas Murrills (Mar 27 2023 at 08:21):
I tried to make gcd2
monadic to test that. Interestingly, before I even got to play around with irreducibility, when trace[debug]
was present, the error vanished:
private def gcdF (x : Nat) : (∀ x₁, x₁ < x → Nat → CoreM Nat) → Nat → CoreM Nat :=
match x with
| 0 => fun _ y => pure y
| Nat.succ x => (fun f y => do trace[debug] "{x}"; f (y % Nat.succ x) (Nat.mod_lt _ (Nat.zero_lt_succ _)) (Nat.succ x))
noncomputable def gcd2 (a b : Nat) : CoreM Nat := do
WellFounded.fix (measure id).wf gcdF a b
theorem x : id (gcd2 314159 10000) = gcd2 314159 10000 := rfl -- ok
But remove trace[debug] "{x}";
(and keep the do
/monadicity), and suddenly it causes a (kernel) deep recursion detected
error! :upside_down:
Thomas Murrills (Mar 27 2023 at 08:23):
(Though, are noncomputable def
s irreducible by default? That would make sense...)
Thomas Murrills (Mar 27 2023 at 08:30):
Is there a way to download the lean4 build from lean4#2162 without building it myself? I'd like to test if that solves the issue, but it takes quite a long time for my computer to compile lean from scratch :sweat_smile:
Thomas Murrills (Mar 27 2023 at 08:46):
Ah, noncomputable defs are not automatically irreducible, i see. So I don't think lean4#2162 will make a difference...
Gabriel Ebner (Mar 27 2023 at 20:40):
Filed as lean4#2171
Thomas Murrills (Mar 28 2023 at 04:12):
Alright, !4#1707 is finally green and out for review (again)! :) It was reviewed by @Floris van Doorn at an earlier stage, so I suppose all that needs review are the commits afterward.
(Note: it was agreed in the meeting to go ahead with the lemmas which take an explicit OfScientific
instance argument. When the (kernel) deep recursion detected
error is addressed, I'll fix the lemmas.)
Last updated: Dec 20 2023 at 11:08 UTC