Documentation

Mathlib.Tactic.GCongr.Core

The gcongr ("generalized congruence") tactic #

The gcongr tactic applies "generalized congruence" rules, reducing a relational goal between an LHS and RHS matching the same pattern to relational subgoals between the differing inputs to the pattern. For example,

example {a b x c d : ℝ} (h1 : a + 1 ≤ b + 1) (h2 : c + 2 ≤ d + 2) :
    x ^ 2 * a + c ≤ x ^ 2 * b + d := by
  gcongr
  · linarith
  · linarith

This example has the goal of proving the relation between an LHS and RHS both of the pattern

x ^ 2 * ?_ + ?_

(with inputs a, c on the left and b, d on the right); after the use of gcongr, we have the simpler goals a ≤ b and c ≤ d.

A depth limit, or a pattern can be provided explicitly; this is useful if a non-maximal match is desired:

example {a b c d x : ℝ} (h : a + c + 1 ≤ b + d + 1) :
    x ^ 2 * (a + c) + 5 ≤ x ^ 2 * (b + d) + 5 := by
  gcongr x ^ 2 * ?_ + 5 -- or `gcongr 2`
  linarith

Sourcing the generalized congruence lemmas #

Relevant "generalized congruence" lemmas are declared using the attribute @[gcongr]. For example, the first example constructs the proof term

add_le_add (mul_le_mul_of_nonneg_left _ (pow_bit0_nonneg x 1)) _

using the generalized congruence lemmas add_le_add and mul_le_mul_of_nonneg_left. The term pow_bit0_nonneg x 1 is automatically generated by a discharger (see below).

When a lemma is tagged @[gcongr], it is verified that the lemma is of "generalized congruence" form, f x₁ y z₁ ∼ f x₂ y z₂, that is, a relation between the application of a function to two argument lists, in which the "varying argument" pairs (here x₁/x₂ and z₁/z₂) are all free variables. The gcongr tactic will try a lemma only if it matches the goal in relation , head function f and the arity of f. It prioritizes lemmas with fewer "varying arguments". Thus, for example, all three of the following lemmas are tagged @[gcongr] and are used in different situations according to whether the goal compares constant-left-multiplications, constant-right-multiplications, or fully varying multiplications:

theorem mul_le_mul_of_nonneg_left [Mul α] [Zero α] [Preorder α] [PosMulMono α]
    {a b c : α} (h : b ≤ c) (a0 : 0 ≤ a) :
    a * b ≤ a * c

theorem mul_le_mul_of_nonneg_right [Mul α] [Zero α] [Preorder α] [MulPosMono α]
    {a b c : α} (h : b ≤ c) (a0 : 0 ≤ a) :
    b * a ≤ c * a

theorem mul_le_mul [MulZeroClass α] [Preorder α] [PosMulMono α] [MulPosMono α]
    {a b c d : α} (h₁ : a ≤ b) (h₂ : c ≤ d) (c0 : 0 ≤ c) (b0 : 0 ≤ b) :
    a * c ≤ b * d

The advantage of this approach is that the lemmas with fewer "varying" input pairs typically require fewer side conditions, so the tactic becomes more useful by special-casing them.

There can also be more than one generalized congruence lemma dealing with the same relation and head function, for example with purely notational head functions which have different theories when different typeclass assumptions apply. For example, the following lemma is stored with the same @[gcongr] data as mul_le_mul above, and the two lemmas are simply tried in succession to determine which has the typeclasses relevant to the goal:

theorem mul_le_mul' [Mul α] [Preorder α] [MulLeftMono α]
    [MulRightMono α] {a b c d : α} (h₁ : a ≤ b) (h₂ : c ≤ d) :
    a * c ≤ b * d

Resolving goals #

The tactic attempts to discharge side goals to the "generalized congruence" lemmas (such as the side goal 0 ≤ x ^ 2 in the above application of mul_le_mul_of_nonneg_left) using the tactic gcongr_discharger, which wraps positivity but can also be extended. Side goals not discharged in this way are left for the user.

