Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Mathlib.lean
Original file line number Diff line number Diff line change
Expand Up @@ -6937,6 +6937,7 @@ public import Mathlib.SetTheory.ZFC.VonNeumann
public import Mathlib.Tactic
public import Mathlib.Tactic.Abel
public import Mathlib.Tactic.AdaptationNote
public import Mathlib.Tactic.AddGroup
public import Mathlib.Tactic.Algebra.Basic
public import Mathlib.Tactic.Algebra.Lemmas
public import Mathlib.Tactic.Algebraize
Expand Down
1 change: 1 addition & 0 deletions Mathlib/Tactic.lean
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module -- shake: keep-all

public import Mathlib.Tactic.Abel
public import Mathlib.Tactic.AdaptationNote
public import Mathlib.Tactic.AddGroup
public import Mathlib.Tactic.Algebra.Basic
public import Mathlib.Tactic.Algebra.Lemmas
public import Mathlib.Tactic.Algebraize
Expand Down
125 changes: 125 additions & 0 deletions Mathlib/Tactic/AddGroup.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/-
Copyright (c) 2026 Kevin Buzzard. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Thomas Browning, Patrick Massot, Kevin Buzzard
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, do we have guidelines for handling authorship when taking large inspiration from another file? On the one hand, it's nice to give credit. On the other hand, authorship answers the question "who do I ping on zulip", and is the answer this gives correct? (Have they been asked if they'd like to be "held responsible"?)

I'm tempted to say the right move is actually just

Suggested change
Authors: Thomas Browning, Patrick Massot, Kevin Buzzard
Authors: Kevin Buzzard

and credit them in the module docstring, unless you've discussed this with them!

-/
module

public import Mathlib.Algebra.Order.Sub.Basic
public meta import Mathlib.Tactic.FailIfNoProgress
public import Mathlib.Tactic.Group
public import Mathlib.Tactic.Ring
Comment on lines +8 to +11
Copy link
Copy Markdown
Contributor

@thorimur thorimur Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can see this is the only import we need. But maybe should keep the other two in comments if they provide the other lemmas, in case that changes in the future?

Do you know if that is indeed why they're present, or were the imports LLM-generated as well?

Suggested change
public import Mathlib.Algebra.Order.Sub.Basic
public meta import Mathlib.Tactic.FailIfNoProgress
public import Mathlib.Tactic.Group
public import Mathlib.Tactic.Ring
public import Mathlib.Tactic.Group


/-!
# `add_group` tactic

Normalizes expressions in the language of additive groups. The basic idea is to use the simplifier
to put everything into a sum of integer scalar multiples (`zsmul` which takes an integer and an
additive group element), then simplify the scalars using the `ring` tactic. The process needs to be
repeated since `ring` can normalize a scalar to zero, leading to a summand that can be removed
before collecting scalars again. The simplifier step also uses some extra lemmas to avoid
some `ring` invocations.

Note: Unlike the multiplicative `group` tactic which uses `← zpow_neg_one` to convert `a⁻¹` to
`a ^ (-1 : ℤ)`, the additive version cannot use `← neg_one_zsmul` to convert `-a` to
`(-1 : ℤ) • a` because `(-1 : ℤ)` is itself `-(1 : ℤ)`, causing simp to loop (since `ℤ` is also
an `AddGroup`). Instead, we handle negation via `neg_add_rev` to distribute negation over sums,
`zsmul_neg`/`neg_zsmul` for `n • (-a)`, and custom trick lemmas for combining `-b` with adjacent
`zsmul` terms.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(See other comments)

Suggested change
`zsmul` terms.
`zsmul` terms. We also use `neg_one_zsmul` in the forward direction to normalize `(-1) • b` to `-b`.


Other than this issue, this is a direct port from Thomas Browning and Patrick Massot's `group`
tactic.
Comment on lines +30 to +31
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically, after these changes, it will be a little different... :)

