lean4-htt/tests/elab/sym_arith_classify.lean
Leonardo de Moura 0cd6dbaad2
feat: add Sym.Arith infrastructure for arithmetic normalization (#13289)
This PR adds the shared infrastructure for arithmetic normalization in
`Sym.Arith/`,
laying the groundwork for both `Sym.simp`'s arith pre-simproc and the
eventual
unification of grind's `CommRing` module.

The key components:

- **`Types.lean`**: Classification structures (`Semiring`, `Ring`,
`CommRing`,
`CommSemiring`) stored in a `SymExtension`. These are the
classification-only
subset of grind's ring types — no solver state. Includes
`withExpThreshold` for
  controlling exponent evaluation limits.

- **`EvalNum.lean`**: `evalNat?`/`evalInt?` for evaluating ground
Nat/Int
expressions in type classes (e.g., `IsCharP`), adapted from grind to
`SymM`.

- **`Classify.lean`**: Algebraic structure detection (CommRing > Ring >
CommSemiring > Semiring) with a single `typeClassify : PHashMap ExprPtr
ClassifyResult` cache.
  Detects `IsCharP`, `NoNatZeroDivisors`, and `Field` instances.

- **Type classes**: `MonadCanon`, `MonadRing`/`MonadCommRing`,
`MonadSemiring`/`MonadCommSemiring`, `MonadGetVar`/`MonadMkVar` —
abstract over
the monad so the same code works in both `SymM` and grind's
`RingM`/`SemiringM`.
Grind's `MonadCanon` is deleted; grind's monads inherit it from `SymM`
via
  `MonadLift`.

- **`Functions.lean`**: Cached function getters (`getAddFn`, `getMulFn`,
etc.)
generic over the type classes. Synthesizes instances, validates via
`isDefEqI`,
  canonicalizes via `canonExpr`.

- **`Reify.lean`**: Converts Lean expressions into
`RingExpr`/`SemiringExpr` for
reflection-based normalization. Variable creation abstracted via
`MonadMkVar`.

- **`DenoteExpr.lean`**: Converts reified expressions back to Lean
`Expr`s.
Roundtrip tests confirm reify→denote produces definitionally equal
results.

- **`ToExpr.lean`**, **`VarRename.lean`**, **`Poly.lean`**: Moved from
`Grind.Arith.CommRing/` — pure utilities on `Grind.CommRing` types with
no
  solver dependencies.

- **Tests**: Unit tests for classification (`Int` → commRing, `Nat` →
commSemiring,
`Rat` → commRing), `evalNat?`/`evalInt?`, exp threshold, and
reify-denote roundtrips.
  
  
**TODO**: use abstractions to implement `grind` ring module, and delete
code duplication.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 05:21:09 +00:00

138 lines
3.6 KiB
Text

import Lean
/-!
# Tests for `Sym.Arith.Classify`, `Sym.Arith.EvalNum`, and `Sym.Arith.Functions`
-/
open Lean Meta Sym Arith
/-- Extract the value of a definition by name. -/
def getDefValue (n : Name) : MetaM Expr := do
let some (.defnInfo info) := (← getEnv).find? n
| throwError "expected definition: {n}"
return info.value
/-! ## Classification tests -/
deriving instance Repr for ClassifyResult
/-- info: Lean.Meta.Sym.Arith.ClassifyResult.commRing 0 -/
#guard_msgs in
run_meta SymM.run do
logInfo m!"{repr (← classify? (mkConst ``Int))}"
/-- info: Lean.Meta.Sym.Arith.ClassifyResult.commSemiring 0 -/
#guard_msgs in
run_meta SymM.run do
logInfo m!"{repr (← classify? (mkConst ``Nat))}"
/-- info: Lean.Meta.Sym.Arith.ClassifyResult.none -/
#guard_msgs in
run_meta SymM.run do
logInfo m!"{repr (← classify? (mkConst ``Bool))}"
-- Classifying the same type twice should return cached result with same id
/-- info: true -/
#guard_msgs in
run_meta SymM.run do
let .commRing id1 ← classify? (mkConst ``Int) | unreachable!
let .commRing id2 ← classify? (mkConst ``Int) | unreachable!
logInfo m!"{id1 == id2}"
/--
info: Lean.Meta.Sym.Arith.ClassifyResult.commRing 0
---
info: Lean.Meta.Sym.Arith.ClassifyResult.commSemiring 0
---
info: Lean.Meta.Sym.Arith.ClassifyResult.commRing 2
---
info: Lean.Meta.Sym.Arith.ClassifyResult.commRing 1
-/
#guard_msgs in
run_meta SymM.run do
let int ← shareCommon (mkConst ``Int)
let nat ← shareCommon (mkConst ``Nat)
let rat ← shareCommon (mkConst ``Rat)
logInfo m!"{repr (← classify? int)}"
logInfo m!"{repr (← classify? nat)}"
logInfo m!"{repr (← classify? rat)}"
let inst ← Sym.synthInstance (mkApp (mkConst ``Grind.Semiring [0]) nat)
let ofSemiring ← shareCommon (← Sym.canon <| mkApp2 (mkConst ``Grind.Ring.OfSemiring.Q [0]) nat inst)
logInfo m!"{repr (← classify? ofSemiring)}"
/-! ## EvalNum tests -/
def natZero : Nat := 0
def natSucc3 : Nat := Nat.succ (Nat.succ (Nat.succ 0))
def natSeven : Nat := 7
def natAdd : Nat := 2 + 3
def natMul : Nat := 2 * 3
def natPow : Nat := 2 ^ 3
def natBigPow : Nat := 2 ^ 100
def natPow10 : Nat := 2 ^ 10
/-- info: some (0) -/
#guard_msgs in
run_meta SymM.run do
logInfo m!"{← evalNat? (← getDefValue ``natZero)}"
/-- info: some (3) -/
#guard_msgs in
run_meta SymM.run do
logInfo m!"{← evalNat? (← getDefValue ``natSucc3)}"
/-- info: some (7) -/
#guard_msgs in
run_meta SymM.run do
logInfo m!"{← evalNat? (← getDefValue ``natSeven)}"
/-- info: some (5) -/
#guard_msgs in
run_meta SymM.run do
logInfo m!"{← evalNat? (← getDefValue ``natAdd)}"
/-- info: some (6) -/
#guard_msgs in
run_meta SymM.run do
logInfo m!"{← evalNat? (← getDefValue ``natMul)}"
/-- info: some (8) -/
#guard_msgs in
run_meta SymM.run do
logInfo m!"{← evalNat? (← getDefValue ``natPow)}"
/-! ## Exp threshold tests -/
-- 2 ^ 100 should fail with default exp threshold (8)
/-- info: none -/
#guard_msgs in
run_meta SymM.run do
logInfo m!"{← evalNat? (← getDefValue ``natBigPow)}"
-- 2 ^ 10 succeeds with exp threshold raised to 20
/-- info: some (1024) -/
#guard_msgs in
run_meta SymM.run do
withExpThreshold 20 do
logInfo m!"{← evalNat? (← getDefValue ``natPow10)}"
/-! ## Int EvalNum tests -/
def intNeg : Int := -5
def intAdd : Int := 3 + (-2)
def intMul : Int := (-3) * 4
/-- info: some (-5) -/
#guard_msgs in
run_meta SymM.run do
logInfo m!"{← evalInt? (← getDefValue ``intNeg)}"
/-- info: some (1) -/
#guard_msgs in
run_meta SymM.run do
logInfo m!"{← evalInt? (← getDefValue ``intAdd)}"
/-- info: some (-12) -/
#guard_msgs in
run_meta SymM.run do
logInfo m!"{← evalInt? (← getDefValue ``intMul)}"