lean4-htt/src/Init/Grind/Config.lean
Leonardo de Moura b4c4f265aa
fix: grind ring solver OOM on high-degree polynomials (#13032)
This PR fixes #12842 where `grind` exhausts memory on goals involving
high-degree polynomials such as `(x + y)^2 = x^128 + y^2` over `Fin 2`.

The root cause is that `incSteps` in the ring module's Groebner basis
engine increments the step counter by 1 per simplification, regardless
of polynomial size. For high-degree polynomials (e.g., degree 128),
intermediate results can have hundreds of terms, making each operation
extremely expensive — but the flat counter cannot catch this before
memory is exhausted.

The fix weights each step by `Poly.numTerms` of the result polynomial
and increases the default `ringSteps` from 10 000 to 100 000 to
accommodate the new cost model.

Note: the example from #12842 will not be *proved* by `grind` even after
this fix, because Frobenius / Fermat's little theorem (`x^p = x` in `Fin
p`) is not available in core Lean. The long-term plan is to introduce a
type class in core stating this property, with an instance provided by
Mathlib, so that `grind` can exploit it when Mathlib is imported.

Closes  #12842

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 02:52:44 +00:00

298 lines
12 KiB
Text

/-
Copyright (c) 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
module
prelude
public import Init.Core
public section
namespace Lean.Grind
/--
The configuration for `grind`.
Passed to `grind` using, for example, the `grind (config := { matchEqs := true })` syntax.
-/
structure Config where
/-- If `trace` is `true`, `grind` records used E-matching theorems and case-splits. -/
trace : Bool := false
/-- If `markInstances` is `true`, E-matching proofs are marked with instance IDs
for precise tracking of which theorems appear in the final proof. -/
markInstances : Bool := false
/-- If `lax` is `true`, `grind` will silently ignore any parameters referring to non-existent theorems
or for which no patterns can be generated. -/
lax : Bool := false
/-- If `suggestions` is `true`, `grind` will invoke the currently configured library suggestion engine on the current goal,
and add attempt to use the resulting suggestions as additional parameters to the `grind` tactic. -/
suggestions : Bool := false
/-- If `locals` is `true`, `grind` will add all definitions from the current file. -/
locals : Bool := false
/-- Maximum number of case-splits in a proof search branch. It does not include splits performed during normalization. -/
splits : Nat := 9
/-- Maximum number of E-matching (aka heuristic theorem instantiation) rounds before each case split. -/
ematch : Nat := 5
/--
Maximum term generation.
The input goal terms have generation 0. When we instantiate a theorem using a term from generation `n`,
the new terms have generation `n+1`. Thus, this parameter limits the length of an instantiation chain. -/
gen : Nat := 8
/-- Maximum number of theorem instances generated using E-matching in a proof search tree branch. -/
instances : Nat := 1000
/-- If `matchEqs` is `true`, `grind` uses `match`-equations as E-matching theorems. -/
matchEqs : Bool := true
/-- If `splitMatch` is `true`, `grind` performs case-splitting on `match`-expressions during the search. -/
splitMatch : Bool := true
/-- If `splitIte` is `true`, `grind` performs case-splitting on `if-then-else` expressions during the search. -/
splitIte : Bool := true
/--
If `splitIndPred` is `true`, `grind` performs case-splitting on inductive predicates.
Otherwise, it performs case-splitting only on types marked with `[grind cases]` attribute. -/
splitIndPred : Bool := false
/--
If `splitImp` is `true`, then given an implication `p → q` or `(h : p) → q h`, `grind` splits on `p`
if the implication is true. Otherwise, it will split only if `p` is an arithmetic predicate.
-/
splitImp : Bool := false
/-- Maximum number of heartbeats (in thousands) the canonicalizer can spend per definitional equality test. -/
canonHeartbeats : Nat := 1000
/-- If `ext` is `true`, `grind` uses extensionality theorems that have been marked with `[grind ext]`. -/
ext : Bool := true
/-- If `extAll` is `true`, `grind` uses any extensionality theorems available in the environment. -/
extAll : Bool := false
/--
If `etaStruct` is `true`, then for each term `t : S` such that `S` is a structure,
and is tagged with `[grind ext]`, `grind` adds the equation `t = ⟨t.1, ..., t.n⟩`
which holds by reflexivity. Moreover, the extensionality theorem for `S` is not used.
-/
etaStruct : Bool := true
/--
If `funext` is `true`, `grind` creates new opportunities for applying function extensionality by case-splitting
on equalities between lambda expressions.
-/
funext : Bool := true
/-- TODO -/
lookahead : Bool := true
/-- If `verbose` is `false`, additional diagnostics information is not collected. -/
verbose : Bool := true
/-- If `clean` is `true`, `grind` uses `expose_names` and only generates accessible names. -/
clean : Bool := true
/--
If `qlia` is `true`, `grind` may generate counterexamples for integer constraints
using rational numbers, and ignoring divisibility constraints.
This approach is cheaper but incomplete. -/
qlia : Bool := false
/--
If `mbtc` is `true`, `grind` will use model-based theory combination for creating new case splits.
See paper "Model-based Theory Combination" for details.
-/
mbtc : Bool := true
/--
When set to `true` (default: `true`), local definitions are unfolded during normalization and internalization.
In other words, given a local context with an entry `x : t := e`, the free variable `x` is reduced to `e`.
Note that this behavior is also available in `simp`, but there its default is `false` because `simp` is not
always used as a terminal tactic, and it important to preserve the abstractions introduced by users.
Additionally, in `grind` we observed that `zetaDelta` is particularly important when combined with function induction.
In such scenarios, the same let-expressions can be introduced by function induction and also by unfolding the
corresponding definition. We want to avoid a situation in which `zetaDelta` is not applied to let-declarations
introduced by function induction while `zeta` unfolds the definition, causing a mismatch.
Finally, note that congruence closure is less effective on terms containing many binders such as
`lambda` and `let` expressions.
-/
zetaDelta := true
/--
When `true` (default: `true`), performs zeta reduction of let expressions during normalization.
That is, `let x := v; e[x]` reduces to `e[v]`. See also `zetaDelta`.
-/
zeta := true
/--
When `true` (default: `true`), uses procedure for handling equalities over commutative rings.
This solver also support commutative semirings, fields, and normalizer non-commutative rings and
semirings.
-/
ring := true
/--
Maximum number of steps performed by the `ring` solver.
A step is counted whenever one polynomial is used to simplify another,
weighted by the number of terms in the resulting polynomial.
-/
ringSteps := 100000
/--
When `true` (default: `true`), uses procedure for handling linear arithmetic for `IntModule`, and
`CommRing`.
-/
linarith := true
/--
When `true` (default: `true`), uses procedure for handling linear integer arithmetic for `Int` and `Nat`.
-/
lia := true
/--
When `true` (default: `true`), uses procedure for handling associative (and commutative) operators.
-/
ac := true
/--
Maximum number of steps performed by the `ac` solver.
A step is counted whenever one AC equation is used to simplify another.
For example, given `ma x z = w` and `max x (max y z) = x`, the first can be
used to simplify the second to `max w y = x`.
-/
acSteps := 1000
/--
Maximum exponent eagerly evaluated while computing bounds for `ToInt` and
the characteristic of a ring.
-/
exp : Nat := 2^20
/--
When `true` (default: `true`), automatically creates an auxiliary theorem to store the proof.
-/
abstractProof := true
/--
When `true` (default: `true`), enables the procedure for handling injective functions.
In this mode, `grind` takes into account theorems such as:
```
@[grind inj] theorem double_inj : Function.Injective double
```
-/
inj := true
/--
When `true` (default: `true`), enables the procedure for handling orders that implement
at least `Std.IsPreorder`
-/
order := true
/--
Minimum number of instantiations to trigger summary report in `#grind_lint`.
Remark: this option is only relevant for the `#grind_lint` command.
-/
min : Nat := 10
/--
Minimum number of instantiations to trigger detailed report in `#grind_lint`.
Remark: this option is only relevant for the `#grind_lint` command.
-/
detailed : Nat := 50
/--
When `trace := true`, uses `sorry` to close unsolved branches.
-/
useSorry := true
/--
When `true`, it reverts all hypotheses during the preprocessing step,
and then reintroduces them while simplifying and applying eager `cases`.
-/
revert := false
/--
When `true`, it enables **function-valued congruence closure**.
`grind` treats equalities of partially applied functions as first-class equalities
and propagates them through further applications.
Given an application `f a₁ a₂ … aₙ`, when `funCC := true` *and* function equality is enabled for `f`,
`grind` generates and tracks equalities for all partial applications:
- `f a₁`
- `f a₁ a₂`
- `…`
- `f a₁ a₂ … aₙ`
This allows equalities such as `f a₁ = g` to propagate to
`f a₁ a₂ = g a₂`.
**When is function equality enabled for a symbol?**
Function equality is automatically enabled in the following cases:
1. **`f` is not a constant.** (For example, a lambda expression, a local variable, or a function parameter.)
2. **`f` is a structure field projection**, *provided the structure is not a `class`.*
3. **`f` is a constant marked with the attribute:** `@[grind funCC]`
If none of the above conditions apply, function equality is disabled for `f`, and congruence
closure behaves almost like it does in SMT solvers for first-order logic.
Here is an example, `grind` can solve when `funCC := true`
```
example (a b : Nat) (g : Nat → Nat) (f : Nat → Nat → Nat) (h : f a = g) :
f a b = g b := by
grind
```
-/
funCC := true
/--
When `true`, use reducible transparency when trying to close goals.
In this setting, only declarations marked with `@[reducible]` are unfolded during
definitional equality tests.
This option does not affect the canonicalizer or how theorem patterns are internalized;
reducible declarations are always unfolded in those contexts.
-/
reducible := true
/--
Maximum number of library suggestions to use. If `none`, uses the default limit.
Only relevant when `suggestions` is `true`.
-/
maxSuggestions : Option Nat := none
deriving Inhabited, BEq
/--
Configuration for interactive mode.
We disable `clean := false`.
-/
structure ConfigInteractive extends Config where
clean := false
/--
A minimal configuration, with ematching and splitting disabled, and all solver modules turned off.
`grind` will not do anything in this configuration,
which can be used a starting point for minimal configurations.
-/
-- This is a `structure` rather than `def` so we can use `declare_config_elab`.
structure NoopConfig extends Config where
-- Disable splitting
splits := 0
-- We don't override the various `splitMatch` / `splitIte` settings separately.
-- Disable e-matching
ematch := 0
-- We don't override `matchEqs` separately.
-- Disable extensionality
ext := false
extAll := false
etaStruct := false
funext := false
funCC := false
-- Disable all solver modules
ring := false
linarith := false
lia := false
ac := false
order := false
/--
A `grind` configuration that only uses `cutsat` and splitting.
Note: `cutsat` benefits from some amount of instantiation, e.g. `Nat.max_def`.
We don't currently have a mechanism to enable only a small set of lemmas.
-/
-- This is a `structure` rather than `def` so we can use `declare_config_elab`.
structure CutsatConfig extends NoopConfig where
lia := true
-- Allow the default number of splits.
splits := ({} : Config).splits
/--
A `grind` configuration that only uses `linarith`.
-/
-- This is a `structure` rather than `def` so we can use `declare_config_elab`.
structure LinarithConfig extends NoopConfig where
linarith := true
-- Allow the default number of splits.
splits := ({} : Config).splits
/--
A `grind` configuration that only uses `order`.
-/
-- This is a `structure` rather than `def` so we can use `declare_config_elab`.
structure OrderConfig extends NoopConfig where
order := true
-- Allow the default number of splits.
splits := ({} : Config).splits
/--
A `grind` configuration that only uses `ring`.
-/
-- This is a `structure` rather than `def` so we can use `declare_config_elab`.
structure GrobnerConfig extends NoopConfig where
ring := true
end Lean.Grind