Suggested change
Other than this issue, this is a direct port from Thomas Browning and Patrick Massot's `group`
tactic.
Other than this issue, the strategy parallels Thomas Browning and Patrick Massot's `group`
tactic.


Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about the last two. What do you think?

Suggested change
## TODO
- Allow `add_group` to take a config which can control the behavior when the goal is unchanged
(`ifUnchanged`), and possibly allow passing other config options to the underlying `simp` and
`ring_nf` tactics.
- Surface errors from `simp` and `ring_nf` instead of swallowing them in `repeat1`. (This likely
needs a new `repeat_until_no_progress` combinator.)
- Allow `add_group` to take extra simp lemmas.
- Allow `add_group` to take a `using h` clause like `simpa`, which normalizes `h` with `add_group`
and then uses it to simplify the goal.

## Tags

group theory, additive group
-/

public meta section

namespace Mathlib.Tactic.AddGroup

open Lean
open Lean.Meta
open Lean.Parser.Tactic
open Lean.Elab.Tactic
Comment on lines +42 to +45
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
open Lean
open Lean.Meta
open Lean.Parser.Tactic
open Lean.Elab.Tactic
open Lean Meta Parser Elab Tactic


-- The next three lemmas are not general purpose lemmas, they are intended for use only by
-- the `add_group` tactic.
-- They handle the case where a negated element `-b` appears adjacent to a `zsmul` of the same
-- element, or adjacent to another negated copy.
theorem zsmul_neg_trick {G : Type*} [AddGroup G] (a b : G) (n : ℤ) :
a + n • b + (-b) = a + (n + (-1)) • b := by
rw [add_assoc, ← neg_one_zsmul b, ← add_zsmul]

theorem zsmul_neg_trick' {G : Type*} [AddGroup G] (a b : G) (n : ℤ) :
a + (-b) + n • b = a + ((-1) + n) • b := by
rw [add_assoc, ← neg_one_zsmul b, ← add_zsmul]

theorem neg_neg_trick {G : Type*} [AddGroup G] (a b : G) :
a + (-b) + (-b) = a + (-2 : ℤ) • b := by
have h : -b = (-1 : ℤ) • b := (neg_one_zsmul b).symm
rw [h, add_assoc, ← add_zsmul]; norm_num
Comment on lines +47 to +62
Copy link
Copy Markdown
Contributor

@thorimur thorimur Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current iteration of add_group can't prove the following:

example (b : G) {n : Int} : (-b) + n • b = (n - 1) • b := by add_group

So I believe we just need to duplicate these trick lemmas to handle the (_ + _) cases; they seem to only handle the ((_ + _) + _) cases.

We also struggle with

example (b : G) : (-b) = (-1) • b := by add_group

Since we're committing to the neg lifestyle via the other trick lemmas, I think we actually want to use neg_one_zsmul in the ordinary direction to put (-1) • in terms of -, then (in more general cases) apply our trick lemmas.

Here, I also deapostrophe'd their names to be more descriptive, and prefixed them with a _. This causes them to be filtered out from autocompletion! :)

