[Merged by Bors] - fix: more closely match mathlib3 behavior in tauto, improving performance#1507
[Merged by Bors] - fix: more closely match mathlib3 behavior in tauto, improving performance#1507
Conversation
Mathlib/Tactic/Tauto.lean
Outdated
| def distribNotAt (h : LocalDecl) (g : MVarId) : MetaM MVarId := do | ||
| def distribNotAt (fvarUserName : Name) (g : MVarId) : MetaM MVarId := g.withContext do | ||
| let ctx ← getLCtx | ||
| let h ← match LocalContext.findFromUserName? ctx fvarUserName with |
There was a problem hiding this comment.
Using user names is flaky (we might have two hypotheses with the same user name, after all). This function should probably return the new fvar id (which you can get from the output of replace).
There was a problem hiding this comment.
If I use FVarIds, then I don't see an easy way to keep track of the remaining hypotheses to work on (the for h in ← getLCtx in distribNot). We'll run distribNotAt on the first FVarId, and then by the time we get around to the second FVarId, it might no longer be present in the local context! I suppose that I can use the information in AssertAfterResult.subst to thread that information through?
There was a problem hiding this comment.
Ok, done. Now we key hypothesis by their fvar ID, and we keep track of renamings by passing around the list of what remains to do. This makes things a bit more complex, but is probably worth it for the increased robustness.
|
When I try your |
|
Never mind, after a |
The main thing I want to fix here is that `tauto` on master currently never fails *quickly*. E.g. this takes many seconds to fail: ```lean example (p : Prop) : p := by tauto ``` The reason is that the final step of `tauto` repeats until it sees a failure, and `constructorMatching` never fails, so we end up going until a timeout is hit. https://github.com/leanprover-community/mathlib4/blob/e78a2694fd2c5b00c16b4139f4a45faa17d2718d/Mathlib/Tactic/Tauto.lean#L150-L153 When I originally wrote this code, I had expected that `constructorMatching` would fail when it finds no match, similar to how the mathlib3 tactic `constructor_matching` fails when it finds no match. To fix the problem, this PR adds a `throwOnNoMatch` optional parameter to the `casesMatching` and `constructorMatching` functions, with default value set to `!recursive`. This means that the `casesm` and `constructorm` tactics will fail on no match, but the `casesm*` and `constructorm*` tactics will succeed on no match. That corresponds exactly to how the analogous tactics work in mathlib3. According to some [zulip discussion](https://leanprover.zulipchat.com/#narrow/stream/287929-mathlib4/topic/casesm.20.2F.20constructorm.20on.20no.20match.3A.20error.20or.20not.3F), other folks agree that the mathlib3 behavior was better. This PR also explicitly sets `(throwOnNoMatch := false)` on the other calls to `constructorMatching` and `casesMatching` in `tautoCore`, because in those cases we don't want a failure. Technically, to exactly match the mathlib3 behavior there, we should be setting `(recursive := true)`, as I do in #1507, but if I do that here without the other performance fixes in that PR, then the proof of `Preorder.toCircularPreorder` gets pushed over the default `maxHeartbeats` timeout.
Mathlib/Tactic/Tauto.lean
Outdated
| let ctx ← getLCtx | ||
| let h ← match LocalContext.find? ctx fvarId with | ||
| | some h => pure h | ||
| | none => throwError "fvar {fvarId.name} not found" |
There was a problem hiding this comment.
| let ctx ← getLCtx | |
| let h ← match LocalContext.find? ctx fvarId with | |
| | some h => pure h | |
| | none => throwError "fvar {fvarId.name} not found" | |
| let some h ← (← getLCtx).find? ctx fvarId | throwError "fvar {fvarId.name} not found" |
There was a problem hiding this comment.
Or even better:
| let ctx ← getLCtx | |
| let h ← match LocalContext.find? ctx fvarId with | |
| | some h => pure h | |
| | none => throwError "fvar {fvarId.name} not found" | |
| let h ← fvarId.getDecl |
|
✌️ dwrensha can now approve this pull request. To approve and merge a pull request, simply reply with |
|
bors r+ |
…ance (#1507) - The `iterateAtMost 3` in `distribNot` currently is not acting as intended, because iterations after the first one still use the same `LocalDecl`, so they have a no-longer-relevant type. This PR fixes that by maintaining a list of fvars that still need to be worked on, updating it on each `distribNot` call, to account for possible renamings. - The `casesMatching` and `constructorMatching` calls in the main loop need to have `recursive := true` in order to match the behavior of their counterparts in mathlib3. See the `some ()` argument here: https://github.com/leanprover-community/mathlib/blob/217daaf45adff6a74a82df0dab1506ed3d5e2eec/src/tactic/tauto.lean#L226. After this change, I observe tactic execution on `Preoder.toCircularPreorder` to drop from 6.8 seconds to 5.5 seconds. Moreover, on this more difficult example, the current code on master fails (times out) while after this PR is applied, it succeeds: ```lean example {α : Type} [Preorder α] (a b c : α) (x₀ x₁ x₂ : Prop) (this1 : x₀ → x₁ → a ≤ c) (this2 : x₁ → x₂ → b ≤ a) (this3 : x₂ → x₀ → c ≤ b) : ((x₀ ∧ ¬b ≤ a) ∧ x₁ ∧ ¬c ≤ b ∨ (x₁ ∧ ¬c ≤ b) ∧ x₂ ∧ ¬a ≤ c ∨ (x₂ ∧ ¬a ≤ c) ∧ x₀ ∧ ¬b ≤ a ↔ (x₀ ∧ x₁ ∨ x₁ ∧ x₂ ∨ x₂ ∧ x₀) ∧ ¬(c ≤ b ∧ b ≤ a ∨ b ≤ a ∧ a ≤ c ∨ a ≤ c ∧ c ≤ b)) := by tauto ``` (I did not add this to tests/Tauto.lean because it takes 8.5 seconds to execute, which seems maybe too expensive for a test.)
|
Pull request successfully merged into master. Build succeeded:
|
The main thing I want to fix here is that `tauto` on master currently never fails *quickly*. E.g. this takes many seconds to fail: ```lean example (p : Prop) : p := by tauto ``` The reason is that the final step of `tauto` repeats until it sees a failure, and `constructorMatching` never fails, so we end up going until a timeout is hit. https://github.com/leanprover-community/mathlib4/blob/e78a2694fd2c5b00c16b4139f4a45faa17d2718d/Mathlib/Tactic/Tauto.lean#L150-L153 When I originally wrote this code, I had expected that `constructorMatching` would fail when it finds no match, similar to how the mathlib3 tactic `constructor_matching` fails when it finds no match. To fix the problem, this PR adds a `throwOnNoMatch` optional parameter to the `casesMatching` and `constructorMatching` functions, with default value set to `!recursive`. This means that the `casesm` and `constructorm` tactics will fail on no match, but the `casesm*` and `constructorm*` tactics will succeed on no match. That corresponds exactly to how the analogous tactics work in mathlib3. According to some [zulip discussion](https://leanprover.zulipchat.com/#narrow/stream/287929-mathlib4/topic/casesm.20.2F.20constructorm.20on.20no.20match.3A.20error.20or.20not.3F), other folks agree that the mathlib3 behavior was better. This PR also explicitly sets `(throwOnNoMatch := false)` on the other calls to `constructorMatching` and `casesMatching` in `tautoCore`, because in those cases we don't want a failure. Technically, to exactly match the mathlib3 behavior there, we should be setting `(recursive := true)`, as I do in #1507, but if I do that here without the other performance fixes in that PR, then the proof of `Preorder.toCircularPreorder` gets pushed over the default `maxHeartbeats` timeout.
…ance (#1507) - The `iterateAtMost 3` in `distribNot` currently is not acting as intended, because iterations after the first one still use the same `LocalDecl`, so they have a no-longer-relevant type. This PR fixes that by maintaining a list of fvars that still need to be worked on, updating it on each `distribNot` call, to account for possible renamings. - The `casesMatching` and `constructorMatching` calls in the main loop need to have `recursive := true` in order to match the behavior of their counterparts in mathlib3. See the `some ()` argument here: https://github.com/leanprover-community/mathlib/blob/217daaf45adff6a74a82df0dab1506ed3d5e2eec/src/tactic/tauto.lean#L226. After this change, I observe tactic execution on `Preoder.toCircularPreorder` to drop from 6.8 seconds to 5.5 seconds. Moreover, on this more difficult example, the current code on master fails (times out) while after this PR is applied, it succeeds: ```lean example {α : Type} [Preorder α] (a b c : α) (x₀ x₁ x₂ : Prop) (this1 : x₀ → x₁ → a ≤ c) (this2 : x₁ → x₂ → b ≤ a) (this3 : x₂ → x₀ → c ≤ b) : ((x₀ ∧ ¬b ≤ a) ∧ x₁ ∧ ¬c ≤ b ∨ (x₁ ∧ ¬c ≤ b) ∧ x₂ ∧ ¬a ≤ c ∨ (x₂ ∧ ¬a ≤ c) ∧ x₀ ∧ ¬b ≤ a ↔ (x₀ ∧ x₁ ∨ x₁ ∧ x₂ ∨ x₂ ∧ x₀) ∧ ¬(c ≤ b ∧ b ≤ a ∨ b ≤ a ∧ a ≤ c ∨ a ≤ c ∧ c ≤ b)) := by tauto ``` (I did not add this to tests/Tauto.lean because it takes 8.5 seconds to execute, which seems maybe too expensive for a test.)
iterateAtMost 3indistribNotcurrently is not acting as intended, because iterations after the first one still use the sameLocalDecl, so they have a no-longer-relevant type. This PR fixes that by maintaining a list of fvars that still need to be worked on, updating it on eachdistribNotcall, to account for possible renamings.casesMatchingandconstructorMatchingcalls in the main loop need to haverecursive := truein order to match the behavior of their counterparts in mathlib3. See thesome ()argument here: https://github.com/leanprover-community/mathlib/blob/217daaf45adff6a74a82df0dab1506ed3d5e2eec/src/tactic/tauto.lean#L226.After this change, I observe tactic execution on
Preoder.toCircularPreorderto drop from 6.8 seconds to 5.5 seconds.Moreover, on this more difficult example, the current code on master fails (times out) while after this PR is applied, it succeeds:
(I did not add this to tests/Tauto.lean because it takes 8.5 seconds to execute, which seems maybe too expensive for a test.)