-
Notifications
You must be signed in to change notification settings - Fork 259
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Merged by Bors] - fix: correct bad handling of rat literals in ring_nf
#8836
Conversation
Co-authored-by: Mario Carneiro <mcarneir@andrew.cmu.edu>
Mathlib/Tactic/Ring/RingNF.lean
Outdated
theorem int_rawCast_pos {R} [Ring R] : (Int.rawCast (.ofNat n) : R) = Nat.rawCast n := by simp | ||
theorem int_rawCast_neg {R} [Ring R] : (Int.rawCast (.negOfNat n) : R) = -Nat.rawCast n := by simp | ||
theorem rat_rawCast {R} [DivisionRing R] : | ||
(Rat.rawCast n d : R) = Int.rawCast n / Nat.rawCast d := by simp |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docstring in the other file says that Int.rawCast (.ofNat n)
is not allowed to ever appear; so arguably rat_rawCast
is a bad lemma because it introduces this illegal form, even if only temporarily.
I think the version I had that preserved the invariant was a little clearer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're taking things out of raw cast form here, so it's fine to break those invariants. (More precisely we are temporarily weakening one of the invariants to allow the Int.rawCast (.ofNat n)
form.) The main reason I like this version better is that it's more orthogonal, we only need one case each for negation and division and the int cast. Otherwise we need a cartesian product of cases for all the ways that you can build literals.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just for comparison, my proposal is
theorem int_rawCast_pos {R} [Ring R] : (Int.rawCast (.ofNat n) : R) = Nat.rawCast n := by simp | |
theorem int_rawCast_neg {R} [Ring R] : (Int.rawCast (.negOfNat n) : R) = -Nat.rawCast n := by simp | |
theorem rat_rawCast {R} [DivisionRing R] : | |
(Rat.rawCast n d : R) = Int.rawCast n / Nat.rawCast d := by simp | |
theorem int_rawCast_neg {R} [Ring R] : (Int.rawCast (.negOfNat n) : R) = -Nat.rawCast n := by simp | |
theorem rat_rawCast_pos {R} [DivisionRing R] : | |
(Rat.rawCast (.ofNat n) d : R) = Nat.rawCast n / Nat.rawCast d := by simp | |
theorem rat_rawCast_neg {R} [DivisionRing R] : | |
(Rat.rawCast (.negOfNat n) d : R) = Int.rawCast (.negOfNat n) / Nat.rawCast d := by simp |
which has two benefits:
- It never transitions through an invalid use of
Int.rawCast
- It doesn't accept invalid uses of
Rat.rawCast
This is hardly a cartesian explosion, since we only have three types here anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bhavik posted the following example on Zulip:
import Mathlib.Data.Real.Basic
example {n : ℝ} : (n + 1 / 2) ^ 2 * (n + 1 + 1 / 3) ≤ (n + 1 / 3) * (n + 1) ^ 2 := by ring_nf; sorry
On 86251e6, the remaining goal displays as ⊢ 1 / 3 + n * (OfNat.ofNat 19 / 12) + n ^ 2 * (OfNat.ofNat 7 / 3) + n ^ 3 ≤ 1 / 3 + n * (OfNat.ofNat 5 / 3) + n ^ 2 * (OfNat.ofNat 7 / 3) + n ^ 3
in the infoview. Applying Eric's suggestion, it is ⊢ 1 / 3 + n * (19 / 12) + n ^ 2 * (7 / 3) + n ^ 3 ≤ 1 / 3 + n * (5 / 3) + n ^ 2 * (7 / 3) + n ^ 3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Note, regarding the two simp lemma strategies, I don't really care that much about it. Use whichever one you prefer.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@collares, do you have any ideas for how to write a test for that? It seems the one I added doesn't spot the issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ended up using guard_mgs
with trace_state
test/ring.lean
Outdated
example {n : ℝ} (hn : 0 ≤ n) : (n + 1 / 2) ^ 2 * (n + 1 + 1 / 3) ≤ (n + 1 / 3) * (n + 1) ^ 2 := by | ||
ring_nf | ||
trace_state | ||
sorry |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry | |
exact test_sorry |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test_sorry
has the interesting effect of complaining about unused hypotheses
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you will have to use _hn
etc as well. The reason is because the linter doesn't fire on proofs that contain sorry.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, I've already pushed such a change.
-- `conv_lhs` prevents `ring_nf` picking a bad normalization for both sides. | ||
conv_lhs => ring_nf | ||
|
||
-- We can't use `guard_target =ₛ` here, as while it does detect stray `OfNat`s, it also complains |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it sounds like we need a guard_target mode for pretty printed output rather than expr equality
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or some intermediate "syntactically equal except for instances implicit arguments"
bors r+ |
Reported [on Zulip](https://leanprover.zulipchat.com/#narrow/stream/287929-mathlib4/topic/ring_nf.20returns.20ugly.20literals/near/400988184): > ```lean > import Mathlib.Data.Real.Basic > > local macro_rules | `($x ^ $y) => `(HPow.hPow $x $y) -- Porting note: See issue lean4#2220 > > example {n : ℝ} : (n + 1 / 2) ^ 2 * (n + 1 + 1 / 3) ≤ (n + 1 / 3) * (n + 1) ^ 2 := by > ring_nf > ``` > After the `ring_nf` call, the goal looks like: > ```lean > ↑(Int.ofNat 1) / ↑3 + n * (↑(Int.ofNat 19) / ↑12) + n ^ 2 * (↑(Int.ofNat 7) / ↑3) + n ^ 3 ≤ > ↑(Int.ofNat 1) / ↑3 + n * (↑(Int.ofNat 5) / ↑3) + n ^ 2 * (↑(Int.ofNat 7) / ↑3) + n ^ 3 > ``` This removes the unwanted `Int.ofNat` and coercions. The docstring was apparently incorrect on `Rat.rawCast` (according to @digama0). With that in mind, it's just a case of fixing the local lemmas used to clean up, to reflect the actual invariant instead of the previously-documented one. This also fixes a hang when using `mode = .raw`, by using only a single pass to prevent `1` recursively expanding to `Nat.rawCast 1 + 0`. Co-authored-by: Mario Carneiro <di.gama@gmail.com>
Pull request successfully merged into master. Build succeeded: |
ring_nf
ring_nf
Reported [on Zulip](https://leanprover.zulipchat.com/#narrow/stream/287929-mathlib4/topic/ring_nf.20returns.20ugly.20literals/near/400988184): > ```lean > import Mathlib.Data.Real.Basic > > local macro_rules | `($x ^ $y) => `(HPow.hPow $x $y) -- Porting note: See issue lean4#2220 > > example {n : ℝ} : (n + 1 / 2) ^ 2 * (n + 1 + 1 / 3) ≤ (n + 1 / 3) * (n + 1) ^ 2 := by > ring_nf > ``` > After the `ring_nf` call, the goal looks like: > ```lean > ↑(Int.ofNat 1) / ↑3 + n * (↑(Int.ofNat 19) / ↑12) + n ^ 2 * (↑(Int.ofNat 7) / ↑3) + n ^ 3 ≤ > ↑(Int.ofNat 1) / ↑3 + n * (↑(Int.ofNat 5) / ↑3) + n ^ 2 * (↑(Int.ofNat 7) / ↑3) + n ^ 3 > ``` This removes the unwanted `Int.ofNat` and coercions. The docstring was apparently incorrect on `Rat.rawCast` (according to @digama0). With that in mind, it's just a case of fixing the local lemmas used to clean up, to reflect the actual invariant instead of the previously-documented one. This also fixes a hang when using `mode = .raw`, by using only a single pass to prevent `1` recursively expanding to `Nat.rawCast 1 + 0`. Co-authored-by: Mario Carneiro <di.gama@gmail.com>
Reported on Zulip:
This removes the unwanted
Int.ofNat
and coercions.The docstring was apparently incorrect on
Rat.rawCast
(according to @digama0).With that in mind, it's just a case of fixing the local lemmas used to clean up, to reflect the actual invariant instead of the previously-documented one.
This also fixes a hang when using
mode = .raw
, by using only a single pass to prevent1
recursively expanding toNat.rawCast 1 + 0
.