Suggested change
-- The next three lemmas are not general purpose lemmas, they are intended for use only by
-- the `add_group` tactic.
-- They handle the case where a negated element `-b` appears adjacent to a `zsmul` of the same
-- element, or adjacent to another negated copy.
theorem zsmul_neg_trick {G : Type*} [AddGroup G] (a b : G) (n : ℤ) :
a + n • b + (-b) = a + (n + (-1)) • b := by
rw [add_assoc, ← neg_one_zsmul b, ← add_zsmul]
theorem zsmul_neg_trick' {G : Type*} [AddGroup G] (a b : G) (n : ℤ) :
a + (-b) + n • b = a + ((-1) + n) • b := by
rw [add_assoc, ← neg_one_zsmul b, ← add_zsmul]
theorem neg_neg_trick {G : Type*} [AddGroup G] (a b : G) :
a + (-b) + (-b) = a + (-2 : ℤ) • b := by
have h : -b = (-1 : ℤ) • b := (neg_one_zsmul b).symm
rw [h, add_assoc, ← add_zsmul]; norm_num
-- The next six lemmas are not general purpose lemmas; they are intended for use only by
-- the `add_group` tactic, and so are prefixed with `_` to keep them out of autocomplete.
-- They handle the case where a negated element `-b` appears adjacent to a `zsmul` of the same
-- element, or adjacent to another negated copy.
-- This means we also want to convert `(-1) • b` to `-b` in order to apply these lemmas.
theorem _zsmul_neg_trick {G : Type*} [AddGroup G] (b : G) (n : ℤ) :
n • b + (-b) = (n + (-1)) • b := by
rw [← neg_one_zsmul b, ← add_zsmul]
theorem _neg_zsmul_trick {G : Type*} [AddGroup G] (b : G) (n : ℤ) :
(-b) + n • b = ((-1) + n) • b := by
rw [← neg_one_zsmul b, ← add_zsmul]
theorem _neg_neg_trick {G : Type*} [AddGroup G] (b : G) :
(-b) + (-b) = (-2 : ℤ) • b := by
have h : -b = (-1 : ℤ) • b := (neg_one_zsmul b).symm
rw [h, ← add_zsmul]; norm_num
theorem _add_zsmul_neg_trick {G : Type*} [AddGroup G] (a b : G) (n : ℤ) :
a + n • b + (-b) = a + (n + (-1)) • b := by
rw [add_assoc, _zsmul_neg_trick]
theorem _add_neg_zsmul_trick {G : Type*} [AddGroup G] (a b : G) (n : ℤ) :
a + (-b) + n • b = a + ((-1) + n) • b := by
rw [add_assoc, _neg_zsmul_trick]
theorem _add_neg_neg_trick {G : Type*} [AddGroup G] (a b : G) :
a + (-b) + (-b) = a + (-2 : ℤ) • b := by
rw [add_assoc, _neg_neg_trick]


/-- Auxiliary tactic for the `add_group` tactic. Calls the simplifier only. -/
syntax (name := aux_add_group₁) "aux_add_group₁" (location)? : tactic

macro_rules
| `(tactic| aux_add_group₁ $[at $location]?) =>
`(tactic| simp -decide -failIfUnchanged only
[addCommutatorElement_def, neg_add_rev, neg_zero, zsmul_neg, ← neg_zsmul,
add_zero, zero_add,
← natCast_zsmul, ← mul_zsmul',
Int.natCast_add, Int.natCast_mul, neg_neg,
zsmul_zero, zero_zsmul, one_zsmul,
← add_assoc,
← add_zsmul, ← add_one_zsmul, ← one_add_zsmul,
Mathlib.Tactic.Group.zsmul_trick,
Mathlib.Tactic.Group.zsmul_trick_zero,
Mathlib.Tactic.Group.zsmul_trick_zero',
zsmul_neg_trick, zsmul_neg_trick',
add_neg_cancel_right, neg_add_cancel_right,
neg_neg_trick,
sub_eq_add_neg,
tsub_self, sub_self, add_neg_cancel, neg_add_cancel]
$[at $location]?)

/-- Auxiliary tactic for the `add_group` tactic. Calls `ring_nf` to normalize scalars. -/
syntax (name := aux_add_group₂) "aux_add_group₂" (location)? : tactic

macro_rules
| `(tactic| aux_add_group₂ $[at $location]?) =>
`(tactic| ring_nf (ifUnchanged := .silent) $[at $location]?)

/-- `add_group` normalizes expressions in additive groups that occur in the goal. `add_group` does
not assume commutativity, instead using only the additive group axioms without any information about
which group is manipulated. If the goal is an equality, and after normalization the two sides are
equal, `add_group` closes the goal.

For multiplicative groups, use the `group` tactic instead.
For additive commutative groups, use the `abel` tactic instead.

