Skip to content
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: more stable choice of representative for atoms in ring and abel #19119

Closed
wants to merge 15 commits into from
6 changes: 3 additions & 3 deletions Mathlib/Tactic/Abel.lean
Original file line number Diff line number Diff line change
Expand Up @@ -247,11 +247,11 @@ theorem term_atom_pfg {α} [AddCommGroup α] (x x' : α) (h : x = x') : x = term
/-- Interpret an expression as an atom for `abel`'s normal form. -/
def evalAtom (e : Expr) : M (NormalExpr × Expr) := do
let { expr := e', proof?, .. } ← (← readThe AtomM.Context).evalAtom e
let i ← AtomM.addAtom e'
let (i, a) ← AtomM.addAtom e'
let p ← match proof? with
| none => iapp ``term_atom #[e]
| some p => iapp ``term_atom_pf #[e, e', p]
return (← term' (← intToExpr 1, 1) (i, e') (← zero'), p)
| some p => iapp ``term_atom_pf #[e, a, p]
return (← term' (← intToExpr 1, 1) (i, a) (← zero'), p)

theorem unfold_sub {α} [SubtractionMonoid α] (a b c : α) (h : a + -b = c) : a - b = c := by
rw [sub_eq_add_neg, h]
Expand Down
2 changes: 1 addition & 1 deletion Mathlib/Tactic/ITauto.lean
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ partial def reify (e : Q(Prop)) : AtomM IProp :=
| ~q(@Ne Prop $a $b) => return .not (.eq (← reify a) (← reify b))
| e =>
if e.isArrow then return .imp (← reify e.bindingDomain!) (← reify e.bindingBody!)
else return .var (← AtomM.addAtom e)
else return .var (← AtomM.addAtom e).1

/-- Once we have a proof object, we have to apply it to the goal. -/
partial def applyProof (g : MVarId) (Γ : NameMap Expr) (p : Proof) : MetaM Unit :=
Expand Down
6 changes: 3 additions & 3 deletions Mathlib/Tactic/Linarith/Preprocessing.lean
Original file line number Diff line number Diff line change
Expand Up @@ -273,12 +273,12 @@ partial def findSquares (s : RBSet (Nat × Bool) lexOrd.compare) (e : Expr) :
| (``HPow.hPow, #[_, _, _, _, a, b]) => match b.numeral? with
| some 2 => do
let s ← findSquares s a
let ai ← AtomM.addAtom a
let (ai, _) ← AtomM.addAtom a
return (s.insert (ai, true))
| _ => e.foldlM findSquares s
| (``HMul.hMul, #[_, _, _, _, a, b]) => do
let ai ← AtomM.addAtom a
let bi ← AtomM.addAtom b
let (ai, _) ← AtomM.addAtom a
let (bi, _) ← AtomM.addAtom b
if ai = bi then do
let s ← findSquares s a
return (s.insert (ai, false))
Expand Down
6 changes: 3 additions & 3 deletions Mathlib/Tactic/Module.lean
Original file line number Diff line number Diff line change
Expand Up @@ -464,9 +464,9 @@ partial def parse (iM : Q(AddCommMonoid $M)) (x : Q($M)) :
pure ⟨0, q(Nat), q(Nat.instSemiring), q(AddCommGroup.toNatModule), [], q(NF.zero_eq_eval $M)⟩
/- anything else should be treated as an atom -/
| _ =>
let k : ℕ ← AtomM.addAtom x
pure ⟨0, q(Nat), q(Nat.instSemiring), q(AddCommGroup.toNatModule), [((q(1), x), k)],
q(NF.atom_eq_eval $x)⟩
let (k, x') ← AtomM.addAtomQ x
pure ⟨0, q(Nat), q(Nat.instSemiring), q(AddCommGroup.toNatModule), [((q(1), x'), k)],
(q(NF.atom_eq_eval $x'):)⟩
hrmacbeth marked this conversation as resolved.
Show resolved Hide resolved

/-- Given expressions `R` and `M` representing types such that `M`'s is a module over `R`'s, and
given two terms `l₁`, `l₂` of type `qNF R M`, i.e. lists of `(Q($R) × Q($M)) × ℕ`s (two `Expr`s
Expand Down
2 changes: 1 addition & 1 deletion Mathlib/Tactic/Polyrith.lean
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ partial def parse {u : Level} {α : Q(Type u)} (sα : Q(CommSemiring $α))
(c : Ring.Cache sα) (e : Q($α)) : AtomM Poly := do
let els := do
try pure <| Poly.const (← (← NormNum.derive e).toRat)
catch _ => pure <| Poly.var (← addAtom e)
catch _ => pure <| Poly.var (← addAtom e).1
let .const n _ := (← withReducible <| whnf e).getAppFn | els
match n, c.rα with
| ``HAdd.hAdd, _ | ``Add.add, _ => match e with
Expand Down
14 changes: 7 additions & 7 deletions Mathlib/Tactic/Ring/Basic.lean
Original file line number Diff line number Diff line change
Expand Up @@ -513,8 +513,8 @@ partial def ExBase.evalNatCast {a : Q(ℕ)} (va : ExBase sℕ a) : AtomM (Result
match va with
| .atom _ => do
let a' : Q($α) := q($a)
let i ← addAtom a'
pure ⟨a', ExBase.atom i, (q(Eq.refl $a') : Expr)⟩
let (i, b') ← addAtomQ a'
pure ⟨b', ExBase.atom i, (q(Eq.refl $b') : Expr)⟩
| .sum va => do
let ⟨_, vc, p⟩ ← va.evalNatCast
pure ⟨_, .sum vc, p⟩
Expand Down Expand Up @@ -952,11 +952,11 @@ Evaluates an atom, an expression where `ring` can find no additional structure.
def evalAtom (e : Q($α)) : AtomM (Result (ExSum sα) e) := do
let r ← (← read).evalAtom e
have e' : Q($α) := r.expr
let i ← addAtom e'
let ve' := (ExBase.atom i (e := e')).toProd (ExProd.mkNat sℕ 1).2 |>.toSum
let (i, a') ← addAtomQ e'
let ve' := (ExBase.atom i (e := a')).toProd (ExProd.mkNat sℕ 1).2 |>.toSum
pure ⟨_, ve', match r.proof? with
| none => (q(atom_pf $e) : Expr)
| some (p : Q($e = $e')) => (q(atom_pf' $p) : Expr)⟩
| some (p : Q($e = $a')) => (q(atom_pf' $p) : Expr)⟩

theorem inv_mul {R} [DivisionRing R] {a₁ a₂ a₃ b₁ b₃ c}
(_ : (a₁⁻¹ : R) = b₁) (_ : (a₃⁻¹ : R) = b₃)
Expand All @@ -978,8 +978,8 @@ variable (dα : Q(DivisionRing $α))
/-- Applies `⁻¹` to a polynomial to get an atom. -/
def evalInvAtom (a : Q($α)) : AtomM (Result (ExBase sα) q($a⁻¹)) := do
let a' : Q($α) := q($a⁻¹)
let i ← addAtom a'
pure ⟨a', ExBase.atom i, (q(Eq.refl $a') : Expr)⟩
let (i, b') ← addAtomQ a'
pure ⟨b', ExBase.atom i, (q(Eq.refl $b') : Expr)⟩

/-- Inverts a polynomial `va` to get a normalized result polynomial.

Expand Down
10 changes: 5 additions & 5 deletions Mathlib/Tactic/TFAE.lean
Original file line number Diff line number Diff line change
Expand Up @@ -298,18 +298,18 @@ elab_rules : tactic
goal.withContext do
let (tfaeListQ, tfaeList) ← getTFAEList (← goal.getType)
closeMainGoal `tfae_finish <|← AtomM.run .reducible do
let is ← tfaeList.mapM AtomM.addAtom
let is ← tfaeList.mapM (fun e ↦ Prod.fst <$> AtomM.addAtom e)
let mut hyps := #[]
for hyp in ← getLocalHyps do
let ty ← whnfR <|← instantiateMVars <|← inferType hyp
if let (``Iff, #[p1, p2]) := ty.getAppFnArgs then
let q1 ← AtomM.addAtom p1
let q2 ← AtomM.addAtom p2
let (q1, _) ← AtomM.addAtom p1
let (q2, _) ← AtomM.addAtom p2
hyps := hyps.push (q1, q2, ← mkAppM ``Iff.mp #[hyp])
hyps := hyps.push (q2, q1, ← mkAppM ``Iff.mpr #[hyp])
else if ty.isArrow then
let q1 ← AtomM.addAtom ty.bindingDomain!
let q2 ← AtomM.addAtom ty.bindingBody!
let (q1, _) ← AtomM.addAtom ty.bindingDomain!
let (q2, _) ← AtomM.addAtom ty.bindingBody!
hyps := hyps.push (q1, q2, hyp)
proveTFAE hyps (← get).atoms is tfaeListQ

Expand Down
20 changes: 15 additions & 5 deletions Mathlib/Util/AtomM.lean
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Authors: Mario Carneiro
-/
import Mathlib.Init
import Lean.Meta.Tactic.Simp.Types
import Qq

/-!
# A monad for tracking and deduplicating atoms
Expand Down Expand Up @@ -44,13 +45,22 @@ def AtomM.run {α : Type} (red : TransparencyMode) (m : AtomM α)
MetaM α :=
(m { red, evalAtom }).run' {}

/-- Get the index corresponding to an atomic expression, if it has already been encountered, or
put it in the list of atoms and return the new index, otherwise. -/
def AtomM.addAtom (e : Expr) : AtomM Nat := do
/-- If an atomic expression has already been encountered, get the index and the stored form of the
atom (which will be defeq at the specified transparency, but not necessarily syntactically equal).
If the atomic expression has *not* already been encountered, store it in the list of atoms, and
return the new index (and the stored form of the atom, which will be itself). -/
def AtomM.addAtom (e : Expr) : AtomM (Nat × Expr) := do
hrmacbeth marked this conversation as resolved.
Show resolved Hide resolved
let c ← get
for h : i in [:c.atoms.size] do
if ← withTransparency (← read).red <| isDefEq e c.atoms[i] then
return i
modifyGet fun c ↦ (c.atoms.size, { c with atoms := c.atoms.push e })
return (i, c.atoms[i])
modifyGet fun c ↦ ((c.atoms.size, e), { c with atoms := c.atoms.push e })

open Qq in
/-- If an atomic expression has already been encountered, get the index and the stored form of the
atom (which will be defeq at the specified transparency, but not necessarily syntactically equal).
If the atomic expression has *not* already been encountered, store it in the list of atoms, and
return the new index (and the stored form of the atom, which will be itself). -/
hrmacbeth marked this conversation as resolved.
Show resolved Hide resolved
def AtomM.addAtomQ {u : Level} {α : Q(Type u)} (e : Q($α)) : AtomM (Nat × Q($α)) := AtomM.addAtom e
hrmacbeth marked this conversation as resolved.
Show resolved Hide resolved

end Mathlib.Tactic
19 changes: 19 additions & 0 deletions MathlibTest/abel.lean
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,22 @@ example [AddCommGroup α] (x y z : α) (h : False) (w : x - x = y + z) : False :
abel_nf at *
guard_hyp w : 0 = y + z
assumption

/-
Test that when `abel_nf` normalizes multiple expressions which contain a particular atom, it uses a
form for that atom which is consistent between expression.

Note: This test is useless unless done at `=ₛ` (the level of syntactic equality); doing this test at
`=` (the level of reducible equality) would not catch the erroneous old behaviour (normalizing one
expression to `2 • a` and the other to `2 • x`). But to get syntactic equality, we need to make the
hrmacbeth marked this conversation as resolved.
Show resolved Hide resolved
typeclass arguments agree, which requires some handholding in typeclass inference ... which is the
reason for all the locally deleted instances here.
-/
attribute [-instance] Int.instAddGroup AddGroupWithOne.toAddGroup in
example (x : ℤ) (R : ℤ → ℤ → Prop) (hR : Reflexive R) : True := by
let a := x
have : R (a + x) (x + a) := by
abel_nf
guard_target =ₛ R ((2:ℤ) • a) ((2:ℤ) • a)
apply hR
trivial
22 changes: 22 additions & 0 deletions MathlibTest/ring.lean
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,28 @@ example (x : ℝ) (hx : x ≠ 0) :
field_simp
ring

/-
Test that when `ring_nf` normalizes multiple expressions which contain a particular atom, it uses a
form for that atom which is consistent between expressions.

Note: This test is useless unless done at `=ₛ` (the level of syntactic equality); doing this test at
`=` (the level of reducible equality) would not catch the erroneous old behaviour (normalizing one
expression to `a * 2` and the other to `x * 2`). But to get syntactic equality, we need to make the
typeclass arguments agree, which requires some handholding in typeclass inference ... which is the
reason for all the locally deleted instances here.
-/
attribute [-instance] instOfNat instNatCastInt Int.instSemiring Int.instOrderedCommRing
Int.instOrderedRing Int.instLinearOrderedCommRing Int.instMul NonUnitalNonAssocRing.toMul
NonUnitalNonAssocCommSemiring.toNonUnitalNonAssocSemiring
NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring in
example (x : ℤ) (R : ℤ → ℤ → Prop) : True := by
let a := x
have : R (a + x) (x + a) := by
ring_nf
guard_target =ₛ R (a * 2) (a * 2)
exact test_sorry
trivial

-- As reported at
-- https://leanprover.zulipchat.com/#narrow/stream/287929-mathlib4/topic/ring_nf.20failing.20to.20fully.20normalize
example (x : ℤ) (h : x - x + x = 0) : x = 0 := by
Expand Down
Loading