The tactic also attempts to discharge "main" goals using the available hypotheses, as well as a limited amount of forward reasoning. Such attempts are made before descending further into matching by congruence. The built-in forward-reasoning includes reasoning by symmetry and reflexivity, and this can be extended by writing tactic extensions tagged with the @[gcongr_forward] attribute.

Introducing variables and hypotheses #

Some natural generalized congruence lemmas have "main" hypotheses which are universally quantified or have the structure of an implication, for example

theorem GCongr.Finset.sum_le_sum [OrderedAddCommMonoid N] {f g : ι → N} {s : Finset ι}
    (h : ∀ (i : ι), i ∈ s → f i ≤ g i) :
    s.sum f ≤ s.sum g

The tactic automatically introduces the variable i✝ : ι and hypothesis hi✝ : i✝ ∈ s in the subgoal ∀ (i : ι), i ∈ s → f i ≤ g i generated by applying this lemma. By default this is done anonymously, so they are inaccessible in the goal state which results. The user can name them if needed using the syntax gcongr with i hi.

Variants #

The tactic rel is a variant of gcongr, intended for teaching. Local hypotheses are not used automatically to resolve main goals, but must be invoked by name:

example {a b x c d : ℝ} (h1 : a ≤ b) (h2 : c ≤ d) :
    x ^ 2 * a + c ≤ x ^ 2 * b + d := by
  rel [h1, h2]

The rel tactic is finishing-only: it fails if any main or side goals are not resolved.

Implementation notes #

Patterns #

When you provide a pattern, such as in gcongr x ^ 2 * ?_ + 5, this is elaborated with a metadata annotation at each ?_ hole. This is then unified with the LHS of the relation in the goal. As a result, the pattern becomes the same as the LHS, but with mdata annotations that indicate where the original ?_ holes were. The LHS is then replaced with this expression so that gcongr can tell based on the LHS how far to continue recursively. We also keep track of when then LHS and RHS swap around, so that we know where to look for the metadata annotation.

GCongrKey is the key used in the hashmap for looking up gcongr lemmas.

  • relName : Lean.Name

    The name of the relation. For example, a + b ≤ a + c has relName := `LE.le.

  • head : Lean.Name

    The name of the head function. For example, a + b ≤ a + c has head := `HAdd.hAdd.

  • arity : Nat

    The number of arguments that head is applied to. For example, a + b ≤ a + c has arity := 6, because HAdd.hAdd has 6 arguments.