* `add_group at l1 l2 ...` normalizes at the given locations.

Example:
```lean
example {G : Type} [AddGroup G] (a b c d : G) (h : c = (a + 2 • b) + (-(b + b) + (-a)) + d) :
a + c + (-d) = a := by
add_group at h -- normalizes `h` which becomes `h : c = d`
rw [h] -- the goal is now `a + d + (-d) = a`
add_group -- which is then normalized and closed
```
-/
syntax (name := add_group) "add_group" (location)? : tactic

macro_rules
| `(tactic| add_group $[$loc]?) =>
`(tactic| repeat (fail_if_no_progress (aux_add_group₁ $[$loc]? <;> aux_add_group₂ $[$loc]?)))
Comment on lines +64 to +117
Copy link
Copy Markdown
Contributor

@thorimur thorimur Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a couple of things I think we can change here:

  1. We don't actually need the aux_* tactics. We can/should just inline them inside the macro, since we use them exactly once and don't extend them downstream. (Otherwise their only purpose nowadays is basically to pollute the tactic documentation page! 😅) The simp call is maybe worth isolating for readability, but we can do that with a let in the body of the macro_rules for the main tactic. (Honestly maybe we should do this for group too, since I see that's where this pattern is coming from.)
  2. add_group should probably fail with the appropriate message if no progress is made. For that, we can use first | repeat1 (fail_if_no_progress ...) | fail "`add_group` made no progress; repeat1 ensures that the tactic is successful at least once. Ideally ifUnchanged becomes a config option to add_group, but we can handle that in a separate PR :)
  3. Nit: syntax kind (name := ...)s are usually lowerCamelCase.
  4. (Future issue) Technically repeat(1) (fail_if_no_progress ...) is a bad pattern, I think, because repeat repeats until any failure, then rewinds. We should probably only be repeating until we stop making progress, and surface any other non-progress-related errors. repeat obscures the latter sort of error. (I'm also a little worried about fail_if_no_progress's main-goal-only focus when combined with repeat.) But, this is also a problem with group. So let's not hold up this functionality over that.

I've added the new lemmas/names from the above suggestion, including neg_one_zsmul in the forward direction, and tweaked the docstring to try to point users to abel earlier if it better suits their needs. Feel free to tweak it further! :)

Suggested change
/-- Auxiliary tactic for the `add_group` tactic. Calls the simplifier only. -/
syntax (name := aux_add_group₁) "aux_add_group₁" (location)? : tactic
macro_rules
| `(tactic| aux_add_group₁ $[at $location]?) =>
`(tactic| simp -decide -failIfUnchanged only
[addCommutatorElement_def, neg_add_rev, neg_zero, zsmul_neg, ← neg_zsmul,
add_zero, zero_add,
← natCast_zsmul, ← mul_zsmul',
Int.natCast_add, Int.natCast_mul, neg_neg,
zsmul_zero, zero_zsmul, one_zsmul,
← add_assoc,
← add_zsmul, ← add_one_zsmul, ← one_add_zsmul,
Mathlib.Tactic.Group.zsmul_trick,
Mathlib.Tactic.Group.zsmul_trick_zero,
Mathlib.Tactic.Group.zsmul_trick_zero',
zsmul_neg_trick, zsmul_neg_trick',
add_neg_cancel_right, neg_add_cancel_right,
neg_neg_trick,
sub_eq_add_neg,
tsub_self, sub_self, add_neg_cancel, neg_add_cancel]
$[at $location]?)
/-- Auxiliary tactic for the `add_group` tactic. Calls `ring_nf` to normalize scalars. -/
syntax (name := aux_add_group₂) "aux_add_group₂" (location)? : tactic
macro_rules
| `(tactic| aux_add_group₂ $[at $location]?) =>
`(tactic| ring_nf (ifUnchanged := .silent) $[at $location]?)
/-- `add_group` normalizes expressions in additive groups that occur in the goal. `add_group` does
not assume commutativity, instead using only the additive group axioms without any information about
which group is manipulated. If the goal is an equality, and after normalization the two sides are
equal, `add_group` closes the goal.
For multiplicative groups, use the `group` tactic instead.
For additive commutative groups, use the `abel` tactic instead.
* `add_group at l1 l2 ...` normalizes at the given locations.
Example:
```lean
example {G : Type} [AddGroup G] (a b c d : G) (h : c = (a + 2 • b) + (-(b + b) + (-a)) + d) :
a + c + (-d) = a := by
add_group at h -- normalizes `h` which becomes `h : c = d`
rw [h] -- the goal is now `a + d + (-d) = a`
add_group -- which is then normalized and closed
```
-/
syntax (name := add_group) "add_group" (location)? : tactic
macro_rules
| `(tactic| add_group $[$loc]?) =>
`(tactic| repeat (fail_if_no_progress (aux_add_group₁ $[$loc]? <;> aux_add_group₂ $[$loc]?)))
/--
`add_group` normalizes expressions in additive groups without assuming commutativity. Unlike
`abel`, which does take advantage of commutativity, `add_group` instead only uses the additive
group axioms without any information about which group is manipulated. If the goal is an equality,
and after normalization the two sides are equal, `add_group` closes the goal.
`add_group at l1 l2 ...` normalizes at the given locations.
For additive commutative groups, use the `abel` tactic instead.
For multiplicative groups, use the `group` tactic instead.
Example:
```lean
example {G : Type} [AddGroup G] (a b c d : G) (h : c = (a + 2 • b) + (-(b + b) + (-a)) + d) :
a + c + (-d) = a := by
add_group at h -- normalizes `h` which becomes `h : c = d`
rw [h] -- the goal is now `a + d + (-d) = a`
add_group -- which is then normalized and closed
```
-/
syntax (name := addGroup) "add_group" (location)? : tactic
macro_rules
| `(tactic| add_group $[$loc]?) => do
let simpTacStx ←
`(tactic| simp -decide -failIfUnchanged only
[addCommutatorElement_def, neg_add_rev, neg_zero, zsmul_neg, ← neg_zsmul,
add_zero, zero_add,
← natCast_zsmul, ← mul_zsmul',
Int.natCast_add, Int.natCast_mul,
neg_neg,
zsmul_zero, zero_zsmul, one_zsmul,
← add_assoc,
← add_zsmul, ← add_one_zsmul, ← one_add_zsmul,
neg_one_zsmul,
Mathlib.Tactic.Group.zsmul_trick,
Mathlib.Tactic.Group.zsmul_trick_zero,
Mathlib.Tactic.Group.zsmul_trick_zero',
Mathlib.Tactic.AddGroup._zsmul_neg_trick,
Mathlib.Tactic.AddGroup._neg_zsmul_trick,
Mathlib.Tactic.AddGroup._neg_neg_trick,
Mathlib.Tactic.AddGroup._add_zsmul_neg_trick,
Mathlib.Tactic.AddGroup._add_neg_zsmul_trick,
Mathlib.Tactic.AddGroup._add_neg_neg_trick,
add_neg_cancel_right, neg_add_cancel_right,
sub_eq_add_neg,
tsub_self, sub_self, add_neg_cancel, neg_add_cancel]
$[$loc]?)
/- First, we simplify with a custom simp set, then use `ring_nf`. If neither make progress, we
stop. If we do not make any progress at all, we fail. -/
`(tactic| first
| repeat1 (fail_if_no_progress ($simpTacStx <;> ring_nf (ifUnchanged := .silent) $[$loc]?))
| fail "`add_group` made no progress")


end Mathlib.Tactic.AddGroup

/-!
We register `add_group` with the `hint` tactic.
-/

register_hint 900 add_group
62 changes: 62 additions & 0 deletions MathlibTest/AddGroup.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
module
import Mathlib.Tactic.AddGroup
public import Mathlib.Algebra.Group.Commutator

variable {G : Type} [AddGroup G]

attribute [instance] addCommutatorElement
Copy link
Copy Markdown
Contributor

@thorimur thorimur Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fact that something like open scoped (add)commutatorElement doesn't work is surely an oversight in the source file for addCommutatorElement, right (given the docstring)? Can we fix that (possibly in another PR)?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aha, great! :)

So let's wait for that to be merged then change this to

Suggested change
attribute [instance] addCommutatorElement
open scoped addCommutatorElement


example (a b c : G) : c + (a + b) + (-b + -a) + c = c + c := by add_group

example (a b c : G) : (b + -c) + c + (a + b) + (-b + -a) + c = b + c := by add_group

example (a b c : G) : -c + (b + -c) + c + (a + b) + (-b + -a + -b) + c = 0 := by add_group

-- The following is known as the Hall-Witt identity,
-- see e.g.
-- https://en.wikipedia.org/wiki/Three_subgroups_lemma#Proof_and_the_Hall%E2%80%93Witt_identity
example (g h k : G) :
g + ⁅⁅-g, h⁆, k⁆ + -g + k + ⁅⁅-k, g⁆, h⁆ + -k + h + ⁅⁅-h, k⁆, g⁆ + -h = 0 := by add_group

example (a : G) : 2 • a + a = 3 • a := by add_group

example (n m : ℕ) (a : G) : n • a + m • a = (n + m) • a := by add_group

example (a b c : G) : c + (a + 2 • b) + (-(b + b) + -a) + c = c + c := by add_group

example (n : ℕ) (a : G) : n • a + n • (-a) = 0 := by add_group

example (a : G) : 2 • a + -a + -a = 0 := by add_group

example (n m : ℕ) (a : G) : n • a + m • a = (m + n) • a := by add_group

example (n : ℕ) (a : G) : (n - n) • a = 0 := by add_group

example (n : ℤ) (a : G) : (n - n) • a = 0 := by add_group

Copy link
Copy Markdown
Contributor

@thorimur thorimur Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add these tests, which failed without the new extra three lemmas + neg_one_zsmul added in another suggestion.

Suggested change
example (b : G) {n : ℤ} : (-b) + n • b = (n - 1) • b := by add_group
example (b : G) : (-b) + (-b) = (-2) • b := by add_group
example (b : G) {n : ℤ} : n • b + (-b) = (n - 1) • b := by add_group
example (b : G) : -b = (-1) • b := by add_group

example (n : ℤ) (a : G) (h : (n * (n + 1) - n - n ^ 2) • a = a) : a = 0 := by
add_group at h
exact h.symm

example (a b c d : G) (h : c = (a + 2 • b) + (-(b + b) + -a) + d) : a + c + -d = a := by
add_group at h
rw [h]
add_group

-- The next example can be expanded to require an arbitrarily high number of alternations
-- between simp and ring

example (n m : ℤ) (a b : G) : (m - n) • a + (m - n) • b + (n - m) • b + (n - m) • a = 0 := by
add_group

example (n : ℤ) (a b : G) :
n • a + n • b + n • a + n • a + (-n) • a + (-n) • a + (-n) • b + (-n) • a = 0 := by add_group

-- Test that add_group deals with `-(0)` properly

example (x y : G) : -((-x) + (x + y) + (-y)) = 0 := by add_group

set_option linter.unusedTactic false in
example (x : G) (h : x = 0) : x = 0 := by
add_group
exact h
Comment on lines +59 to +62
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given other suggestions:

Suggested change
set_option linter.unusedTactic false in
example (x : G) (h : x = 0) : x = 0 := by
add_group
exact h
/--
error: `add_group` made no progress
G : Type
inst✝ : AddGroup G
x : G
h : x = 0
⊢ x = 0
-/
#guard_msgs in
example (x : G) (h : x = 0) : x = 0 := by
add_group

Loading