File: Mathlib/Tactic/ComputeDegree.lean

full_name: Mathlib.Tactic.ComputeDegree.natDegree_C_le
code: theorem natDegree_C_le (a : R) : natDegree (C a) ≤ 0 
goal: (Polynomial.C a).natDegree ≤ 0

full_name: Mathlib.Tactic.ComputeDegree.natDegree_natCast_le
code: theorem natDegree_natCast_le (n : ℕ) : natDegree (n : R[X]) ≤ 0 
goal: (↑n).natDegree ≤ 0

full_name: Mathlib.Tactic.ComputeDegree.natDegree_zero_le
code: theorem natDegree_zero_le : natDegree (0 : R[X]) ≤ 0 
goal: Polynomial.natDegree 0 ≤ 0

full_name: Mathlib.Tactic.ComputeDegree.natDegree_one_le
code: theorem natDegree_one_le : natDegree (1 : R[X]) ≤ 0 
goal: Polynomial.natDegree 1 ≤ 0

full_name: Mathlib.Tactic.ComputeDegree.coeff_add_of_eq
code: theorem coeff_add_of_eq {n : ℕ} {a b : R} {f g : R[X]}
    (h_add_left : f.coeff n = a) (h_add_right : g.coeff n = b) :
    (f + g).coeff n = a + b 
goal: (f + g).coeff n = a + b

full_name: Mathlib.Tactic.ComputeDegree.coeff_mul_add_of_le_natDegree_of_eq_ite
code: theorem coeff_mul_add_of_le_natDegree_of_eq_ite {d df dg : ℕ} {a b : R} {f g : R[X]}
    (h_mul_left : natDegree f ≤ df) (h_mul_right : natDegree g ≤ dg)
    (h_mul_left : f.coeff df = a) (h_mul_right : g.coeff dg = b) (ddf : df + dg ≤ d) :
    (f * g).coeff d = if d = df + dg then a * b else 0 
goal: f.coeff df = a → g.coeff dg = b → df + dg ≤ d → (f * g).coeff d = if d = df + dg then a * b else 0

full_name: Mathlib.Tactic.ComputeDegree.coeff_pow_of_natDegree_le_of_eq_ite'
code: theorem coeff_pow_of_natDegree_le_of_eq_ite' {m n o : ℕ} {a : R} {p : R[X]}
    (h_pow : natDegree p ≤ n) (h_exp : m * n ≤ o) (h_pow_bas : coeff p n = a) :
    coeff (p ^ m) o = if o = m * n then a ^ m else 0 
goal: (p ^ m).coeff o = if o = m * n then a ^ m else 0

full_name: Mathlib.Tactic.ComputeDegree.natDegree_smul_le_of_le
code: theorem natDegree_smul_le_of_le {n : ℕ} {a : R} {f : R[X]} (hf : natDegree f ≤ n) :
    natDegree (a • f) ≤ n 
goal: (a • f).natDegree ≤ n

full_name: Mathlib.Tactic.ComputeDegree.degree_smul_le_of_le
code: theorem degree_smul_le_of_le {n : ℕ} {a : R} {f : R[X]} (hf : degree f ≤ n) :
    degree (a • f) ≤ n 
goal: (a • f).degree ≤ ↑n

full_name: Mathlib.Tactic.ComputeDegree.coeff_smul
code: theorem coeff_smul {n : ℕ} {a : R} {f : R[X]} : (a • f).coeff n = a * f.coeff n 
goal: (a • f).coeff n = a * f.coeff n

full_name: Mathlib.Tactic.ComputeDegree.natDegree_eq_of_le_of_coeff_ne_zero'
code: /--  The following two lemmas should be viewed as a hand-made "congr"-lemmas.
They achieve the following goals.
* They introduce *two* fresh metavariables replacing the given one `deg`,
  one for the `natDegree ≤` computation and one for the `coeff =` computation.
  This helps `compute_degree`, since it does not "pre-estimate" the degree,
  but it "picks it up along the way".
* They split checking the inequality `coeff p n ≠ 0` into the task of
  finding a value `c` for the `coeff` and then
  proving that this value is non-zero by `coeff_ne_zero`.
-/
theorem natDegree_eq_of_le_of_coeff_ne_zero' {deg m o : ℕ} {c : R} {p : R[X]}
    (h_natDeg_le : natDegree p ≤ m) (coeff_eq : coeff p o = c)
    (coeff_ne_zero : c ≠ 0) (deg_eq_deg : m = deg) (coeff_eq_deg : o = deg) :
    natDegree p = deg 
goal: p.natDegree = deg

