Zulip Chat Archive

Stream: new members

Topic: Examples highlighting "unique" capabilities of tactics


Isak Colboubrani (Feb 20 2025 at 19:31):

For demonstration purposes, I’m compiling a list of goals that are “uniquely solved” by certain closing tactics (with zero arguments). By “uniquely solved,” I loosely mean that no other tactic can directly solve these goals unless it internally calls the same “primary tactic.”

For instance, consider the following example, which (as far as I can tell) is only solvable by polyrith:

import Mathlib
example (a b c d e f : ) (h : b * c = e * f) : a * b * c * d = a * e * f * d := by polyrith

Some other candidates include:

import Mathlib
example (P : Nat  Prop) (h : {x // P x}) :  x, P x  0  x := by aesop
example : Nat.Prime 37 := by decide
example (a b c : ) (h : a  b) : c - Real.exp b  c - Real.exp a := by gcongr
example (h : 1 < 0) : False := by linarith
example : (2 : ) < 5 / 2  5 / 2 < 3 := by norm_num
example : 37^2 - 35^2 = 72 * 2 := by omega
example : (-126432 : ZMod 1235412223) ^ 12355342321 = 1001528716 := by reduce_mod_char
example {α : Nat  Type} (f : (n : Nat)  α n  α (n+1)) (a : α 0) : α 4 := by solve_by_elim
example (p q r : Prop) : p  (q  r)  (p  q)  (r  p  r) := by tauto

Do these examples look correct? And do they serve as good representatives for their respective tactics?

And can anyone suggest more examples that highlight the unique capabilities of other tactics?

The preliminary list of tactics I plan to cover: abel aesop aesop_cat assumption bound coherence continuity contradiction decide dsimp ext fun_prop gcongr group infer_instance linarith measurability module noncomm_ring norm_num omega polyrith positivity reduce_mod_char rfl ring rw_search simp simp_all solve_by_elim tauto trivial

Which of these tactics are unlikely to have such an "unique example"? Also, which relevant tactics have I overlooked?

Kyle Miller (Feb 20 2025 at 19:36):

example : Nat.Prime 37 := by norm_num

Kyle Miller (Feb 20 2025 at 19:37):

example : 37^2 - 35^2 = 72 * 2 := by rfl -- or norm_num or simp

Kyle Miller (Feb 20 2025 at 19:40):

omega is meant for linear integer inequalities rather than equations without variables. It can do some case-splitting too to deal with some non-linearities.

Julian Berman (Feb 20 2025 at 19:41):

Is that rfl example meant to be a counterexample to uniqueness part of this or are you saying it's really a Good Idea™ to use it for that goal? To me I like knowing what kinds of goals make someone say "that's exactly what tactic XYZ is meant for", regardless even of whether a "simpler" tactic can directly solve it -- is rfl really solving that because it should or because it just happens to? (I'd have reached for norm_num seeing all numerals there personally)

Isak Colboubrani (Feb 20 2025 at 19:53):

@Kyle Miller Thanks for sharing those counterexamples. I’ll remove this example as well:

example (p q r : Prop) : p  (q  r)  (p  q)  (r  p  r) := by tauto
example (p q r : Prop) : p  (q  r)  (p  q)  (r  p  r) := by aesop

The following examples still stand as presumably “unique examples”:

import Mathlib
example (P : Nat  Prop) (h : {x // P x}) :  x, P x  0  x := by aesop
example (a b c : ) (h : a  b) : c - Real.exp b  c - Real.exp a := by gcongr
example (h : 1 < 0) : False := by linarith
example : (2 : ) < 5 / 2  5 / 2 < 3 := by norm_num
example (a b c d e f : ) (h : b * c = e * f) : a * b * c * d = a * e * f * d := by polyrith
example : (-126432 : ZMod 1235412223) ^ 12355342321 = 1001528716 := by reduce_mod_char
example (α : Nat  Type) (f : (n : Nat)  α n  α (n+1)) (a : α 0) : α 4 := by solve_by_elim

Isak Colboubrani (Feb 20 2025 at 20:07):

Four new presumably unique examples:

import Mathlib
example (a b : α) [AddCommMonoid α] : a + (b + a) = a + a + b := by abel
example (a b c : G) [Group G] : c⁻¹*(b*c⁻¹)*c*(a*b)*(b⁻¹*a⁻¹*b⁻¹)*c = 1 := by group
example (a b : R) [Ring R] : (a + b)^3 = a^3 + a^2*b + a*b*a + a*b^2 + b*a^2 + b*a*b + b^2*a + b^3 := by noncomm_ring
example (a b c : α) [LinearOrderedField α] :  a * (-c / b) * (-c / b) + -c + c = a * (c / b * (c / b)) := by ring

Julian Berman (Feb 20 2025 at 20:10):

Adding division somewhere to an example will probably give you a field_simp one.

Kyle Miller (Feb 20 2025 at 20:11):

@Julian Berman For natural number arithmetic with concrete small numbers, I think rfl is best. It gets the kernel to just compute. It gives an efficient proof term.

Julian Berman (Feb 20 2025 at 20:14):

Wow interesting, TIL. (@Isak Colboubrani I think is a super useful kind of reference to have FWIW so thanks already for putting the list you have together)

Isak Colboubrani (Feb 20 2025 at 20:14):

@Julian Berman Thanks! Found this one for field_simp:

import Mathlib
example (a b : R) [CommRing R] (u₁ : Rˣ) : a /ₚ u₁ + b /ₚ u₁ = (a + b) /ₚ u₁ := by field_simp

Kyle Miller (Feb 20 2025 at 20:22):

(Side note @Julian Berman: You know how people tend to use decide for natural number and integer equalities? That's just doing rfl in the end, but with an extra unnecessary step or two. There's no harm in it, but if decide is ok then rfl is too.)

Isak Colboubrani (Feb 20 2025 at 20:23):

Found this one for rw_search:

import Mathlib
example (xs ys : List α) : (xs ++ ys ++ ys).length = 2 * ys.length + xs.length := by rw_search

@Kim Morrison, is this a good example for rw_search, or do you have any better suggestion?

Isak Colboubrani (Feb 20 2025 at 20:32):

Presumably unique examples for fun_prop, measurability and positivity:

import Mathlib
example : Measurable fun x :  => (x * x - 1) / x + (x - x*x) := by fun_prop
example [MeasurableSpace α] [MeasurableSpace β] (F :   α  β) (hF :  i, Measurable (F i)) : Measurable (F 0) := by measurability
example (a b : ) (ha : 3 < a) (hb : 4  b) : 0 < 3 + a * b / 7 + b + 7 + 14 := by positivity

Isak Colboubrani (Feb 20 2025 at 20:38):

And coherence:

import Mathlib
open CategoryTheory MonoidalCategory
example (C : Type) [CategoryTheory.Category.{v} C] [CategoryTheory.MonoidalCategory C] (f : 𝟙_ C  _) : f  (λ_ (𝟙_ C)).hom = f  (ρ_ (𝟙_ C)).hom := by coherence

Isak Colboubrani (Feb 20 2025 at 20:43):

And assumption_mod_cast:

import Mathlib
example (az bz cz : ) (h : (cz : ) = az / bz) : (cz : ) = az / bz := by assumption_mod_cast

Aaron Liu (Feb 20 2025 at 20:55):

This one for linarith is not unique, contradiction calls decide on the hypothesis here I think.

example (h : 1 < 0) : False := by contradiction

Kyle Miller (Feb 20 2025 at 20:56):

linarith is meant for linear inequalities anyway; adding some variables to the example would be better.

Isak Colboubrani (Feb 20 2025 at 20:56):

And arith_mult:

import Mathlib
open ArithmeticFunction
example {R : Type*} [Field R] (n : ) : IsMultiplicative <| (σ n * pow (n + 3)).ppow 2 := by arith_mult

Isak Colboubrani (Feb 20 2025 at 21:02):

And compute_degree:

import Mathlib
open Polynomial
example [Semiring R] : natDegree (OfNat.ofNat (OfNat.ofNat 0) : R[X])  0 := by compute_degree

Damiano Testa (Feb 20 2025 at 21:04):

simp does not solve the last one?

Isak Colboubrani (Feb 20 2025 at 21:06):

Oh, it does. Thanks for catching that!

Isak Colboubrani (Feb 20 2025 at 21:13):

And tfae_finish:

import Mathlib
example (h₁ : P  Q) (h₂ : Q  P) : List.TFAE [P, Q] := by tfae_finish

Damiano Testa (Feb 20 2025 at 21:18):

I think that for compute_degree you can use

example : natDegree (X + 1 : Nat[X])  1 := by
  compute_degree

Isak Colboubrani (Feb 20 2025 at 22:09):

And mfld_set_tac:

import Mathlib
example (e : PartialEquiv α β) (e' : PartialEquiv β γ) : (e.trans e').source = e.source  Set.preimage e (e.target  e'.source) := by mfld_set_tac

Isak Colboubrani (Feb 21 2025 at 12:29):

I believe I’ve found a tauto test case that neither aesop nor any other tactic seems able to solve:

import Mathlib
example (P : Nat  Prop) (n : Nat) : P n  n = 7  n = 0  ¬ (n = 7  n = 0)  P n := by tauto

Does anyone have a counterexample tactic (i.e., a tactic other than tauto that solves this) to prove otherwise?

Aaron Liu (Feb 21 2025 at 12:34):

grind works, with the warning,

"The grind tactic is experimental and still under development. Avoid using it in production projects"

Isak Colboubrani (Feb 21 2025 at 12:42):

That's really interesting, @Aaron Liu. Thanks for sharing! I’m excited about grind. I’ll keep track of which “unique” test cases the in-development version of grind can handle. For now, I’ll label this test case as “unique for tauto + not-yet-ready-for-production grind.”

Isak Colboubrani (Feb 21 2025 at 12:47):

From the original list of tactics, I’m still looking for “unique” test cases for the following:

aesop_cat assumption bound constructor continuity contradiction decide dsimp ext infer_instance linarith module omega rfl simp simp_all trivial

Test case contributions are welcome!

I’ve also compiled another list of additional candidate tactics to cover. Which of these do you think are likely—or unlikely—to have a unique test case?

cancel_denoms dsimp eq_refl finiteness finiteness_nonterminal interval_cases itauto match_scalars native_decide nlinarith nofun norm_cast pure_coherence qify rify simp_arith simp_intro subsingleton whisker_simps

Aaron Liu (Feb 21 2025 at 12:57):

The one for measurability is also not very good, solve_by_elim and tauto both solve it.

Isak Colboubrani said:

Presumably unique examples for fun_prop, measurability and positivity:

import Mathlib
example : Measurable fun x :  => (x * x - 1) / x + (x - x*x) := by fun_prop
example [MeasurableSpace α] [MeasurableSpace β] (F :   α  β) (hF :  i, Measurable (F i)) : Measurable (F 0) := by measurability
example (a b : ) (ha : 3 < a) (hb : 4  b) : 0 < 3 + a * b / 7 + b + 7 + 14 := by positivity

Michael Rothgang (Feb 21 2025 at 13:58):

In general, I'm a bit surprised if fun_prop can solve some measurability goal and measurabilitycannot. (The other way around exists, e.g. around measurable sets or since measurability uses aesop.)

Michael Rothgang (Feb 21 2025 at 13:58):

Similarly for fun_prop and continuity.

Michael Rothgang (Feb 21 2025 at 13:59):

gcongr could also be worth including

Tomas Skrivan (Feb 21 2025 at 14:10):

The main point of fun_prop is speed and that it is much better at applying theorems like Continuous.comp, continuous_apply and continuous_pi. For example this is not solvable by continuity

example (f :     ) (hf : Continuous f) (x : ) : Continuous (f x) := by fun_prop

Tomas Skrivan (Feb 21 2025 at 14:13):

It also works with bundled morphisms, i.e. you can assume that function (f : ℝ → Path (0:ℝ) (1:ℝ)) is jointly continuous and fun_prop infers that it is component wise continuous

example (f :   Path (0:) (1:)) (hf : Continuous f) (x : ) : Continuous (f x ·) := by fun_prop
example (f :   Path (0:) (1:)) (hf : Continuous f) (y : unitInterval) : Continuous (f · y) := by fun_prop

This all scales to arbitrary number of arguments.

example (f :       Path (0:) (1:)) (hf : Continuous f) (x y : ) : Continuous ((f x · y ·)) := by fun_prop

Isak Colboubrani (Feb 21 2025 at 15:06):

Michael Rothgang said:

gcongr could also be worth including

gcongr was included in the original list and has the following presumably "unique" test case:

example (a b c : ) (h : a  b) : c - Real.exp b  c - Real.exp a := by gcongr

Let me know if this can be solved using any other tactic.

Isak Colboubrani (Feb 21 2025 at 22:34):

Two presumably unique test cases for decide and omega:

import Mathlib

example :  m, m < 25   n, n < 25   c, c < 25  m ^ 2 + n ^ 2 + c ^ 2  7 := by decide
example (a : Nat) : (((a + (2 ^ 64 - 1)) % 2 ^ 64 + 1) * 8 - 1 - (a + (2 ^ 64 - 1)) % 2 ^ 64 * 8 + 1) = 8 := by omega

Isak Colboubrani (Feb 22 2025 at 07:54):

Update -- currently at 23 tactics with unique test cases:

import Mathlib
open ArithmeticFunction CategoryTheory MonoidalCategory Polynomial
set_option autoImplicit false

-- Presumably "unique" test cases for each tactic: no other tactic can directly solve these goals.
example (α : Type) (a b : α) [AddCommMonoid α] : a + (b + a) = a + a + b := by abel
example (P : Nat  Prop) (h : {x // P x}) :  x, P x  0  x := by aesop
example (R : Type) [Field R] (n : ) : IsMultiplicative <| (σ n * pow (n + 3)).ppow 2 := by arith_mult
example (a b c : ) (h : (c : ) = a / b) : (c : ) = a / b := by assumption_mod_cast
example (C : Type) [CategoryTheory.Category C] [CategoryTheory.MonoidalCategory C] (f : 𝟙_ C  _) : f  (λ_ (𝟙_ C)).hom = f  (ρ_ (𝟙_ C)).hom := by coherence
example : natDegree (X + 1 : Nat[X])  1 := by compute_degree
example :  m, m < 25   n, n < 25   c, c < 25  m ^ 2 + n ^ 2 + c ^ 2  7 := by decide
example (R : Type) (a b : R) [CommRing R] (u₁ : Rˣ) : a /ₚ u₁ + b /ₚ u₁ = (a + b) /ₚ u₁ := by field_simp
example : Measurable fun x :  => (x * x - 1) / x + (x - x*x) := by fun_prop
example (a b c : ) (h : a  b) : c - Real.exp b  c - Real.exp a := by gcongr
example (G : Type) (a b c : G) [Group G] : c⁻¹*(b*c⁻¹)*c*(a*b)*(b⁻¹*a⁻¹*b⁻¹)*c = 1 := by group
example (α β γ : Type) (e : PartialEquiv α β) (e' : PartialEquiv β γ) : (e.trans e').source = e.source  Set.preimage e (e.target  e'.source) := by mfld_set_tac
example (R : Type) (a b : R) [Ring R] : (a + b)^3 = a^3 + a^2*b + a*b*a + a*b^2 + b*a^2 + b*a*b + b^2*a + b^3 := by noncomm_ring
example : (2 : ) < 5 / 2  5 / 2 < 3 := by norm_num
example (a : Nat) : (((a + (2 ^ 64 - 1)) % 2 ^ 64 + 1) * 8 - 1 - (a + (2 ^ 64 - 1)) % 2 ^ 64 * 8 + 1) = 8 := by omega
example (a b c d e f : ) (h : b * c = e * f) : a * b * c * d = a * e * f * d := by polyrith
example (a b : ) (ha : 3 < a) (hb : 4  b) : 0 < 3 + a * b / 7 + b + 7 + 14 := by positivity
example : (-126432 : ZMod 1235412223) ^ 12355342321 = 1001528716 := by reduce_mod_char
example (α : Type) (a b c : α) [LinearOrderedField α] :  a * (-c / b) * (-c / b) + -c + c = a * (c / b * (c / b)) := by ring
example (α : Type) (xs ys : List α) : (xs ++ ys ++ ys).length = 2 * ys.length + xs.length := by rw_search
example (α : Nat  Type) (f : (n : Nat)  α n  α (n+1)) (a : α 0) : α 4 := by solve_by_elim
example (P : Nat  Prop) (n : Nat) : P n  n = 7  n = 0  ¬ (n = 7  n = 0)  P n := by tauto
example (P Q : Prop) (h₁ : P  Q) (h₂ : Q  P) : List.TFAE [P, Q] := by tfae_finish

-- The following tactics currently lack unique test cases:
--   aesop_cat assumption bound cancel_denoms constructor continuity contradiction
--   dsimp eq_refl ext finiteness finiteness_nonterminal infer_instance interval_cases
--   itauto linarith match_scalars module native_decide nlinarith nofun norm_cast
--   pure_coherence qify rfl rify simp simp_all simp_arith simp_intro subsingleton
--   trivial whisker_simps

Daniel Weber (Feb 22 2025 at 13:32):

Example for native_decide:

example :  n < 1000, n.gcd (n + 1) = 1 := by native_decide

Daniel Weber (Feb 22 2025 at 13:33):

trivial attempts all of assumption,rfl,contradiction,decide, so there's no true example for any of them. Here's an example for trivial:

example (m : Nat) (h : m  1) : True  m  1   n < 100, n^2 < 10000 := by trivial

Edward van de Meent (Feb 22 2025 at 13:35):

Daniel Weber said:

Example for native_decide:

example :  n < 1000, n.gcd (n + 1) = 1 := by native_decide

importing mathlib, aesop does this too

Edward van de Meent (Feb 22 2025 at 13:36):

concretely:

import Mathlib
example :  n < 1000, n.gcd (n + 1) = 1 := by
  aesop? says
    intro n a
    simp_all only [Nat.gcd_self_add_right, Nat.gcd_one_right]

Daniel Weber (Feb 22 2025 at 13:36):

simp also does

Daniel Weber (Feb 22 2025 at 13:38):

Fixed example:

example :  a, a < 100  0 < a   b, b < 100  0 < b   c, c < 100  a^3 + b^3  c^3 := by native_decide

Daniel Weber (Feb 22 2025 at 13:42):

Example for module:

example [AddCommGroup V] [CommRing K] [Module K V] {x : V} {a b c d : K} : (c - d)  a  x = (a  c  x + b  d  y) - d  (a  x + b  y) := by module

Daniel Weber (Feb 22 2025 at 13:42):

I doubt rify/zify/qify would have any example, they aren't finishing tactics

Daniel Weber (Feb 22 2025 at 13:46):

Example for constructor:

structure True' : Prop
example : True' := by constructor

Daniel Weber (Feb 22 2025 at 13:46):

Any example for dsimp should also be one for rfl, so I don't expect any

Edward van de Meent (Feb 22 2025 at 13:46):

never once had i considered constructor to be a closing tactic :rolling_on_the_floor_laughing:

Daniel Weber (Feb 22 2025 at 13:47):

rfl attempts eq_refl, so eq_refl shouldn't have any example

Edward van de Meent (Feb 22 2025 at 13:52):

Isak Colboubrani said:

example :  m, m < 25   n, n < 25   c, c < 25  m ^ 2 + n ^ 2 + c ^ 2  7 := by decide

this is solved by native_decide too

Edward van de Meent (Feb 22 2025 at 13:53):

(to be expected, i guess, as the docstring says it's an alias for decide +native)

Daniel Weber (Feb 22 2025 at 13:54):

Example for ext (although it's slightly a stretch :laughing: )

inductive Empty'
example (a b : Set Empty') : a = b := by ext ⟨⟩

Markus Himmel (Feb 22 2025 at 13:57):

Daniel Weber said:

although it's slightly a stretch

It is useful sometimes if you're too lazy to type by subsingleton :)

Edward van de Meent (Feb 22 2025 at 13:57):

i wonder if there is a nice one for contradiction

Daniel Weber (Feb 22 2025 at 13:57):

It's subsumed by trivial, but maybe ignoring that :thinking:

Daniel Weber (Feb 22 2025 at 13:59):

Example for finiteness:

import Mathlib

example (a : ) : (ENNReal.ofReal (1 + a ^ 2))⁻¹ <  := by finiteness

Daniel Weber (Feb 22 2025 at 13:59):

finiteness_nonterminal shouldn't have any example

Daniel Weber (Feb 22 2025 at 14:00):

Example for infer_instance:

example : Field  := by infer_instance

Edward van de Meent (Feb 22 2025 at 14:01):

is this a thing?

example {R : Type*} [LinearOrderedRing R] (x:R) (hx : x > 0): 0 < x ^ 9348 := by
  positivity

Edward van de Meent (Feb 22 2025 at 14:01):

ah nvm we had that one already

Bryan Gin-ge Chen (Feb 22 2025 at 14:02):

At least for the mathlib tactics, these seem like good examples to add to the tactic docstrings, if they're not already there. (In general I think we still have too few examples in those docstrings.)

Edward van de Meent (Feb 22 2025 at 14:02):

does anyone know, is assumption subsumed by trivial?

Daniel Weber (Feb 22 2025 at 14:02):

itauto should be subsumed by tauto if you aren't looking at the axioms used

Henrik Böving (Feb 22 2025 at 14:17):

Isak Colboubrani said:

Update -- currently at 23 tactics with unique test cases:

import Mathlib
open ArithmeticFunction CategoryTheory MonoidalCategory Polynomial
set_option autoImplicit false

-- Presumably "unique" test cases for each tactic: no other tactic can directly solve these goals.
example (α : Type) (a b : α) [AddCommMonoid α] : a + (b + a) = a + a + b := by abel
example (P : Nat  Prop) (h : {x // P x}) :  x, P x  0  x := by aesop
example (R : Type) [Field R] (n : ) : IsMultiplicative <| (σ n * pow (n + 3)).ppow 2 := by arith_mult
example (a b c : ) (h : (c : ) = a / b) : (c : ) = a / b := by assumption_mod_cast
example (C : Type) [CategoryTheory.Category C] [CategoryTheory.MonoidalCategory C] (f : 𝟙_ C  _) : f  (λ_ (𝟙_ C)).hom = f  (ρ_ (𝟙_ C)).hom := by coherence
example : natDegree (X + 1 : Nat[X])  1 := by compute_degree
example :  m, m < 25   n, n < 25   c, c < 25  m ^ 2 + n ^ 2 + c ^ 2  7 := by decide
example (R : Type) (a b : R) [CommRing R] (u₁ : Rˣ) : a /ₚ u₁ + b /ₚ u₁ = (a + b) /ₚ u₁ := by field_simp
example : Measurable fun x :  => (x * x - 1) / x + (x - x*x) := by fun_prop
example (a b c : ) (h : a  b) : c - Real.exp b  c - Real.exp a := by gcongr
example (G : Type) (a b c : G) [Group G] : c⁻¹*(b*c⁻¹)*c*(a*b)*(b⁻¹*a⁻¹*b⁻¹)*c = 1 := by group
example (α β γ : Type) (e : PartialEquiv α β) (e' : PartialEquiv β γ) : (e.trans e').source = e.source  Set.preimage e (e.target  e'.source) := by mfld_set_tac
example (R : Type) (a b : R) [Ring R] : (a + b)^3 = a^3 + a^2*b + a*b*a + a*b^2 + b*a^2 + b*a*b + b^2*a + b^3 := by noncomm_ring
example : (2 : ) < 5 / 2  5 / 2 < 3 := by norm_num
example (a : Nat) : (((a + (2 ^ 64 - 1)) % 2 ^ 64 + 1) * 8 - 1 - (a + (2 ^ 64 - 1)) % 2 ^ 64 * 8 + 1) = 8 := by omega
example (a b c d e f : ) (h : b * c = e * f) : a * b * c * d = a * e * f * d := by polyrith
example (a b : ) (ha : 3 < a) (hb : 4  b) : 0 < 3 + a * b / 7 + b + 7 + 14 := by positivity
example : (-126432 : ZMod 1235412223) ^ 12355342321 = 1001528716 := by reduce_mod_char
example (α : Type) (a b c : α) [LinearOrderedField α] :  a * (-c / b) * (-c / b) + -c + c = a * (c / b * (c / b)) := by ring
example (α : Type) (xs ys : List α) : (xs ++ ys ++ ys).length = 2 * ys.length + xs.length := by rw_search
example (α : Nat  Type) (f : (n : Nat)  α n  α (n+1)) (a : α 0) : α 4 := by solve_by_elim
example (P : Nat  Prop) (n : Nat) : P n  n = 7  n = 0  ¬ (n = 7  n = 0)  P n := by tauto
example (P Q : Prop) (h₁ : P  Q) (h₂ : Q  P) : List.TFAE [P, Q] := by tfae_finish

-- The following tactics currently lack unique test cases:
--   aesop_cat assumption bound cancel_denoms constructor continuity contradiction
--   dsimp eq_refl ext finiteness finiteness_nonterminal infer_instance interval_cases
--   itauto linarith match_scalars module native_decide nlinarith nofun norm_cast
--   pure_coherence qify rfl rify simp simp_all simp_arith simp_intro subsingleton
--   trivial whisker_simps

If you're interested in less mathematical tactics as well, bv_decide can do some fancy stuff like:

import Std.Tactic.BVDecide

example (x : BitVec 32) :
    x + x.sdiv 8#32 * 4294967288#32 &&& 1#32 = x &&& 1#32 := by
  bv_decide

Which I'm pretty sure no other tactic will solve for you.

Isak Colboubrani (Feb 23 2025 at 07:18):

@Henrik Böving Every tactic tt available after import Mathlib that "uniquely solves" example a : b := by t for some aa and bb is of interest. Thus, bv_decide is a welcome addition! Thanks!

Isak Colboubrani (Feb 23 2025 at 07:34):

@Daniel Weber Very nice findings. Thanks a lot! I've added the finiteness, infer_instance, module, native_decide, and trivial examples to the collection.

Isak Colboubrani (Feb 23 2025 at 08:07):

It seems that all the examples I’ve tested for aesop_cat can also be solved by aesop. Is this expected, and does it hold in general? Or is there a case where aesop_cat succeeds while aesop does not?

#mwe

import Mathlib
open CategoryTheory Limits MonoidalCategory

example (C : Type) [Category C] (X Y : C) (α : X  Y) (f : Aut X) : α.conjAut f = α.symm ≪≫ f ≪≫ α := by aesop
example (C : Type) [Category C] [HasFiniteProducts C] [HasPullbacks C] (X₁ X₂ : Dial C) : (𝟙 X₁  𝟙 X₂ : _  _) = 𝟙 (X₁  X₂ : Dial C) := by aesop
example (C : Type) [Category C] (D : Type) [Category D] (e : C  D) : e.changeFunctor (Iso.refl _) = e := by aesop

example (C : Type) [Category C] (X Y : C) (α : X  Y) (f : Aut X) : α.conjAut f = α.symm ≪≫ f ≪≫ α := by aesop_cat
example (C : Type) [Category C] [HasFiniteProducts C] [HasPullbacks C] (X₁ X₂ : Dial C) : (𝟙 X₁  𝟙 X₂ : _  _) = 𝟙 (X₁  X₂ : Dial C) := by aesop_cat
example (C : Type) [Category C] (D : Type) [Category D] (e : C  D) : e.changeFunctor (Iso.refl _) = e := by aesop_cat

Isak Colboubrani (Feb 23 2025 at 12:38):

The following 16 tactics currently lack unique test cases:

aesop_cat bound cancel_denoms continuity
linarith match_scalars nlinarith nofun norm_cast
pure_coherence simp simp_all simp_arith simp_intro
subsingleton whisker_simps

I'm a bit surprised that I can't construct a unique test case for linarith (or nlinarith). Should I be?

Are any of these 16 tactics particularly unlikely to have a unique example?

Even better—can you come up with a unique example for any of them?

Isak Colboubrani (Feb 23 2025 at 12:43):

I think this is a unique test case for bound:

import Mathlib

example (a b : ) (h1 : a  b) (h2 : 3  b) : b ^ 2 + a  b ^ 2 - a   b ^ 2 - a  b ^ 2 - b  (b - 1) * b  2 * b := by bound

Does anyone have a counterexample tactic (i.e., a tactic other than bound that solves this) to prove otherwise?

Kyle Miller (Feb 23 2025 at 15:15):

Isak Colboubrani said:

I've added [...] native_decide [...]

(Just note that native_decide is not accepted in mathlib contributions. It uses the docs#Lean.ofReduceBool axiom, which is a way for the kernel to call out to user-defined compiled code via docs#Lean.reduceBool to more efficiently evaluate a boolean value. There are many tricks to get native_decide to prove false things. But, with careful auditing, the risk can be bounded. Just pointing this out so that things it can do don't count against unique capabilities of other tactics.)

Mario Carneiro (Feb 23 2025 at 18:32):

Do we have a native_decide linter in mathlib?

Damiano Testa (Feb 23 2025 at 18:35):

I don't think that there is a native_decide linter, but it seems to only be used in MathlibTest/Polynomial.lean.

Isak Colboubrani (Feb 23 2025 at 22:36):

Update -- currently at 29 tactics with unique test cases:

import Mathlib

set_option autoImplicit false
open ArithmeticFunction CategoryTheory MonoidalCategory Polynomial

-- Presumably "unique" test cases for each tactic: no other tactic can directly solve these goals.
example (α : Type) (a b : α) [AddCommMonoid α] : a + (b + a) = a + a + b := by abel
example (P : Nat  Prop) (h : {x // P x}) :  x, P x  0  x := by aesop
example (R : Type) [Field R] (n : ) : IsMultiplicative <| (σ n * pow (n + 3)).ppow 2 := by arith_mult
example (a b c : ) (h : (c : ) = a / b) : (c : ) = a / b := by assumption_mod_cast
example (a b : ) (h1 : a  b) (h2 : 3  b) : b ^ 2 + a  b ^ 2 - a   b ^ 2 - a  b ^ 2 - b  (b - 1) * b  2 * b := by bound
example (x : BitVec 32) : x + x.sdiv 8#32 * 4294967288#32 &&& 1#32 = x &&& 1#32 := by bv_decide
example (C : Type) [CategoryTheory.Category C] [CategoryTheory.MonoidalCategory C] (f : 𝟙_ C  _) : f  (λ_ (𝟙_ C)).hom = f  (ρ_ (𝟙_ C)).hom := by coherence
example : natDegree (X + 1 : Nat[X])  1 := by compute_degree
example :  m, m < 25   n, n < 25   c, c < 25  m ^ 2 + n ^ 2 + c ^ 2  7 := by decide
example (R : Type) (a b : R) [CommRing R] (u₁ : Rˣ) : a /ₚ u₁ + b /ₚ u₁ = (a + b) /ₚ u₁ := by field_simp
example (a : ) : (ENNReal.ofReal (1 + a ^ 2))⁻¹ <  := by finiteness
example : Measurable fun x :  => (x * x - 1) / x + (x - x*x) := by fun_prop
example (a b c : ) (h : a  b) : c - Real.exp b  c - Real.exp a := by gcongr
example (G : Type) (a b c : G) [Group G] : c⁻¹*(b*c⁻¹)*c*(a*b)*(b⁻¹*a⁻¹*b⁻¹)*c = 1 := by group
example : Field  := by infer_instance
example (α β γ : Type) (e : PartialEquiv α β) (e' : PartialEquiv β γ) : (e.trans e').source = e.source  Set.preimage e (e.target  e'.source) := by mfld_set_tac
example (V K : Type)[AddCommGroup V] [CommRing K] [Module K V] {x y : V} {a b c d : K} : (c - d)  a  x = (a  c  x + b  d  y) - d  (a  x + b  y) := by module
example (R : Type) (a b : R) [Ring R] : (a + b)^3 = a^3 + a^2*b + a*b*a + a*b^2 + b*a^2 + b*a*b + b^2*a + b^3 := by noncomm_ring
example : (2 : ) < 5 / 2  5 / 2 < 3 := by norm_num
example (a : Nat) : (((a + (2 ^ 64 - 1)) % 2 ^ 64 + 1) * 8 - 1 - (a + (2 ^ 64 - 1)) % 2 ^ 64 * 8 + 1) = 8 := by omega
example (a b c d e f : ) (h : b * c = e * f) : a * b * c * d = a * e * f * d := by polyrith
example (a b : ) (ha : 3 < a) (hb : 4  b) : 0 < 3 + a * b / 7 + b + 7 + 14 := by positivity
example : (-126432 : ZMod 1235412223) ^ 12355342321 = 1001528716 := by reduce_mod_char
example (α : Type) (a b c : α) [LinearOrderedField α] :  a * (-c / b) * (-c / b) + -c + c = a * (c / b * (c / b)) := by ring
example (α : Type) (xs ys : List α) : (xs ++ ys ++ ys).length = 2 * ys.length + xs.length := by rw_search
example (α : Nat  Type) (f : (n : Nat)  α n  α (n+1)) (a : α 0) : α 4 := by solve_by_elim
example (P : Nat  Prop) (n : Nat) : P n  n = 7  n = 0  ¬ (n = 7  n = 0)  P n := by tauto
example (m : Nat) (h : m  1) : True  m  1   n < 100, n^2 < 10000 := by trivial
example (P Q : Prop) (h₁ : P  Q) (h₂ : Q  P) : List.TFAE [P, Q] := by tfae_finish
-- The following tactics currently lack unique test cases:
--   aesop_cat cancel_denoms continuity interval_cases linarith
--   match_scalars nlinarith nofun norm_cast pure_coherence simp
--   simp_all simp_arith simp_intro subsingleton whisker_simps
--
-- Contributions welcome!

Aaron Liu (Feb 23 2025 at 22:46):

Your example for fun_prop is also solved (a lot more slowly) by measurability.

import Mathlib

example : Measurable fun x :  => (x * x - 1) / x + (x - x*x) := by measurability

Damiano Testa (Feb 23 2025 at 22:55):

The solve_by_elim example is also solved by exact? and aesop.

Rado Kirov (Feb 23 2025 at 23:21):

That's a great list, thanks for compiling it! Some of these look quite non-minimal, would it be worth trying to make them minimal while preserving the uniqueness property? I tried to give it a shot, but to test the uniqueness I need have an automated test that no other tactic solves it. It is easy to script, but a fresh run of lake lean <one_line_generated_file.lean> takes ~2min each on my machine! I thought its due to rechecking Mathlib, but AFAIKT it uses -t=max by default (I have no idea what I am doing)?

Damiano Testa (Feb 24 2025 at 01:33):

Here is a simple-minded modification of hint that tries out all tactics and reports which goals are solved by exactly one tactic and which ones are solved by more than one.

import Mathlib

/-!
This is a straightforward (and buggy!) modification of `hint`
that tries out several tactics and reports which ones are successful.
-/

-- clear the `hint` cache
run_cmd
  Lean.modifyEnv (Mathlib.Tactic.Hint.hintExtension.setState · [])

-- add the tactics that we want to test for uniqueness
register_hint abel
register_hint aesop
register_hint arith_mult
register_hint assumption_mod_cast
register_hint bound
register_hint bv_decide
register_hint coherence
register_hint compute_degree
register_hint decide
register_hint field_simp
register_hint finiteness
register_hint fun_prop
register_hint gcongr
register_hint infer_instance
register_hint group
register_hint mfld_set_tac
register_hint module
register_hint noncomm_ring
register_hint norm_num
register_hint omega
register_hint polyrith
register_hint positivity
register_hint reduce_mod_char
register_hint ring
register_hint rw_search
register_hint solve_by_elim
register_hint tauto
register_hint trivial
register_hint tfae_finish

open Lean Elab Mathlib Meta Tactic Hint TryThis in
def hint (stx : Syntax) : TacticM Unit := withMainContext do
  let tacs := Nondet.ofList ( getHints)
  let results := tacs.filterMapM fun t : TSyntax `tactic => do
    if let some msgs  observing? (withMessageLog (withoutInfoTrees ((do evalTactic t; evalTactic ( `(tactic| done))) <|> return))) then
      return some ( getGoals,  suggestion t msgs)
    else
      return none
  let results  (results.toMLList).asArray
  let results := results.filter (·.1.1.isEmpty)
  addSuggestions stx (results.map (·.1.2))
  for r in results do
    setMCtx r.2.term.meta.meta.mctx
  if results.size != 1 then
    logWarning "More than one tactic solves the goal!"

/-- Try all the registered tactics and report which ones solve the goal. -/
syntax (name := uniqueTacStx) "unique_tac" : tactic

@[inherit_doc uniqueTacStx]
elab_rules : tactic
  | `(tactic| unique_tac%$tk) => hint tk

set_option autoImplicit false
open ArithmeticFunction CategoryTheory MonoidalCategory Polynomial

-- Presumably "unique" test cases for each tactic: no other tactic can directly solve these goals.
example (α : Type) (a b : α) [AddCommMonoid α] : a + (b + a) = a + a + b := by unique_tac
example (P : Nat  Prop) (h : {x // P x}) :  x, P x  0  x := by unique_tac
example (R : Type) [Field R] (n : ) : IsMultiplicative <| (σ n * pow (n + 3)).ppow 2 := by unique_tac
example (a b c : ) (h : (c : ) = a / b) : (c : ) = a / b := by unique_tac
example (a b : ) (h1 : a  b) (h2 : 3  b) : b ^ 2 + a  b ^ 2 - a   b ^ 2 - a  b ^ 2 - b  (b - 1) * b  2 * b := by unique_tac
example (x : BitVec 32) : x + x.sdiv 8#32 * 4294967288#32 &&& 1#32 = x &&& 1#32 := by unique_tac
example (C : Type) [CategoryTheory.Category C] [CategoryTheory.MonoidalCategory C] (f : 𝟙_ C  _) : f  (λ_ (𝟙_ C)).hom = f  (ρ_ (𝟙_ C)).hom := by unique_tac
example : natDegree (X + 1 : Nat[X])  1 := by unique_tac
example :  m, m < 25   n, n < 25   c, c < 25  m ^ 2 + n ^ 2 + c ^ 2  7 := by unique_tac
example (R : Type) (a b : R) [CommRing R] (u₁ : Rˣ) : a /ₚ u₁ + b /ₚ u₁ = (a + b) /ₚ u₁ := by unique_tac
example (a : ) : (ENNReal.ofReal (1 + a ^ 2))⁻¹ <  := by unique_tac
example : Measurable fun x :  => (x * x - 1) / x + (x - x*x) := by unique_tac
example (a b c : ) (h : a  b) : c - Real.exp b  c - Real.exp a := by unique_tac
example (G : Type) (a b c : G) [Group G] : c⁻¹*(b*c⁻¹)*c*(a*b)*(b⁻¹*a⁻¹*b⁻¹)*c = 1 := by unique_tac
example : Field  := by unique_tac
example (α β γ : Type) (e : PartialEquiv α β) (e' : PartialEquiv β γ) : (e.trans e').source = e.source  Set.preimage e (e.target  e'.source) := by unique_tac
example (V K : Type)[AddCommGroup V] [CommRing K] [Module K V] {x y : V} {a b c d : K} : (c - d)  a  x = (a  c  x + b  d  y) - d  (a  x + b  y) := by unique_tac
example (R : Type) (a b : R) [Ring R] : (a + b)^3 = a^3 + a^2*b + a*b*a + a*b^2 + b*a^2 + b*a*b + b^2*a + b^3 := by unique_tac
example : (2 : ) < 5 / 2  5 / 2 < 3 := by unique_tac
example (a : Nat) : (((a + (2 ^ 64 - 1)) % 2 ^ 64 + 1) * 8 - 1 - (a + (2 ^ 64 - 1)) % 2 ^ 64 * 8 + 1) = 8 := by unique_tac
example (a b c d e f : ) (h : b * c = e * f) : a * b * c * d = a * e * f * d := by unique_tac
example (a b : ) (ha : 3 < a) (hb : 4  b) : 0 < 3 + a * b / 7 + b + 7 + 14 := by unique_tac
example : (-126432 : ZMod 1235412223) ^ 12355342321 = 1001528716 := by unique_tac
example (α : Type) (a b c : α) [LinearOrderedField α] :  a * (-c / b) * (-c / b) + -c + c = a * (c / b * (c / b)) := by unique_tac
example (α : Type) (xs ys : List α) : (xs ++ ys ++ ys).length = 2 * ys.length + xs.length := by unique_tac
example (α : Nat  Type) (f : (n : Nat)  α n  α (n+1)) (a : α 0) : α 4 := by unique_tac
example (P : Nat  Prop) (n : Nat) : P n  n = 7  n = 0  ¬ (n = 7  n = 0)  P n := by unique_tac
example (m : Nat) (h : m  1) : True  m  1   n < 100, n^2 < 10000 := by unique_tac
example (P Q : Prop) (h₁ : P  Q) (h₂ : Q  P) : List.TFAE [P, Q] := by unique_tac
-- The following tactics currently lack unique test cases:
--   aesop_cat cancel_denoms continuity interval_cases linarith
--   match_scalars nlinarith nofun norm_cast pure_coherence simp
--   simp_all simp_arith simp_intro subsingleton whisker_simps
--
-- Contributions welcome!

Damiano Testa (Feb 24 2025 at 01:34):

It is buggy and produces some errors, but, when it does not have errors, it seems to correctly report whether exactly one tactic solves the goal (with a blue squiggle) and when more than one solves the goal (with a yellow squiggle).

Damiano Testa (Feb 24 2025 at 01:35):

Some of the errors are timeouts/max-depth overflows, others I do not really understand.

Rado Kirov (Feb 24 2025 at 03:50):

Neat! Starring this as a good example of the power of custom tactics (once I get to learning those).

Isak Colboubrani (Feb 24 2025 at 22:10):

@Damiano Testa The hint modification is incredibly useful—thank you for your help!

Damiano Testa (Feb 24 2025 at 22:10):

I am glad that it is helpful! I wish it was more stable...

Isak Colboubrani (Feb 24 2025 at 22:12):

It appears that several test cases are not unique:

  • The abel test case is solved also by module
  • The aesop test case is solved also by bound
  • The decide test case is solved also by trivial
  • The fun_prop test case is solved also by measurability
  • The gcongr test case is solved also by bound
  • The mfld_set_tac test case is solved also by aesop
  • The norm_num test case is solved also by bound
  • The positivity test case is solved also by finiteness and field_simp
  • The ring test case is solved also by group
  • The solve_by_elim test case is solved also by aesop, arith_mult, bound and tauto

Damiano Testa (Feb 24 2025 at 22:13):

Do not just trust the output of hint1: verify that what it says is actually true! :smile:

Isak Colboubrani (Feb 24 2025 at 22:14):

I did! :)

Damiano Testa (Feb 24 2025 at 22:14):

I think that if it exits with no errors it is reliable, but there is a lot of internal hiding/catching of errors that I am not entirely sure of this.

Isak Colboubrani (Feb 24 2025 at 22:16):

Verification #mwe:

import Mathlib

set_option autoImplicit false
open ArithmeticFunction CategoryTheory MonoidalCategory Polynomial

-- abel test case is not unique
example (α : Type) (a b : α) [AddCommMonoid α] : a + (b + a) = a + a + b := by abel
example (α : Type) (a b : α) [AddCommMonoid α] : a + (b + a) = a + a + b := by module

-- aesop test case is not unique
example (P : Nat  Prop) (h : {x // P x}) :  x, P x  0  x := by aesop
example (P : Nat  Prop) (h : {x // P x}) :  x, P x  0  x := by bound

-- decide test case is not unique
example :  m, m < 25   n, n < 25   c, c < 25  m ^ 2 + n ^ 2 + c ^ 2  7 := by decide
example :  m, m < 25   n, n < 25   c, c < 25  m ^ 2 + n ^ 2 + c ^ 2  7 := by trivial

-- fun_prop test case is not unique
example : Measurable fun x :  => (x * x - 1) / x + (x - x*x) := by fun_prop
example : Measurable fun x :  => (x * x - 1) / x + (x - x*x) := by measurability

-- gcongr test case is not unique
example (a b c : ) (h : a  b) : c - Real.exp b  c - Real.exp a := by gcongr
example (a b c : ) (h : a  b) : c - Real.exp b  c - Real.exp a := by bound

-- mfld_set_tac test case is not unique
example (α β γ : Type) (e : PartialEquiv α β) (e' : PartialEquiv β γ) : (e.trans e').source = e.source  Set.preimage e (e.target  e'.source) := by mfld_set_tac
example (α β γ : Type) (e : PartialEquiv α β) (e' : PartialEquiv β γ) : (e.trans e').source = e.source  Set.preimage e (e.target  e'.source) := by aesop

-- norm_num test case is not unique
example : (2 : ) < 5 / 2  5 / 2 < 3 := by norm_num
example : (2 : ) < 5 / 2  5 / 2 < 3 := by bound

-- positivity test case is not unique
example (a b : ) (ha : 3 < a) (hb : 4  b) : 0 < 3 + a * b / 7 + b + 7 + 14 := by positivity
example (a b : ) (ha : 3 < a) (hb : 4  b) : 0 < 3 + a * b / 7 + b + 7 + 14 := by finiteness
example (a b : ) (ha : 3 < a) (hb : 4  b) : 0 < 3 + a * b / 7 + b + 7 + 14 := by field_simp

-- solve_by_elim test case is not unique
example (α : Nat  Type) (f : (n : Nat)  α n  α (n+1)) (a : α 0) : α 4 := by solve_by_elim
example (α : Nat  Type) (f : (n : Nat)  α n  α (n+1)) (a : α 0) : α 4 := by aesop
example (α : Nat  Type) (f : (n : Nat)  α n  α (n+1)) (a : α 0) : α 4 := by arith_mult
example (α : Nat  Type) (f : (n : Nat)  α n  α (n+1)) (a : α 0) : α 4 := by bound
example (α : Nat  Type) (f : (n : Nat)  α n  α (n+1)) (a : α 0) : α 4 := by tauto

-- ring test case is not unique
example (α : Type) (a b c : α) [LinearOrderedField α] :  a * (-c / b) * (-c / b) + -c + c = a * (c / b * (c / b)) := by ring
example (α : Type) (a b c : α) [LinearOrderedField α] :  a * (-c / b) * (-c / b) + -c + c = a * (c / b * (c / b)) := by group

Damiano Testa (Feb 24 2025 at 22:17):

By the way, if you can find a goal that is solved by exactly one tactic that is nice. But even goals that are solved by multiple tactics can still be pedagogically useful to know what you should expect a tactic to be able to solve.

Aaron Liu (Feb 24 2025 at 22:25):

Beware the false-positives! They sneak in.

import Mathlib

set_option autoImplicit false

-- `bound` is build off `aesop`
example (P : Nat  Prop) (h : {x // P x}) :  x, P x  0  x := by aesop
example (P : Nat  Prop) (h : {x // P x}) :  x, P x  0  x := by bound

-- `trivial` is calling `decide`
example :  m, m < 25   n, n < 25   c, c < 25  m ^ 2 + n ^ 2 + c ^ 2  7 := by decide
example :  m, m < 25   n, n < 25   c, c < 25  m ^ 2 + n ^ 2 + c ^ 2  7 := by trivial

-- `bound` calls `norm_num`
example : (2 : ) < 5 / 2  5 / 2 < 3 := by norm_num
example : (2 : ) < 5 / 2  5 / 2 < 3 := by bound

-- `finiteness` and `field_simp` both call `positivity`
example (a b : ) (ha : 3 < a) (hb : 4  b) : 0 < 3 + a * b / 7 + b + 7 + 14 := by positivity
example (a b : ) (ha : 3 < a) (hb : 4  b) : 0 < 3 + a * b / 7 + b + 7 + 14 := by finiteness
example (a b : ) (ha : 3 < a) (hb : 4  b) : 0 < 3 + a * b / 7 + b + 7 + 14 := by field_simp

-- I strongly suspect all of these are calling `solve_by_elim`
example (α : Nat  Type) (f : (n : Nat)  α n  α (n+1)) (a : α 0) : α 4 := by solve_by_elim
example (α : Nat  Type) (f : (n : Nat)  α n  α (n+1)) (a : α 0) : α 4 := by aesop
example (α : Nat  Type) (f : (n : Nat)  α n  α (n+1)) (a : α 0) : α 4 := by arith_mult
example (α : Nat  Type) (f : (n : Nat)  α n  α (n+1)) (a : α 0) : α 4 := by bound
example (α : Nat  Type) (f : (n : Nat)  α n  α (n+1)) (a : α 0) : α 4 := by tauto

-- `group` is calling `ring`
example (α : Type) (a b c : α) [LinearOrderedField α] :  a * (-c / b) * (-c / b) + -c + c = a * (c / b * (c / b)) := by ring
example (α : Type) (a b c : α) [LinearOrderedField α] :  a * (-c / b) * (-c / b) + -c + c = a * (c / b * (c / b)) := by group

Isak Colboubrani (Feb 24 2025 at 22:37):

Yes, @Aaron Liu , that's an excellent point, and it was exactly my next step—to investigate which of these are "real duplicates" as defined in the first post of this thread: "By ‘uniquely solved,’ I loosely mean that no other tactic can directly solve these goals unless it internally calls the same ‘primary tactic.’"

Thank you for helping me with that investigation!

It seems that the remaining "real duplicates" after this investigation are as follows:

import Mathlib

set_option autoImplicit false
open ArithmeticFunction CategoryTheory MonoidalCategory Polynomial

-- abel test case is not unique
example (α : Type) (a b : α) [AddCommMonoid α] : a + (b + a) = a + a + b := by abel
example (α : Type) (a b : α) [AddCommMonoid α] : a + (b + a) = a + a + b := by module

-- fun_prop test case is not unique
example : Measurable fun x :  => (x * x - 1) / x + (x - x*x) := by fun_prop
example : Measurable fun x :  => (x * x - 1) / x + (x - x*x) := by measurability

-- gcongr test case is not unique
example (a b c : ) (h : a  b) : c - Real.exp b  c - Real.exp a := by gcongr
example (a b c : ) (h : a  b) : c - Real.exp b  c - Real.exp a := by bound

-- mfld_set_tac test case is not unique
example (α β γ : Type) (e : PartialEquiv α β) (e' : PartialEquiv β γ) : (e.trans e').source = e.source  Set.preimage e (e.target  e'.source) := by mfld_set_tac
example (α β γ : Type) (e : PartialEquiv α β) (e' : PartialEquiv β γ) : (e.trans e').source = e.source  Set.preimage e (e.target  e'.source) := by aesop

Isak Colboubrani (Feb 25 2025 at 08:10):

Generally, test cases that can be solved by gcongr are often also solvable by bound or aesop. However, the following test case is solved exclusively by gcongr:

example (b : ) (hb : 3  b [ZMOD 5]) : b ^ 2  3 ^ 2 [ZMOD 5] := by gcongr

Isak Colboubrani (Feb 25 2025 at 08:26):

I haven’t been able to create a test case that can be solved by abel but not by module (which makes some sense). Is it possible to create such a test case, or does the module tactic indeed have a strictly broader capability set than the abel tactic?

Isak Colboubrani (Feb 25 2025 at 08:49):

Generally, the test cases I have tested that can be solved by fun_prop are often also solvable by continuity. However, the following test case is solved exclusively by fun_prop:

example {α} [MeasurableSpace α] (f : α  α  α) (hf : Measurable fun (x,y) => f x y) (a : α) : Measurable (fun x => (f x a, f (f x x) (f (f x x) x))) := by fun_prop

Isak Colboubrani (Feb 25 2025 at 09:11):

The following test case seems to be exclusively solved by nlinarith:

example (x y : ) : 0  x*x + y*y := by nlinarith

Isak Colboubrani (Feb 25 2025 at 12:20):

Turns out the positivity test case was not unique since it could be solved also by nlinarith:

example (a b : ) (ha : 3 < a) (hb : 4  b) : 0 < 3 + a * b / 7 + b + 7 + 14 := by positivity
example (a b : ) (ha : 3 < a) (hb : 4  b) : 0 < 3 + a * b / 7 + b + 7 + 14 := by nlinarith

Luckily I found this even simpler test case that is solved exclusively by positivity:

example (a : ) : 0 < |a| + 3 := by positivity

Isak Colboubrani (Feb 25 2025 at 12:25):

I think I found a better omega exclusive test case too:

example (a : ) (hlt : 1  a) (hne : a  1) : 1 < a := by omega

... is solved only by omega and is much simpler than the previous omega exclusive test case …

example (a : ) : (((a + (2 ^ 64 - 1)) % 2 ^ 64 + 1) * 8 - 1 - (a + (2 ^ 64 - 1)) % 2 ^ 64 * 8 + 1) = 8 := by omega

Damiano Testa (Feb 25 2025 at 12:29):

I think that exact? solves the new omega example.

Aaron Liu (Feb 25 2025 at 12:31):

only if you import Mathlib

Damiano Testa (Feb 25 2025 at 12:31):

I thought that it was with import Mathlib, though.

Isak Colboubrani (Feb 25 2025 at 12:37):

Thanks @Damiano Testa, you're right! Reverting to the previous omega test case.

#mwe

import Mathlib

example (a : ) (hlt : 1  a) (hne : a  1) : 1 < a := by omega
example (a : ) (hlt : 1  a) (hne : a  1) : 1 < a := by exact?

example (a : ) : (((a + (2 ^ 64 - 1)) % 2 ^ 64 + 1) * 8 - 1 - (a + (2 ^ 64 - 1)) % 2 ^ 64 * 8 + 1) = 8 := by omega

Isak Colboubrani (Feb 25 2025 at 12:39):

Yes, import Mathlib is assumed. From a previous message:

Every tactic tt available after import Mathlib that "uniquely solves" example a : b := by t for some aa and bb is of interest.

For demonstration purposes, I’m compiling a list of goals that are “uniquely solved” by certain closing tactics (with zero arguments). By “uniquely solved,” I loosely mean that no other tactic can directly solve these goals unless it internally calls the same “primary tactic.”

Isak Colboubrani (Feb 28 2025 at 23:10):

I believe I’ve found a grind test case that no other tactic seems able to solve:

import Mathlib
example (P : Nat  Prop) (hp : P Nat.zero.succ) (h :  n, P n  P n.succ) : P Nat.zero.succ.succ.succ.succ := by grind

Isak Colboubrani (Mar 01 2025 at 07:27):

Found a few more presumably "unique" grind test cases:

import Mathlib
example {α} (a b c : α) [LE α] : ¬(¬a  b  a  c  ¬a  c  a  b)  a  b  a  c  ¬a  c  ¬a  b := by grind
example (f : Nat  Nat) (a b c d e : Nat) : f (a + 3) = b  f (c + 1) = d  c  a + 2  a + 1  e  e < c  b = d := by grind
example (a b : Nat) (f : Nat  Nat) : f 1 = a  b  1  b  1  f b = a := by grind
example (a b c : Nat)  (f : Nat  Nat) : f 2 = a  b  1  b  1  c = b + 1  f c = a := by grind

Daniel Weber (Mar 01 2025 at 07:35):

tauto can solve the first one

Isak Colboubrani (Mar 01 2025 at 12:17):

Oh, you're right. Thanks for pointing out that counterexample.

Updated list of presumably "unique" grind test cases:

import Mathlib

example (P : Nat  Prop) (hp : P Nat.zero.succ) (h :  n, P n  P n.succ) : P Nat.zero.succ.succ.succ.succ := by grind
example (f : Nat  Nat) (a b c d e : Nat) : f (a + 3) = b  f (c + 1) = d  c  a + 2  a + 1  e  e < c  b = d := by grind
example (a b : Nat) (f : Nat  Nat) : f 1 = a  b  1  b  1  f b = a := by grind
example (a b c : Nat)  (f : Nat  Nat) : f 2 = a  b  1  b  1  c = b + 1  f c = a := by grind

Isak Colboubrani (Mar 01 2025 at 12:22):

And here's another, perhaps more interesting presumably "unique" grind test case:

import Mathlib
example {α : Type*} {p : α  Prop} {a : α} {s : Set α} (h :  a  s,  b  s, p a  p b) (h' : a  s) :  b  s, p b := by grind

Isak Colboubrani (Mar 03 2025 at 09:24):

One new and one improved "unique" test case solvable only by positivity:

import Mathlib
open Nat
example (n : ) : 0 < (n)! + (n + 1)! := by positivity
example (n : ) : 0 < |n| + 1 := by positivity

Isak Colboubrani (Mar 03 2025 at 20:12):

Found a new "unique" test case solvable only by bound, which is a bit simpler than the old test case:

import Mathlib
-- new test case
example (x y : ) (h₁ : 0  y) (h₂ : x  y) : x / y  1 := by bound
-- old test case
example (a b : ) (h₁ : a  b) (h₂ : 3  b) : b ^ 2 + a  b ^ 2 - a  b ^ 2 - a  b ^ 2 - b  (b - 1) * b  2 * b := by bound

Isak Colboubrani (Mar 04 2025 at 20:18):

Found one unique test case for continuity and one for fun_prop:

import Mathlib

example {W X Y Z : Type*} [TopologicalSpace W] [TopologicalSpace X] [TopologicalSpace Y] [TopologicalSpace Z]
  (f : C(X, Y)) (g : C(W, Z)) : Continuous (Prod.map f g) := by continuity

example {α β : Type*} [TopologicalSpace α] [TopologicalSpace β] {x₀ : α} (f : α  α  β)
  (hf : ContinuousAt (Function.uncurry f) (x₀, x₀)) :
  ContinuousAt (fun x  f x x) x₀ := by fun_prop

Isak Colboubrani (Mar 07 2025 at 07:01):

Found a pretty nice "unique" test case solvable only by tauto_set:

import Mathlib

example {α : Type*} (s t u : Set α) : s  t  s  u  s  (t  u) := by tauto_set

Isak Colboubrani (Mar 10 2025 at 19:36):

Found a presumably unique test case solvable only by monicity!:

import Mathlib
open Polynomial
example {R : Type*} [Ring R] : Monic (X ^ 2 + X + 1 : R[X]) := by monicity!

Isak Colboubrani (Mar 18 2025 at 23:32):

Is there a test case that simp_all can close but aesop cannot?

From what I understand, aesop calls simp_all in at least some situations, but I’m not sure if aesop is a strict superset of simp_all in terms of capabilities.

Isak Colboubrani (Apr 06 2025 at 18:57):

Adding a "unique" test case solvable only by the exciting and still under development grind tactic:

import Mathlib
example (x y : ) : 27  11*x + 13*y  11*x + 13*y  45  -10  7*x - 9*y  7*x - 9*y  4  False := by grind

Isak Colboubrani (Apr 08 2025 at 05:52):

Found a presumably unique test case solvable only by abel:

import Mathlib
example {α} [AddCommMonoid α] (a b c d: α) : a + b + c + d + 0 = d + (c + b) + (0 + 0 + a) := by abel

Isak Colboubrani (Apr 09 2025 at 21:30):

Found a presumably unique test case solvable only by aesop_cat:

import Mathlib
open CategoryTheory MonoidalCategory
example {C} [Category C] [Limits.HasFiniteProducts C] [Limits.HasPullbacks C] (X₁ X₂ : Dial C) : (𝟙 X₁  𝟙 X₂ : _  _) = 𝟙 (X₁  X₂ : Dial C) := by aesop_cat

Last updated: May 02 2025 at 03:31 UTC