Instances For
    Equations
    Instances For
      @[implicit_reducible]
      Equations
      • One or more equations did not get rendered due to their size.

      The data for a "main" hypothesis of a gcongr lemma. That is a hypothesis of the form ∀ xs, a xs' ~ b xs' where xs' is a subset of xs, and a and b form a "varying pair" in the conclusion of the gcongr lemma.

      • lhsIdx : Nat

        Index of the left varying argument.

      • rhsIdx : Nat

        Index of the right varying argument.

      • hypIdx : Nat

        Index of the hypothesis itself.

      • hypsPos : List (Option Nat)

        In ∀ xs, a xs' ~ b xs', for each x in xs, the position it appears in xs'. This information is used to infer suitable names when introducing these variables.

      • isContra : Bool

        Whether the order of the two varying arguments is the opposite from in the conclusion.

      Instances For

        Structure recording the data for a "generalized congruence" (gcongr) lemma.

        • key : GCongrKey

          The key under which the lemma is stored.

        • declName : Lean.Name

          The name of the lemma.

        • mainSubgoals : Array GCongrHyp

          mainSubgoals are the subgoals on which gcongr will be recursively called.

        • numHyps : Nat

          The number of arguments that declName takes when applying it.

        • prio : Nat

          The given priority of the lemma, for example as @[gcongr high].

        • numVarying : Nat

          The number of arguments in the application of head that are different. This is used for sorting the lemmas. For example, a + b ≤ a + c has numVarying := 1.

        • forGrw : Bool

          Whether the lemma is suitable for use in grw. For most lemmas this is true, but there are lemmas that aren't quite congruence lemmas, but can still be used in gcongr.

        Instances For
          @[reducible, inline]

          A collection of GCongrLemma, to be stored in the environment extension.

          Equations
          Instances For

            Return true if the priority of a is less than or equal to the priority of b.

            Equations
            Instances For

              Insert a GCongrLemma in a collection of lemmas, making sure that the lemmas are sorted.

              Equations
              • One or more equations did not get rendered due to their size.
              Instances For

                Environment extension for "generalized congruence" (gcongr) lemmas.

                Given an application f a₁ .. aₙ, return the name of f, and the array of arguments aᵢ.

                Equations
                • One or more equations did not get rendered due to their size.
                Instances For

                  If e is of the form r a b, return (r, a, b). Note: we assume that e does not have an Expr.mdata annotation.

                  Equations
                  Instances For

                    If r is of the form rel a b, replace either a or b with e.

                    Equations
                    Instances For
                      def Mathlib.Tactic.GCongr.makeGCongrLemma (hyps : Array Lean.Expr) (target : Lean.Expr) (declName : Lean.Name) (prio : Nat) (forGrw : Bool) :

                      Try to construct the GCongrLemma for a lemma with hypotheses hyps and conclusion target.

                      Equations
                      • One or more equations did not get rendered due to their size.
                      Instances For

                        Attribute marking "generalized congruence" (gcongr) lemmas. Such lemmas must have a conclusion of a form such as f x₁ y z₁ ∼ f x₂ y z₂; that is, a relation between the application of a function to two argument lists, in which the "varying argument" pairs (here x₁/x₂ and z₁/z₂) are all free variables.

                        The antecedents of such a lemma are classified as generating "main goals" if they are of the form x₁ ≈ x₂ for some "varying argument" pair x₁/x₂ (and a possibly different relation to ), or more generally of the form ∀ i h h' j h'', f₁ i j ≈ f₂ i j (say) for some "varying argument" pair f₁/f₂, where the arguments of f₁ and f₂ are the same list of variables which have to be bound by the preceding . (Other antecedents are considered to generate "side goals".) Use gcongr only to relax these conditions. A gcongr only lemma is not used by grw.

                        If a lemma such as add_le_add : a ≤ b → c ≤ d → a + c ≤ b + d has been tagged with gcongr, then a direct consequence like a ≤ b → a + c ≤ b + c does not need to be tagged. However, if a more specific lemma has fewer side conditions, it should also be tagged with gcongr. For example, mul_le_mul_of_nonneg_right and mul_le_mul_of_nonneg_left are both tagged.

                        Lemmas involving < or can also be marked @[bound] for use in the related bound tactic.

                        Equations
                        • One or more equations did not get rendered due to their size.
                        Instances For

                          A version of the rfl tactic that also closes goals of the form ⊢ p → p.

                          Equations
                          • One or more equations did not get rendered due to their size.
                          Instances For

                            gcongr_discharger is used by gcongr to discharge side goals.

                            This is an extensible tactic using macro_rules. By default it calls positivity (after importing the positivity tactic). Example: macro_rules | `(tactic| gcongr_discharger) => `(tactic| positivity).

                            Equations
                            Instances For

                              This is used as the default side-goal discharger, it calls the gcongr_discharger extensible tactic.

                              Equations
                              • One or more equations did not get rendered due to their size.
                              Instances For

                                See if the term is a = b and the goal is a ∼ b or b ∼ a, with reflexive.

                                Equations
                                • One or more equations did not get rendered due to their size.
                                Instances For

                                  Attempt to resolve an (implicitly) relational goal by one of a provided list of hypotheses, either with such a hypothesis directly or by a limited palette of relational forward-reasoning from these hypotheses.

                                  Equations
                                  • One or more equations did not get rendered due to their size.
                                  Instances For

                                    This is used as the default main-goal discharger, consisting of running Lean.MVarId.gcongrForward (trying a term together with limited forward-reasoning on that term) on each nontrivial hypothesis.

                                    Equations
                                    • One or more equations did not get rendered due to their size.
                                    Instances For

                                      Annotate e with a gcongrHole mdata annotation.

                                      Equations
                                      Instances For

                                        Check whether e has a gcongrHole mdata annotation.

                                        Equations
                                        Instances For

                                          Determine whether e contains a gcongrHole mdata annotation in any subexpression. This tells gcongr whether to continue applying gcongr lemmas.

                                          Equations
                                          Instances For

                                            (Internal for gcongr) Elaborates to an expression satisfying hasHoleAnnotation.

                                            Equations
                                            • One or more equations did not get rendered due to their size.
                                            Instances For

                                              (Internal for gcongr) Elaborates to an expression satisfying hasHoleAnnotation.

                                              Equations
                                              • One or more equations did not get rendered due to their size.
                                              Instances For

                                                The lemma rel_imp_rel is too general to be tagged with @[gcongr], so instead we use relImpRelLemma to look it up.

                                                theorem Mathlib.Tactic.GCongr.rel_imp_rel {α : Sort u_1} {r : ααProp} [IsTrans α r] {a b c d : α} (h₁ : r c a) (h₂ : r b d) :
                                                r a br c d

                                                Construct a GCongrLemma for gcongr goals of the form a ≺ b → c ≺ d. This will be tried if there is no other available @[gcongr] lemma. For example, the relation a ≡ b [ZMOD n] has an instance of IsTrans, so a congruence of the form a ≡ b [ZMOD n] → c ≡ d [ZMOD n] can be solved with rel_imp_rel.

                                                Equations
                                                • One or more equations did not get rendered due to their size.
                                                Instances For

                                                  The state used by GCongrM.

                                                  • newGoals : Array Lean.MVarId

                                                    The new goals produced by gcongr. This includes side-goals and main goals. TODO?: split this into separate side and main goals, so that the side goals can be discharged using a nested gcongr by ... syntax.

                                                  • patterns : List (Lean.TSyntax `rintroPat)

                                                    Patterns to use when doing intro.

                                                  Instances For

                                                    The context used by GCongrM.

                                                    Instances For
                                                      def Mathlib.Tactic.GCongr.GCongrM.run {α : Type} (x : GCongrM α) (patterns : List (Lean.TSyntax `rintroPat) := []) (mainGoalDischarger : Lean.MVarIdLean.MetaM Bool := gcongrForwardDischarger) (sideGoalDischarger : Lean.MVarIdLean.MetaM Unit := gcongrDischarger) :

                                                      Run a GCongrM computation in MetaM.

                                                      Equations
                                                      • One or more equations did not get rendered due to their size.
                                                      Instances For

                                                        Add an unsolved goal to the newGoals array in the state.

                                                        Equations
                                                        Instances For

                                                          Run the discharger for side goals.

                                                          Equations
                                                          • One or more equations did not get rendered due to their size.
                                                          Instances For

                                                            Apply generalized congruence lemma lem to goal g, throwing an error if it fails. Return an array of main subgoals and an array of side goals. For each main subgoal, also return whether the sides of the relation are swapped (i.e. whether it is contravariant).

                                                            This function is used by both the gcongr and grw tactic. In the case of grw, one of the two sides of the goal is a metavariable that is filled in by this function.

                                                            Equations
                                                            • One or more equations did not get rendered due to their size.
                                                            Instances For
                                                              partial def Lean.MVarId.gcongr (g : MVarId) (mdataLhs? : Option Bool) (depth : Nat := 1000000) :

                                                              The core of the gcongr tactic. Parse a goal into the form (f _ ... _) ∼ (f _ ... _), look up any relevant @[gcongr] lemmas, try to apply them, recursively run the tactic itself on "main" goals which are generated, and run the discharger on side goals which are generated. If there is a user-provided template, first check that the template asks us to descend this far into the match.

                                                              gcongr applies "generalized congruence" rules to recursively reduce a goal of form ⊢ R (f a₁ ... aₙ) (f b₁ ... bₙ) to (possibly multiple) goal(s) ⊢ Rᵢ aᵢ bᵢ, keeping only the distinct pairs aᵢ ≠ bᵢ, where Rᵢ is a possibly different relation (depending on the precise rule). The relations R, Rᵢ can be any two-argument relation, including · → ·.

                                                              This tactic is extensible: to add a "generalized congruence" rule, tag a theorem with the attribute @[gcongr].

                                                              If a "generalized congruence" lemma has a side goal, gcongr will try to discharge it using gcongr_discharger, which is an extensible tactic based on positivity. Side goals not discharged in this way are left for the user.

                                                              • gcongr with x y ... z names the variables that are introduced by descending into binders (for example sums or suprema).
                                                              • gcongr n, where n is a natural number literal, limits the depth of the recursive applications. This is useful if gcongr is too aggressive in breaking down the goal.
                                                              • gcongr t, where t is a term with ?_ holes, performs congruence up to the holes in t. In other words, gcongr f ?_ turns a goal ⊢ R (f x) (f y) into ⊢ R x y (but no further). This is useful if gcongr is too aggressive in breaking down the goal.

                                                              Examples:

                                                              example {a b x c d : ℝ} (h1 : a + 1 ≤ b + 1) (h2 : c + 2 ≤ d + 2) :
                                                                  x ^ 2 * a + c ≤ x ^ 2 * b + d := by
                                                                -- LHS and RHS both have the form x ^ 2 * ?_ + ?_
                                                                gcongr
                                                                · -- New goal: ⊢ a ≤ b
                                                                  linarith
                                                                · -- ⊢ New goal: c ≤ d
                                                                  linarith
                                                              -- Resulting proof term is:
                                                              --   add_le_add (mul_le_mul_of_nonneg_left ?_ (Even.pow_nonneg (even_two_mul 1) x)) ?_
                                                              -- where `add_le_add` and `mul_le_mul_of_nonneg_left` are generalized congruence lemmas
                                                              -- and the side goal `0 ≤ x ^ 2` is discharged by `gcongr_discharger`.
                                                              
                                                              example {a b c d x : ℝ} (h : a + c + 1 ≤ b + d + 1) :
                                                                  x ^ 2 * (a + c) + 5 ≤ x ^ 2 * (b + d) + 5 := by
                                                                -- Using a pattern to limit the depth.
                                                                gcongr x ^ 2 * ?_ + 5 -- or `gcongr 2`
                                                                -- New goal: ⊢ a + c ≤ b + d
                                                                linarith
                                                              
                                                              -- Descending into binders (here: ⨆).
                                                              example {f g : ℕ → ℝ≥0∞} (h : ∀ n, f n ≤ g n) : ⨆ n, f n ≤ ⨆ n, g n := by
                                                                gcongr with i
                                                                exact h i
                                                              
                                                              Equations
                                                              • One or more equations did not get rendered due to their size.
                                                              Instances For

                                                                rel [h₁, ..., hₙ] uses "generalized congruence" rules to solve a goal of form ⊢ R (f a₁ ... aₙ) (f b₁ ... bₙ) by substituting with the terms hᵢ : Rᵢ aᵢ bᵢ. The relations R, Rᵢ can be any two-argument relation, including · → ·.

                                                                This tactic is extensible: to add a "generalized congruence" rule, tag a theorem with the attribute @[gcongr].

                                                                If a "generalized congruence" lemma has a side goal, rel will try to discharge it using gcongr_discharger, which is an extensible tactic based on positivity. If side goals cannot be discharged, or the terms h₁, ..., hₙ cannot solve the goals, the tactic fails.

                                                                Examples:

                                                                example {a b x c d : ℝ} (h1 : a ≤ b) (h2 : c ≤ d) :
                                                                    x ^ 2 * a + c ≤ x ^ 2 * b + d := by
                                                                  rel [h1, h2]
                                                                -- In this example we "substitute" the hypotheses `a ≤ b` and `c ≤ d` into the LHS `x ^ 2 * a + c`
                                                                -- of the goal and obtain the RHS `x ^ 2 * b + d`, thus proving the goal.
                                                                -- This constructs the proof term:
                                                                --   add_le_add (mul_le_mul_of_nonneg_left h1 (pow_bit0_nonneg x 1)) h2
                                                                -- using the generalized congruence lemmas `add_le_add` and `mul_le_mul_of_nonneg_left`.
                                                                
                                                                Equations
                                                                • One or more equations did not get rendered due to their size.
                                                                Instances For