full_name: Mathlib.Tactic.ComputeDegree.degree_eq_of_le_of_coeff_ne_zero'
code: theorem degree_eq_of_le_of_coeff_ne_zero' {deg m o : WithBot ℕ} {c : R} {p : R[X]}
    (h_deg_le : degree p ≤ m) (coeff_eq : coeff p (WithBot.unbot' 0 deg) = c)
    (coeff_ne_zero : c ≠ 0) (deg_eq_deg : m = deg) (coeff_eq_deg : o = deg) :
    degree p = deg 
goal: p.degree = deg

full_name: Mathlib.Tactic.ComputeDegree.coeff_congr_lhs
code: theorem coeff_congr_lhs : coeff f n = r 
goal: f.coeff n = r

full_name: Mathlib.Tactic.ComputeDegree.coeff_congr
code: theorem coeff_congr {s : R} (rs : r = s) : coeff f n = s 
goal: f.coeff n = s

full_name: Mathlib.Tactic.ComputeDegree.natDegree_intCast_le
code: theorem natDegree_intCast_le (n : ℤ) : natDegree (n : R[X]) ≤ 0 
goal: (↑n).natDegree ≤ 0

full_name: Mathlib.Tactic.ComputeDegree.coeff_sub_of_eq
code: theorem coeff_sub_of_eq {n : ℕ} {a b : R} {f g : R[X]} (hf : f.coeff n = a) (hg : g.coeff n = b) :
    (f - g).coeff n = a - b 
goal: (f - g).coeff n = a - b

full_name: Mathlib.Tactic.ComputeDegree.coeff_intCast_ite
code: theorem coeff_intCast_ite {n : ℕ} {a : ℤ} : (Int.cast a : R[X]).coeff n = ite (n = 0) a 0 
goal: (↑a).coeff n = ↑(if n = 0 then a else 0)

full_name: Mathlib.Tactic.ComputeDegree.twoHeadsArgs
code: /-- `twoHeadsArgs e` takes an `Expr`ession `e` as input and recurses into `e` to make sure
the `e` looks like `lhs ≤ rhs` or `lhs = rhs` and that `lhs` is one of
`natDegree f, degree f, coeff f d`.
It returns
* the function being applied on the LHS (`natDegree`, `degree`, or `coeff`),
  or else `.anonymous` if it's none of these.
* the name of the relation (`Eq` or `LE.le`), or else `.anonymous` if it's none of these.
* either
  * `.inl zero`, `.inl one`, or `.inl many` if the polynomial in a numeral
  * or `.inr` of the head symbol of `f`
  * or `.inl .anonymous` if inapplicable
* if it exists, whether the `rhs` is a metavariable
* if the LHS is `coeff f d`, whether `d` is a metavariable

This is all the data needed to figure out whether `compute_degree` can make progress on `e`
and, if so, which lemma it should apply.

Sample outputs:
* `natDegree (f + g) ≤ d => (natDegree, LE.le, HAdd.hAdd, d.isMVar, none)` (similarly for `=`);
* `degree (f * g) = d => (degree, Eq, HMul.hMul, d.isMVar, none)` (similarly for `≤`);
* `coeff (1 : ℕ[X]) c = x => (coeff, Eq, one, x.isMVar, c.isMVar)` (no `≤` option!).
-/
def twoHeadsArgs (e : Expr) : Name × Name × (Name ⊕ Name) × List Bool 
goal: Lean.Name × Lean.Name × (Lean.Name ⊕ Lean.Name) × List Bool

full_name: Mathlib.Tactic.ComputeDegree.getCongrLemma
code: /--
`getCongrLemma (lhs_name, rel_name, Mvars?)` returns the name of a lemma that preprocesses
one of the five target
*  `natDegree f ≤ d`;
*  `natDegree f = d`.
*  `degree f ≤ d`;
*  `degree f = d`.
*  `coeff f d = r`.

The end goals are of the form
* `natDegree f ≤ ?_`, `degree f ≤ ?_`, `coeff f ?_ = ?_`, with fresh metavariables;
* `coeff f m ≠ s` with `m, s` not necessarily metavariables;
* several equalities/inequalities between expressions and assignments for metavariables.

`getCongrLemma` gets called at the very beginning of `compute_degree` and whenever an intermediate
goal does not have the right metavariables.
Note that the side-goals of the congruence lemma are neither of the form `natDegree f = d` nor
of the form `degree f = d`.

`getCongrLemma` admits an optional "debug" flag: `getCongrLemma data true` prints the name of
the congruence lemma that it returns.
-/
def getCongrLemma (twoH : Name × Name × List Bool) (debug : Bool := false) : Name 
goal: Lean.Name

full_name: Mathlib.Tactic.ComputeDegree.dispatchLemma
code: /--
`dispatchLemma twoH` takes its input `twoH` from the output of `twoHeadsArgs`.

Using the information contained in `twoH`, it decides which lemma is the most appropriate.

`dispatchLemma` is essentially the main dictionary for `compute_degree`.
-/
--  Internally, `dispatchLemma` produces 3 names: these are the lemmas that are appropriate
--  for goals of the form `natDegree f ≤ d`, `degree f ≤ d`, `coeff f d = a`, in this order.
def dispatchLemma
    (twoH : Name × Name × (Name ⊕ Name) × List Bool) (debug : Bool := false) : Name 
goal: Lean.Name

full_name: Mathlib.Tactic.ComputeDegree.try_rfl
code: /-- `try_rfl mvs` takes as input a list of `MVarId`s, scans them partitioning them into two
lists: the goals containing some metavariables and the goals not containing any metavariable.

If a goal containing a metavariable has the form `?_ = x`, `x = ?_`, where `?_` is a metavariable
and `x` is an expression that does not involve metavariables, then it closes this goal using `rfl`,
effectively assigning the metavariable to `x`.

If a goal does not contain metavariables, it tries `rfl` on it.

It returns the list of `MVarId`s, beginning with the ones that initially involved (`Expr`)
metavariables followed by the rest.
-/
def try_rfl (mvs : List MVarId) : MetaM (List MVarId) 
goal: Lean.MetaM (List Lean.MVarId)

full_name: Mathlib.Tactic.ComputeDegree.splitApply
code: /--
`splitApply mvs static` takes two lists of `MVarId`s.  The first list, `mvs`,
corresponds to goals that are potentially within the scope of `compute_degree`:
namely, goals of the form
`natDegree f ≤ d`, `degree f ≤ d`, `natDegree f = d`, `degree f = d`, `coeff f d = r`.

`splitApply` determines which of these goals are actually within the scope, it applies the relevant
lemma and returns two lists: the left-over goals of all the applications, followed by the
concatenation of the previous `static` list, followed by the newly discovered goals outside of the
scope of `compute_degree`. -/
def splitApply (mvs static : List MVarId) : MetaM ((List MVarId) × (List MVarId)) 
goal: Lean.MetaM (List Lean.MVarId × List Lean.MVarId)

full_name: Mathlib.Tactic.ComputeDegree.miscomputedDegree?
code: /-- `miscomputedDegree? deg false_goals` takes as input
*  an `Expr`ession `deg`, representing the degree of a polynomial
   (i.e. an `Expr`ession of inferred type either `ℕ` or `WithBot ℕ`);
*  a list of `MVarId`s `false_goals`.

Although inconsequential for this function, the list of goals `false_goals` reduces to `False`
if `norm_num`med.
`miscomputedDegree?` extracts error information from goals of the form
*  `a ≠ b`, assuming it comes from `⊢ coeff_of_given_degree ≠ 0`
   -- reducing to `False` means that the coefficient that was supposed to vanish, does not;
*  `a ≤ b`, assuming it comes from `⊢ degree_of_subterm ≤ degree_of_polynomial`
   -- reducing to `False` means that there is a term of degree that is apparently too large;
*  `a = b`, assuming it comes from `⊢ computed_degree ≤ given_degree`
   -- reducing to `False` means that there is a term of degree that is apparently too large.

The cases `a ≠ b` and `a = b` are not a perfect match with the top coefficient:
reducing to `False` is not exactly correlated with a coefficient being non-zero.
It does mean that `compute_degree` reduced the initial goal to an unprovable state
(unless there was already a contradiction in the initial hypotheses!), but it is indicative that
there may be some problem.
-/
def miscomputedDegree? (deg : Expr) : List Expr → List MessageData
  
goal: List Lean.Expr → List Lean.MessageData

full_name: Mathlib.Tactic.ComputeDegree.computeDegree
code: /--
`compute_degree` is a tactic to solve goals of the form
*  `natDegree f = d`,
*  `degree f = d`,
*  `natDegree f ≤ d`,
*  `degree f ≤ d`,
*  `coeff f d = r`, if `d` is the degree of `f`.

The tactic may leave goals of the form `d' = d` `d' ≤ d`, or `r ≠ 0`, where `d'` in `ℕ` or
`WithBot ℕ` is the tactic's guess of the degree, and `r` is the coefficient's guess of the
leading coefficient of `f`.

`compute_degree` applies `norm_num` to the left-hand side of all side goals, trying to clos them.

The variant `compute_degree!` first applies `compute_degree`.
Then it uses `norm_num` on all the whole remaining goals and tries `assumption`.
-/
syntax (name := computeDegree) "compute_degree" "!"? : tactic
goal: Lean.ParserDescr

full_name: Mathlib.Tactic.ComputeDegree.initFn._@.Mathlib.Tactic.ComputeDegree._hyg.3774
code: initialize registerTraceClass `Tactic.compute_degree
goal: IO Unit

full_name: Mathlib.Tactic.ComputeDegree.tacticCompute_degree!
code: macro "compute_degree!" : tactic => `(tactic| compute_degree !)
goal: Lean.ParserDescr

full_name: Mathlib.Tactic.ComputeDegree._aux_Mathlib_Tactic_ComputeDegree___macroRules_Mathlib_Tactic_ComputeDegree_tacticCompute_degree!_1
code: macro "compute_degree!" : tactic => `(tactic| compute_degree !)
goal: Lean.Macro

full_name: Mathlib.Tactic.ComputeDegree._aux_Mathlib_Tactic_ComputeDegree___elabRules_Mathlib_Tactic_ComputeDegree_computeDegree_1
code: elab_rules : tactic | `(tactic| compute_degree $[!%$bang]?) => focus <| withMainContext do
  let goal ← getMainGoal
  let gt ← goal.getType''
  let deg? := match gt.eq? with
    | some (_, _, rhs) => some rhs
    | _ => none
  let twoH := twoHeadsArgs gt
  match twoH with
    | (_, .anonymous, _) => throwError m!"'compute_degree' inapplicable. \
        The goal{indentD gt}\nis expected to be '≤' or '='."
    | (.anonymous, _, _) => throwError m!"'compute_degree' inapplicable. \
        The LHS must be an application of 'natDegree', 'degree', or 'coeff'."
    | _ =>
      let lem := dispatchLemma twoH
      trace[Tactic.compute_degree]
        f!"'compute_degree' first applies lemma '{lem.lastComponentAsString}'"
      let mut (gls, static) := (← goal.applyConst lem, [])
      while gls != [] do (gls, static) ← splitApply gls static
      let rfled ← try_rfl static
      setGoals rfled
      --  simplify the left-hand sides, since this is where the degree computations leave
      --  expressions such as `max (0 * 1) (max (1 + 0 + 3 * 4) (7 * 0))`
      evalTactic
        (← `(tactic| try any_goals conv_lhs =>
                       (simp (config := {decide := true}) only [Nat.cast_withBot]; norm_num)))
      if bang.isSome then
        let mut false_goals : Array MVarId := #[]
        let mut new_goals : Array MVarId := #[]
        for g in ← getGoals do
          let gs' ← run g do evalTactic (←
            `(tactic| try (any_goals norm_num <;> norm_cast <;> try assumption)))
          new_goals := new_goals ++ gs'.toArray
          if ← gs'.anyM fun g' => g'.withContext do return (← g'.getType'').isConstOf ``False then
            false_goals := false_goals.push g
        setGoals new_goals.toList
        if let some deg := deg? then
          let errors := miscomputedDegree? deg (← false_goals.mapM (MVarId.getType'' ·)).toList
          unless errors.isEmpty do
            throwError Lean.MessageData.joinSep
              (m!"The given degree is '{deg}'.  However,\n" :: errors) "\n"
goal: Lean.Elab.Tactic.Tactic

full_name: Mathlib.Tactic.ComputeDegree.monicityMacro
code: macro (name := monicityMacro) "monicity" : tactic =>
  `(tactic| (apply monic_of_natDegree_le_of_coeff_eq_one <;> compute_degree))
goal: Lean.ParserDescr

full_name: Mathlib.Tactic.ComputeDegree._aux_Mathlib_Tactic_ComputeDegree___macroRules_Mathlib_Tactic_ComputeDegree_monicityMacro_1
code: macro (name := monicityMacro) "monicity" : tactic =>
  `(tactic| (apply monic_of_natDegree_le_of_coeff_eq_one <;> compute_degree))
goal: Lean.Macro

full_name: Mathlib.Tactic.ComputeDegree.tacticMonicity!
code: macro "monicity!" : tactic =>
  `(tactic| (apply monic_of_natDegree_le_of_coeff_eq_one <;> compute_degree!))
goal: Lean.ParserDescr

full_name: Mathlib.Tactic.ComputeDegree._aux_Mathlib_Tactic_ComputeDegree___macroRules_Mathlib_Tactic_ComputeDegree_tacticMonicity!_1
code: macro "monicity!" : tactic =>
  `(tactic| (apply monic_of_natDegree_le_of_coeff_eq_one <;> compute_degree!))
goal: Lean.Macro