test: grind homomorphism demo (#13497)

This PR adds an example for the Lean hackathon in Paris. It demonstrates
how users can implement https://hackmd.io/Qd0nkWdzQImVe7TDGSAGbA
This commit is contained in:
Leonardo de Moura 2026-04-21 23:17:32 +02:00 committed by GitHub
parent f32106283f
commit e542810e79
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 129 additions and 0 deletions

25
tests/pkg/homo/Homo.lean Normal file
View file

@ -0,0 +1,25 @@
import Homo.Init
set_option warn.sorry false
opaque TSpec : (α : Type) × α := ⟨Unit, ()⟩
abbrev T : Type := TSpec.1
instance : Inhabited T := ⟨TSpec.2⟩
opaque add : T → T → T
opaque le : T → T → Prop
opaque pos : T → Prop
opaque small : T → Prop
opaque f : Nat → T
opaque toInt : T → Int
@[grind_homo] theorem T.eq (a b : T) : a = b ↔ toInt a = toInt b := sorry
@[grind_homo] theorem T.le (a b : T) : le a b ↔ toInt a ≤ toInt b := sorry
@[grind_homo] theorem T.pos (a : T) : pos a ↔ toInt a > 0 := sorry
@[grind_homo] theorem T.small (a : T) : small a ↔ toInt a < 8 := sorry
@[grind_homo] theorem T.add (a b : T) : toInt (add a b) = (toInt a + toInt b) % 128 := sorry
@[grind_homo] theorem cleanLeft (a b n : Int) : (a % n + b) % n = (a + b) % n := by simp
@[grind_homo] theorem cleanRight (a b n : Int) : (a + b % n) % n = (a + b) % n := by simp
set_option trace.homo true
example (b : T) : pos b → small b → le b (add b b) := by
grind

View file

@ -0,0 +1,96 @@
module
import Lean.Meta.Tactic.Grind.Types
import Lean.Meta.Sym.Simp.Attr
import Lean.Meta.Sym.Simp.Simproc
import Lean.Meta.Sym.Simp.Rewrite
import Lean.Meta.AppBuilder
namespace Homomorphism
open Lean Meta Grind Sym Simp
initialize registerTraceClass `homo
initialize registerTraceClass `homo.visit
/--
Declares attribute `[grind_mono]` for marking theorems implementing the homomorphism.
-/
initialize homoSimpExtension : SymSimpExtension ←
registerSymSimpAttr `grind_homo "`grind` homomorphism attribute"
/--
Returns theorems marked with `[grind_mono]`
-/
def getTheorems : CoreM Theorems :=
homoSimpExtension.getTheorems
/--
Creates a simproc that applies the theorems marked with `[grind_mono]`.
This simproc is meant to be applied as a `pre` method.
Recall that `grind` internalizes terms bottom-up. By the time a
simplification set runs on a term `e`, all subterms of `e` are already
in the E-graph and have been processed by the pipeline.
**Stop condition.** When simp encounters a term `t` during traversal:
- If a rule matches `t`: apply it, continue (result is a new term).
- If no rule matches `t` AND `t` is already in the E-graph:
stop, don't descend. Otherwise: descend normally.
-/
def mkRewriter : GoalM Sym.Simp.Simproc := do
let s ← get
-- Remark: We are not using any discharger. So, our rewriting rules are all context
-- independent.
let rw := (← getTheorems).rewrite
return fun e => do
trace[homo.visit] "{e}"
let r ← rw e
if !r.isRfl then return r
-- If `e` is already in the E-graph, we don't revisit its children
let done := s.enodeMap.contains { expr := e }
return .rfl (done := done)
structure State where
cache : Sym.Simp.Cache := {}
initialize homoExt : SolverExtension Sym.Simp.Cache ←
registerSolverExtension (return {})
/-- Apply the homomorphism theorems. -/
def applyHomo (e : Expr) : GoalM Sym.Simp.Result := do
let methods := { pre := (← mkRewriter) }
-- Reuse cache.
let persistentCache ← homoExt.getState
homoExt.modifyState fun _ => {} -- Improve uniqueness. This is a minor optimization
let (r, s) ← Sym.Simp.SimpM.run (Sym.Simp.simp e) (methods := methods) (s := { persistentCache })
homoExt.modifyState fun _ => s.persistentCache
return r
/--
Returns `true` if some theorem marked with `[grind_homo]` is applicable to `e`.
Motivation: we don't want to start the simplifier and fail immediately.
-/
def isTarget (e : Expr) : CoreM Bool := do
let thms ← getTheorems
return !(thms.getMatch e).isEmpty
/--
Internalization procedure for this module. See `homoExt.setMethods`
-/
def internalize (e : Expr) (_ : Option Expr) : GoalM Unit := do
unless (← isTarget e) do return ()
let .step e₁ h₁ _ ← applyHomo e | return ()
let r ← preprocess e₁
let h ← mkEqTrans h₁ (← r.getProof)
let gen ← getGeneration e
Grind.internalize r.expr gen
trace[homo] "{e}\n====>\n{r.expr}"
pushEq e r.expr h
initialize
homoExt.setMethods
(internalize := internalize)
end Homomorphism

View file

@ -0,0 +1,5 @@
import Lake
open System Lake DSL
package homo
@[default_target] lean_lib Homo

View file

@ -0,0 +1 @@
../../../build/release/stage1

View file

@ -0,0 +1,2 @@
rm -rf .lake/build
lake build