feat: solve equalities of instances in convert#40663
Conversation
This PR sets the `preTransparency` of `convert` to `.instances`, which means it will not run congruence on goals that already are equal at `.instances` transparency. In other words, `convert` (without exclamation) should leave no goals of the form `instA.toC = instB.toC`.
There are 387 (~ 8.5%) additional `convert!` calls in Mathlib that could become `convert` after this change, in addition to 2626 (~ 57,8%) where `convert` already succeeds at reducible transparency. I found no cases where `postTransparency := .instances` would be needed instead.
Full breakdown of stats, according to the test script below and passing the captured log messages to `sort | uniq -c`:
* 2626 `convert` would work
* 1513 `convert!` is required
* 387 `convert (preTransparency := .instances)` is required
* 16 cause an error in the test script
The test script consists of replacing the elaborator for `convert` with:
```lean
def sameGoals (gs₁ gs₂ : List MVarId) : MetaM Bool := do
if gs₁.length == gs₂.length then
try
(gs₁.zip gs₂).allM fun (g₁, g₂) => do
-- Check that they agree on the set of free variables, otherwise we get errors.
-- We assume the context in the `convert` case is a subset of the `convert!` case
-- since `convert!` can more agressively unfold and introduce more variables.
if !(← g₁.getDecl).lctx.isSubPrefixOf (← g₂.getDecl).lctx then return false
g₂.withContext <| withReducible <| isDefEq (← g₁.getType) (← g₂.getType)
catch _ => return false
else
return false
elab_rules : tactic
| `(tactic| convert $[!%$expensive]? $cfg $[←%$sym]? $term $[using $n]? $[with $ps?*]?) =>
withMainContext do
let actualConfig ← Convert.elabConfig expensive.isSome cfg
let cheapConfig ← Convert.elabConfig false cfg
let redConfig := { cheapConfig with
preTransparency := .reducible, postTransparency := .reducible }
let preInstConfig := { cheapConfig with
preTransparency := .instances, postTransparency := .reducible }
let postInstConfig := { cheapConfig with
postTransparency := .instances, preTransparency := .reducible }
let patterns := (ps?.getD #[]).toList
let expectedType ← mkFreshExprMVar (mkSort (← getLevel (← getMainTarget)))
let (e, gs) ← elabTermForConvert term expectedType
liftMetaTactic fun g ↦ do
-- Don't retain metavar assignments but do retain messages.
let msgs ← withoutModifyingState do
try
let actualGoals ← g.convert e sym.isSome (n.map (·.getNat)) actualConfig patterns
let redGoals ← g.convert e sym.isSome (n.map (·.getNat)) redConfig patterns
if ← sameGoals actualGoals redGoals then
logInfo m!"convert(reducible)"
else
let preInstGoals ← g.convert e sym.isSome (n.map (·.getNat)) preInstConfig patterns
if ← sameGoals actualGoals preInstGoals then
logInfo m!"convert(preInst)"
else
let postInstGoals ← g.convert e sym.isSome (n.map (·.getNat)) postInstConfig patterns
if ← sameGoals actualGoals postInstGoals then
logInfo m!"convert(postInst)"
else if expensive.isSome then
logInfo m!"convert(expensive)"
else
logInfo m!"convert(error)"
catch e =>
logInfo m!"convert(error {e.toMessageData})"
pure ()
Core.getMessageLog
Core.setMessageLog msgs
return (← g.convert e sym.isSome (n.map (·.getNat)) actualConfig patterns) ++ gs
```
PR summary 652cd22844Import changes for modified filesNo significant changes to the import graph Import changes for all files
|
JovanGerb
left a comment
There was a problem hiding this comment.
I discovered this issue yesterday independently, but it seem you beat me to it!
I think it would be best to ensure convert, convert! and congr! all do the same strength of pre-processing, rather than have convert be stronger. I'm also not so sure about running the assumptionCore discharger at instances transparency.
Maybe an alternative solution would be to simply wrap the particular mvarId.refl call in a withAtLeastTransparency .instances.
Just to be clear, because we have the setup of |
|
Thinking about it a bit more: maybe the right way to go is to have |
|
!bench |
|
Benchmark results for 652cd22 against 9fbe5e0 are in. No significant results found. @Vierkantor
No significant changes detected. |
|
I've seen noise of size ±0.07% before, so I don't feel like investigating the benchmark results is worth the effort. I am happy to have this be reviewed. |
joneugster
left a comment
There was a problem hiding this comment.
Thank you for the PR. I believe we talked about this or other similar changes at the mathlib retreat. Seems very reasonable
There was a problem hiding this comment.
I think you should useguard_target (and maybe guard_hyp) instead of catching an error message which contains the goal the test tries to test for.
| SignType.sign (s.excenterWeights {i} i) = -1 := by | ||
| simp_rw [excenterWeights, Pi.smul_apply, smul_eq_mul, sign_mul] | ||
| convert! one_mul _ | ||
| convert! (preTransparency := reducible) one_mul _ |
There was a problem hiding this comment.
| convert! (preTransparency := reducible) one_mul _ | |
| convert! one_mul _ |
I think all of these three are accidental left-overs, aren't they?
This PR sets the
preTransparencyofconvertto.instances, which means it will not run congruence on goals that already are equal at.instancestransparency. In other words,convert(without exclamation) should leave no goals of the forminstA.toC = instB.toC.There are 387 (~ 8.5%) additional
convert!calls in Mathlib that could becomeconvertafter this change, in addition to 2626 (~ 57,8%) whereconvertalready succeeds at reducible transparency. I found no cases wherepostTransparency := .instanceswould be needed instead.Full breakdown of stats, according to the test script below and passing the captured log messages to
sort | uniq -c:convertwould workconvert!(i.e.convert (postTransparency := .default)) is requiredconvert (preTransparency := .instances)is requiredThe test script consists of replacing the elaborator for
convertwith:Ideally we'd run a benchmark on this PR, or even better one that compares
preTransparencyandpostTransparency. But we don't have a lot of calls to plainconvertyet, since I'd want to wait for the migration until #38071 is merged.