From efc4407d7ab906cd476d4e421e4b17b20193b2b3 Mon Sep 17 00:00:00 2001 From: JOSHCLUNE Date: Thu, 20 Aug 2026 13:32:40 -0400 Subject: [PATCH 1/7] Modify how runAesopWithSubProcedures sends arguments to subprocedures --- Hammer/SingleRuleTac.lean | 24 ++++++++------- Hammer/Tactic.lean | 61 +++++++++++++++++++++++---------------- 2 files changed, 50 insertions(+), 35 deletions(-) diff --git a/Hammer/SingleRuleTac.lean b/Hammer/SingleRuleTac.lean index 3610306..3fd1680 100644 --- a/Hammer/SingleRuleTac.lean +++ b/Hammer/SingleRuleTac.lean @@ -14,18 +14,19 @@ open Lean Meta Parser Elab Tactic Auto Duper Syntax Aesop Qq namespace HammerCore -/-- Constructs a `SingleRuleTac` that uses Lean-auto/Zipperposition to attempt to solve the input goal with `formulas` (and all facts in the local context - if `includeLCtx` is enabled), and either throws an error (if Lean-auto/Zipperposition fail) or suggests a Duper invocation using the subset of `formulas` - that Zipperposition used to find a proof. +/-- Constructs a `SingleRuleTac` that uses Lean-auto/Zipperposition to attempt to solve the input goal with `formulas` (retrieved from + `Hammer.duperSubprocedureInputExt`) and all facts in the local context if `includeLCtx` is enabled, then either throws an error + (if Lean-auto/Zipperposition fail) or suggests a Duper invocation using the subset of `formulas` that Zipperposition used to find a proof. **TODO** Currently, Zipperposition's unsat core is not used to minimize the set of formulas from the local context that are sent to Duper, but in the future, this behavior should be added as an option (it should theoretically improve the strength of some suggested Duper invocations at the cost of increasing the size of all suggested Duper invocations). -/ -def duperSingleRuleTac (formulas : List (Expr × Expr × Array Name × Bool × String)) - (includeLCtx : Bool) (configOptions : ConfigurationOptions) : SingleRuleTac := λ input => do +def duperSingleRuleTac : SingleRuleTac := λ input => do let preState ← saveState input.goal.withContext do Core.checkSystem s!"{decl_name%}" + let some (formulas, includeLCtx, configOptions) := Hammer.duperSubprocedureInputExt.getState (← getEnv) + | throwError "{decl_name%} :: The Duper subprocedure's input was not set (this rule is only meant to be invoked by `hammer`)" let lctxBeforeIntros ← getLCtx let originalMainGoal := input.goal let goalType ← originalMainGoal.getType @@ -116,12 +117,14 @@ def duperSingleRuleTac (formulas : List (Expr × Expr × Array Name × Bool × S let postGoals ← postGoals.mapM (mvarIdToSubgoal input.goal ·) return (postGoals, some #[step], some ⟨1.0⟩) -/-- Runs `grind?` with `grind` parameters built from `grindPremiseNames`, then records the first `grind` tactic - that `grind?` suggests (the same suggestion the interactive `grind?` tactic would emit) as an Aesop script step. -/ -def grindSingleRuleTac (grindPremiseNames : Array Name) : SingleRuleTac := λ input => do +/-- Runs `grind?` with `grind` parameters built from `grindPremiseNames` (retrieved from `Hammer.grindSubprocedureInputExt`), then records the + first `grind` tactic that `grind?` suggests (the same suggestion the interactive `grind?` tactic would emit) as an Aesop script step. -/ +def grindSingleRuleTac : SingleRuleTac := λ input => do let preState ← saveState input.goal.withContext do Core.checkSystem s!"{decl_name%}" + let some grindPremiseNames := Hammer.grindSubprocedureInputExt.getState (← getEnv) + | throwError "{decl_name%} :: The Grind subprocedure's input was not set (this rule is only meant to be invoked by `hammer`)" let grindParamStxs : TSyntaxArray `Lean.Parser.Tactic.grindParam ← grindPremiseNames.mapM (fun n => `(Lean.Parser.Tactic.grindParam| $(mkIdent n):ident)) let grindQuestionStx ← `(tactic| grind? [$grindParamStxs,*]) @@ -149,7 +152,6 @@ def grindSingleRuleTac (grindPremiseNames : Array Name) : SingleRuleTac := λ in let postGoals ← postGoals.mapM (mvarIdToSubgoal input.goal ·) return (postGoals, some #[step], some ⟨1.0⟩) --- The following code was written by Tomaz Mascarenhas. It is scheduled to be added to lean-smt, and once it is, it can be removed from here. namespace Smt -- The type and the corresponding syntax @@ -167,9 +169,11 @@ def createArrow (es : List Expr) (e : Expr) : Expr := let hd : Q(Prop) := hd q($hd -> $r) -def smtSingleRuleTac (ps : Premises) (includeLCtx : Bool) (configOptions : HammerCore.ConfigurationOptions) : SingleRuleTac := fun input => do +def smtSingleRuleTac : SingleRuleTac := fun input => do let preState ← saveState input.goal.withContext do + let some (ps, includeLCtx, configOptions) := Hammer.smtSubprocedureInputExt.getState (← getEnv) + | throwError "{decl_name%} :: The Lean-SMT subprocedure's input was not set (this rule is only meant to be invoked by `hammer`)" let g ← input.goal.getType let types := ps.map (fun p => p.1) let arrow := createArrow types g diff --git a/Hammer/Tactic.lean b/Hammer/Tactic.lean index 9e8e8e1..83c9b57 100644 --- a/Hammer/Tactic.lean +++ b/Hammer/Tactic.lean @@ -33,6 +33,36 @@ syntax (name := hammer) "hammer" (ppSpace "[" (term),* "]")? (ppSpace "{"Hammer. set_library_suggestions open Lean.LibrarySuggestions in Cloud.premiseSelector <|> sineQuaNonSelector.intersperse currentFile +/-- The Duper subprocedure invoked by Aesop takes as input: + - `formulas` : `List (Expr × Expr × Array Name × Bool × String)` + - `includeLCtx` : `Bool` + - `configOptions` : `HammerCore.ConfigurationOptions` -/ +abbrev duperSubprocedureInputType := (List (Expr × Expr × Array Name × Bool × String)) × Bool × HammerCore.ConfigurationOptions + +/-- An environment extension that holds the input intended for the Duper subprocedure invoked by Aesop (`HammerCore.duperSingleRuleTac`). -/ +initialize duperSubprocedureInputExt : EnvExtension (Option duperSubprocedureInputType) ← + registerEnvExtension (pure none) (asyncMode := .local) + +/-- The Grind subprocedure invoked by Aesop takes as input: + - `premiseNames` : `Array Name` -/ +abbrev grindSubprocedureInputType := Array Name + +/-- An environment extension that holds the input intended for the Grind subprocedure invoked by Aesop + (`HammerCore.grindSingleRuleTac`). -/ +initialize grindSubprocedureInputExt : EnvExtension (Option grindSubprocedureInputType) ← + registerEnvExtension (pure none) (asyncMode := .local) + +/-- The Lean-SMT subprocedure invoked by Aesop takes as input: + - `hints` : `List (Expr × Syntax)` + - `includeLCtx` : `Bool` + - `configOptions` : `HammerCore.ConfigurationOptions` -/ +abbrev smtSubprocedureInputType := (List (Expr × Syntax)) × Bool × HammerCore.ConfigurationOptions + +/-- An environment extension that holds the input intended for the Lean-SMT subprocedure invoked by Aesop + (`HammerCore.Smt.smtSingleRuleTac`). -/ +initialize smtSubprocedureInputExt : EnvExtension (Option smtSubprocedureInputType) ← + registerEnvExtension (pure none) (asyncMode := .local) + /-- This functions produces one Aesop call where Aesop is given: - unsafe rules corresponding to individual premise applications (these are determined by `addIdentStxs`). - one unsafe rule to call the Lean-auto/Zipperposition/Duper pipeline (if `configOptions.disableDuper` is @@ -53,26 +83,13 @@ def runAesopWithSubprocedures (duperPremises : Array Term) (addIdentStxs : TSynt -- **TODO** This approach prohibits handling arguments that aren't disambiguated theorem names formulas.filterMap (fun (fact, proof, params, isFromGoal, stxOpt) => stxOpt.map (fun stx => (fact, proof, params, isFromGoal, stx.raw.getId.toString))) - let ruleTacType := mkConst `Aesop.SingleRuleTac - let duperRuleTacVal ← mkAppM `HammerCore.duperSingleRuleTac #[q($formulas), q($includeLCtx), q($configOptions)] - let duperRuleTacName := mkPrivateName (← getEnv) `instantiatedDuperRuleTac - let duperRuleTacDecl := - mkDefinitionValEx duperRuleTacName [] ruleTacType duperRuleTacVal - ReducibilityHints.opaque DefinitionSafety.safe [duperRuleTacName] - modifyEnv (markMeta · duperRuleTacName) - addAndCompile $ Declaration.defnDecl duperRuleTacDecl - let duperRuleTacStx ← `(Aesop.rule_expr| ($(mkIdent duperRuleTacName))) + modifyEnv (duperSubprocedureInputExt.setState · (some (formulas, includeLCtx, configOptions))) + let duperRuleTacStx ← `(Aesop.rule_expr| ($(mkIdent `_root_.HammerCore.duperSingleRuleTac))) let addAutoUnsafeRule ← `(Aesop.tactic_clause| (add unsafe $(Syntax.mkNatLit configOptions.aesopDuperPriority):num% tactic $duperRuleTacStx)) -- Building `grindRuleTacStx` - let grindRuleTacVal ← mkAppM `HammerCore.grindSingleRuleTac #[q($grindPremiseNames)] - let grindRuleTacName := mkPrivateName (← getEnv) `instantiatedGrindRuleTac - let grindRuleTacDecl := - mkDefinitionValEx grindRuleTacName [] ruleTacType grindRuleTacVal - ReducibilityHints.opaque DefinitionSafety.safe [grindRuleTacName] - modifyEnv (markMeta · grindRuleTacName) - addAndCompile $ Declaration.defnDecl grindRuleTacDecl - let grindRuleTacStx ← `(Aesop.rule_expr| ($(mkIdent grindRuleTacName))) + modifyEnv (grindSubprocedureInputExt.setState · (some grindPremiseNames)) + let grindRuleTacStx ← `(Aesop.rule_expr| ($(mkIdent `_root_.HammerCore.grindSingleRuleTac))) let addGrindUnsafeRule ← `(Aesop.tactic_clause| (add unsafe $(Syntax.mkNatLit configOptions.aesopGrindPriority):num% tactic $grindRuleTacStx)) -- Building `smtRuleTacStx` @@ -87,14 +104,8 @@ def runAesopWithSubprocedures (duperPremises : Array Term) (addIdentStxs : TSynt pure ({}, #[]) let smtHintTypes ← elabedSmtHints.mapM (fun h => Meta.inferType h) let smtHintTypesAndStx : List (Expr × Syntax) := List.zip smtHintTypes.toList $ smtPremises.toList.map (fun t => t.raw) - let smtRuleTacVal ← mkAppM `HammerCore.Smt.smtSingleRuleTac #[q($smtHintTypesAndStx), q($includeLCtx), q($configOptions)] - let smtRuleTacName := mkPrivateName (← getEnv) `instantiatedSmtRuleTac - let smtRuleTacDecl := - mkDefinitionValEx smtRuleTacName [] ruleTacType smtRuleTacVal - ReducibilityHints.opaque DefinitionSafety.safe [smtRuleTacName] - modifyEnv (markMeta · smtRuleTacName) - addAndCompile $ Declaration.defnDecl smtRuleTacDecl - let smtRuleTacStx ← `(Aesop.rule_expr| ($(mkIdent smtRuleTacName))) + modifyEnv (smtSubprocedureInputExt.setState · (some (smtHintTypesAndStx, includeLCtx, configOptions))) + let smtRuleTacStx ← `(Aesop.rule_expr| ($(mkIdent `_root_.HammerCore.Smt.smtSingleRuleTac))) let addSmtUnsafeRule ← `(Aesop.tactic_clause| (add unsafe $(Syntax.mkNatLit configOptions.aesopSmtPriority):num% tactic $smtRuleTacStx)) -- Calling Aesop with the set of subprocedures determined by `configOptions` From e8cf8f93cec237bc5df24a5e6222a2481c932dd3 Mon Sep 17 00:00:00 2001 From: JOSHCLUNE Date: Thu, 20 Aug 2026 15:45:06 -0400 Subject: [PATCH 2/7] Split solverTimeout into solverShortTimeout and solverLongTimeout --- Hammer/HammerCore.lean | 3 +- Hammer/Options.lean | 69 +++++++++++++++++++++++++++------------ Hammer/SingleRuleTac.lean | 8 +++-- Hammer/Smt.lean | 3 +- 4 files changed, 58 insertions(+), 25 deletions(-) diff --git a/Hammer/HammerCore.lean b/Hammer/HammerCore.lean index ced2809..3a8b010 100644 --- a/Hammer/HammerCore.lean +++ b/Hammer/HammerCore.lean @@ -123,7 +123,8 @@ def runDuper (stxRef : Syntax) (simpLemmas : Syntax.TSepArray [`Lean.Parser.Tact -- **NOTE** We collect `formulas` using `Duper.collectAssumptions` rather than `Auto.collectAllLemmas` because `Auto.collectAllLemmas` -- does not currently support a mode where unusable facts are ignored. let formulas ← withDuperOptions $ collectAssumptions premises includeLCtx goalDecls - withSolverOptions configOptions do + -- `runDuper` calls the solver independently as part of its own procedure, so the long timeout is used + withSolverOptions configOptions.solverLongTimeout do let lemmas ← formulasToAutoLemmas formulas (includeInSetOfSupport := true) -- Calling `Auto.unfoldConstAndPreprocessLemma` is an essential step for the monomorphization procedure let lemmas ← diff --git a/Hammer/Options.lean b/Hammer/Options.lean index cb6ec7a..451c7f7 100644 --- a/Hammer/Options.lean +++ b/Hammer/Options.lean @@ -19,9 +19,14 @@ declare_syntax_cat Hammer.bool_lit (behavior := symbol) syntax "true" : Hammer.bool_lit syntax "false" : Hammer.bool_lit -register_option hammer.solverTimeoutDefault : Nat := { +register_option hammer.solverShortTimeoutDefault : Nat := { + defValue := 1 + descr := "The default short timeout for the solver (in seconds). This is used when Aesop calls multiple solvers as subprocedures." +} + +register_option hammer.solverLongTimeoutDefault : Nat := { defValue := 5 - descr := "The default timeout for the solver (in seconds)" + descr := "The default long timeout for the solver (in seconds). This is used when a solver is called independently as part of its own procedure." } register_option hammer.wallclockTimeoutDefault : Nat := { @@ -106,7 +111,8 @@ register_option hammer.outputAllSuggestionsDefault : Bool := { namespace HammerCore -def getHammerSolverTimeoutDefault (opts : Options) : Nat := hammer.solverTimeoutDefault.get opts +def getHammerSolverShortTimeoutDefault (opts : Options) : Nat := hammer.solverShortTimeoutDefault.get opts +def getHammerSolverLongTimeoutDefault (opts : Options) : Nat := hammer.solverLongTimeoutDefault.get opts def getHammerWallclockTimeoutDefault (opts : Options) : Nat := hammer.wallclockTimeoutDefault.get opts def getPreprocessingDefault (opts : Options) : String := hammer.preprocessingDefault.get opts def getDisableAesopDefault (opts : Options) : Bool := hammer.disableAesopDefault.get opts @@ -124,9 +130,13 @@ def getAesopSmtPriorityDefault (opts : Options) : Nat := hammer.aesopSmtPriority def getParallelismDefault (opts : Options) : Bool := hammer.parallelismDefault.get opts def getOutputAllSuggestionsDefault (opts : Options) : Bool := hammer.outputAllSuggestionsDefault.get opts -def getHammerSolverTimeoutDefaultM : CoreM Nat := do +def getHammerSolverShortTimeoutDefaultM : CoreM Nat := do + let opts ← getOptions + return getHammerSolverShortTimeoutDefault opts + +def getHammerSolverLongTimeoutDefaultM : CoreM Nat := do let opts ← getOptions - return getHammerSolverTimeoutDefault opts + return getHammerSolverLongTimeoutDefault opts def getHammerWallclockTimeoutDefaultM : CoreM Nat := do let opts ← getOptions @@ -231,7 +241,8 @@ def elabBoolLit [Monad m] [MonadError m] (stx : TSyntax `Hammer.bool_lit) : m Bo | `(bool_lit| false) => return false | _ => Elab.throwUnsupportedSyntax -syntax (&"solverTimeout" " := " numLit) : Hammer.configOption +syntax (&"solverShortTimeout" " := " numLit) : Hammer.configOption +syntax (&"solverLongTimeout" " := " numLit) : Hammer.configOption syntax (&"wallclockTimeout" " := " numLit) : Hammer.configOption syntax (&"preprocessing" " := " Hammer.preprocessing) : Hammer.configOption syntax (&"disableDuper" " := " Hammer.bool_lit) : Hammer.configOption @@ -250,7 +261,8 @@ syntax (&"parallelism" " := " Hammer.bool_lit) : Hammer.configOption -- Whether syntax (&"outputAllSuggestions" " := " Hammer.bool_lit) : Hammer.configOption -- Whether to show the user all suggestions or just the first one (default: false) structure ConfigurationOptions where - solverTimeout : Nat + solverShortTimeout : Nat -- The solver timeout used when Aesop calls multiple solvers as subprocedures + solverLongTimeout : Nat -- The solver timeout used when a solver is called independently as part of its own procedure wallclockTimeout : Nat preprocessing : Preprocessing disableDuper : Bool @@ -279,8 +291,14 @@ macro_rules | `(tactic| hammerCore [$simpLemmas,*] [$facts,*]) => `(tactic| hamm /-- Checks to ensure that the set of given `configOptions` is usable. -/ def validateConfigOptions (configOptions : ConfigurationOptions) : TacticM ConfigurationOptions := do - if configOptions.wallclockTimeout > 0 && configOptions.wallclockTimeout < configOptions.solverTimeout then - throwError "Erroneous invocation of hammer: The wallclockTimeout must be greater than or equal to the solverTimeout" + if configOptions.solverShortTimeout > configOptions.solverLongTimeout then + throwError "Erroneous invocation of hammer: The solverShortTimeout must be less than or equal to the solverLongTimeout" + if configOptions.wallclockTimeout == 0 then + throwError "Erroneous invocation of hammer: The wallclockTimeout must be greater than 0" + if configOptions.wallclockTimeout < configOptions.solverShortTimeout then + throwError "Erroneous invocation of hammer: The wallclockTimeout must be greater than or equal to the solverShortTimeout" + if configOptions.wallclockTimeout < configOptions.solverLongTimeout then + throwError "Erroneous invocation of hammer: The wallclockTimeout must be greater than or equal to the solverLongTimeout" if !configOptions.parallelism && configOptions.outputAllSuggestions then throwError "Erroneous invocation of hammer: The outputAllSuggestions option can only be enabled when parallelism is enabled" if configOptions.disableAesop && configOptions.disableDuper && configOptions.disableGrind && configOptions.disableSmt then @@ -310,7 +328,8 @@ def validateConfigOptions (configOptions : ConfigurationOptions) : TacticM Confi return configOptions def parseConfigOptions (configOptionsStx : TSyntaxArray `Hammer.configOption) : TacticM ConfigurationOptions := do - let mut solverTimeoutOpt := none + let mut solverShortTimeoutOpt := none + let mut solverLongTimeoutOpt := none let mut wallclockTimeoutOpt := none let mut preprocessingOpt := none let mut disableDuperOpt := none @@ -329,9 +348,12 @@ def parseConfigOptions (configOptionsStx : TSyntaxArray `Hammer.configOption) : let mut outputAllSuggestionsOpt := none for configOptionStx in configOptionsStx do match configOptionStx with - | `(Hammer.configOption| solverTimeout := $userSolverTimeout:num) => - if solverTimeoutOpt.isNone then solverTimeoutOpt := some (TSyntax.getNat userSolverTimeout) - else throwError "Erroneous invocation of hammer: The solverTimeout option has been specified multiple times" + | `(Hammer.configOption| solverShortTimeout := $userSolverShortTimeout:num) => + if solverShortTimeoutOpt.isNone then solverShortTimeoutOpt := some (TSyntax.getNat userSolverShortTimeout) + else throwError "Erroneous invocation of hammer: The solverShortTimeout option has been specified multiple times" + | `(Hammer.configOption| solverLongTimeout := $userSolverLongTimeout:num) => + if solverLongTimeoutOpt.isNone then solverLongTimeoutOpt := some (TSyntax.getNat userSolverLongTimeout) + else throwError "Erroneous invocation of hammer: The solverLongTimeout option has been specified multiple times" | `(Hammer.configOption| wallclockTimeout := $userWallclockTimeout:num) => if wallclockTimeoutOpt.isNone then wallclockTimeoutOpt := some (TSyntax.getNat userWallclockTimeout) else throwError "Erroneous invocation of hammer: The wallclockTimeout option has been specified multiple times" @@ -382,10 +404,14 @@ def parseConfigOptions (configOptionsStx : TSyntaxArray `Hammer.configOption) : else throwError "Erroneous invocation of hammer: The outputAllSuggestions option has been specified multiple times" | _ => throwUnsupportedSyntax -- Set default values for options that were not specified - let solverTimeout ← - match solverTimeoutOpt with - | none => getHammerSolverTimeoutDefaultM - | some solverTimeout => pure solverTimeout + let solverShortTimeout ← + match solverShortTimeoutOpt with + | none => getHammerSolverShortTimeoutDefaultM + | some solverShortTimeout => pure solverShortTimeout + let solverLongTimeout ← + match solverLongTimeoutOpt with + | none => getHammerSolverLongTimeoutDefaultM + | some solverLongTimeout => pure solverLongTimeout let wallclockTimeout ← match wallclockTimeoutOpt with | none => getHammerWallclockTimeoutDefaultM @@ -453,7 +479,8 @@ def parseConfigOptions (configOptionsStx : TSyntaxArray `Hammer.configOption) : | none => getOutputAllSuggestionsDefaultM | some outputAllSuggestions => pure outputAllSuggestions let configOptions := { - solverTimeout := solverTimeout, wallclockTimeout := wallclockTimeout, preprocessing := preprocessing, + solverShortTimeout := solverShortTimeout, solverLongTimeout := solverLongTimeout, + wallclockTimeout := wallclockTimeout, preprocessing := preprocessing, disableDuper := disableDuper, disableGrind := disableGrind, disableAesop := disableAesop, disableSmt := disableSmt, duperPremises := duperPremises, aesopPremises := aesopPremises, grindPremises := grindPremises, smtPremises := smtPremises, aesopPremisePriority := aesopPremisePriority, aesopDuperPriority := aesopDuperPriority, @@ -463,11 +490,13 @@ def parseConfigOptions (configOptionsStx : TSyntaxArray `Hammer.configOption) : let configOptions ← validateConfigOptions configOptions return configOptions -def withSolverOptions [Monad m] [MonadError m] [MonadWithOptions m] (configOptions : ConfigurationOptions) (x : m α) : m α := +/-- `solverTimeout` should be `configOptions.solverShortTimeout` when the solver is called by Aesop as a subprocedure + and `configOptions.solverLongTimeout` when the solver is called independently as part of its own procedure. -/ +def withSolverOptions [Monad m] [MonadError m] [MonadWithOptions m] (solverTimeout : Nat) (x : m α) : m α := withOptions (fun o => let o := o.set `auto.tptp true - let o := o.set `auto.tptp.timeout configOptions.solverTimeout + let o := o.set `auto.tptp.timeout solverTimeout let o := o.set `auto.smt false let o := o.set `auto.tptp.premiseSelection true let o := o.set `auto.tptp.solver.name "zipperposition" diff --git a/Hammer/SingleRuleTac.lean b/Hammer/SingleRuleTac.lean index 3fd1680..3323704 100644 --- a/Hammer/SingleRuleTac.lean +++ b/Hammer/SingleRuleTac.lean @@ -65,7 +65,8 @@ def duperSingleRuleTac : SingleRuleTac := λ input => do | some stx => some (fact, proof, params, isFromGoal, some s!"{stx}") ) let formulas := (formulas.map (fun (fact, proof, params, isFromGoal, stxOpt) => (fact, proof, params, isFromGoal, some stxOpt))) ++ lctxFormulas - withSolverOptions configOptions do + -- The solver is called by Aesop as a subprocedure, so the short timeout is used + withSolverOptions configOptions.solverShortTimeout do let lemmas ← formulasWithStringsToAutoLemmas formulas (includeInSetOfSupport := true) -- Calling `Auto.unfoldConstAndPreprocessLemma` is an essential step for the monomorphization procedure let lemmas ← @@ -188,7 +189,8 @@ def smtSingleRuleTac : SingleRuleTac := fun input => do pure ((← Smt.Preprocess.getPropHyps).filter (fun fv => !hs.contains fv)) else pure #[] - let res ← Smt.smt {mono := true, timeout := configOptions.solverTimeout} mv' (hs.map (fun fv => .fvar fv) ++ lctxHyps.map (fun fv => .fvar fv)) + -- Lean-SMT is called by Aesop as a subprocedure, so the short timeout is used + let res ← Smt.smt {mono := true, timeout := configOptions.solverShortTimeout} mv' (hs.map (fun fv => .fvar fv) ++ lctxHyps.map (fun fv => .fvar fv)) let unsat_core ← match res with | .unsat mvs uc => pure uc @@ -208,7 +210,7 @@ def smtSingleRuleTac : SingleRuleTac := fun input => do pure names let namesT ← names.mapM cast_stx let idents ← namesT.mapM (fun i => `(Smt.Tactic.smtHintElem| $i:term)) - -- `cfg` doesn't include a timeout parameter because although LeanHammer's solverTimeout should be propagated to Lean-SMT for + -- `cfg` doesn't include a timeout parameter because although LeanHammer's solverShortTimeout should be propagated to Lean-SMT for -- internal calls, that timeout doesn't need to clutter the final suggestion let cfg ← `(optConfig| +$(mkIdent `mono)) let stx ← diff --git a/Hammer/Smt.lean b/Hammer/Smt.lean index 461350d..619f6e9 100644 --- a/Hammer/Smt.lean +++ b/Hammer/Smt.lean @@ -57,7 +57,8 @@ def smtPipeline (stxRef : Syntax) (simpLemmas : Syntax.TSepArray [`Lean.Parser.T -- We create `cfg` and `cfgWithTimeout` because LeanHammer's internal `smt` call should include a timeout argument, -- but the suggestion LeanHammer produces doesn't need to include said argument let cfg ← `(optConfig| +$(mkIdent `mono)) - let cfgWithTimeout ← `(optConfig| +$(mkIdent `mono) (timeout := $(Syntax.mkNatLit configOptions.solverTimeout))) + -- `smtPipeline` calls Lean-SMT independently as part of its own procedure, so the long timeout is used + let cfgWithTimeout ← `(optConfig| +$(mkIdent `mono) (timeout := $(Syntax.mkNatLit configOptions.solverLongTimeout))) let hints ← if includeLCtx then `(Smt.Tactic.smtHints| [*, $premises,*]) else `(Smt.Tactic.smtHints| [$premises,*]) From c745fe6dec94d50d6cc5656ef196aa0e4c7aa77f Mon Sep 17 00:00:00 2001 From: JOSHCLUNE Date: Sat, 22 Aug 2026 20:10:01 -0400 Subject: [PATCH 3/7] Split duperPremises option into duperPremisesShort and duperPremisesLong --- Hammer/Options.lean | 53 +++++++++++++++++++++++++++++++-------------- Hammer/Tactic.lean | 26 ++++++++++++---------- README.md | 3 ++- 3 files changed, 54 insertions(+), 28 deletions(-) diff --git a/Hammer/Options.lean b/Hammer/Options.lean index 451c7f7..e9a650f 100644 --- a/Hammer/Options.lean +++ b/Hammer/Options.lean @@ -64,9 +64,14 @@ register_option hammer.aesopPremisesDefault : Nat := { descr := "The default number of premises sent to aesop to be used as unsafe rules" } -register_option hammer.duperPremisesDefault : Nat := { +register_option hammer.duperPremisesShortDefault : Nat := { defValue := 16 - descr := "The default number of premises sent to duper" + descr := "The default number of premises sent to duper when called as a subprocedure in Aesop" +} + +register_option hammer.duperPremisesLongDefault : Nat := { + defValue := 100 + descr := "The default number of premises sent to duper when called as an independent procedure" } register_option hammer.grindPremisesDefault : Nat := { @@ -119,7 +124,8 @@ def getDisableAesopDefault (opts : Options) : Bool := hammer.disableAesopDefault def getDisableDuperDefault (opts : Options) : Bool := hammer.disableDuperDefault.get opts def getDisableGrindDefault (opts : Options) : Bool := hammer.disableGrindDefault.get opts def getDisableSmtDefault (opts : Options) : Bool := hammer.disableSmtDefault.get opts -def getDuperPremisesDefault (opts : Options) : Nat := hammer.duperPremisesDefault.get opts +def getDuperPremisesShortDefault (opts : Options) : Nat := hammer.duperPremisesShortDefault.get opts +def getDuperPremisesLongDefault (opts : Options) : Nat := hammer.duperPremisesLongDefault.get opts def getAesopPremisesDefault (opts : Options) : Nat := hammer.aesopPremisesDefault.get opts def getGrindPremisesDefault (opts : Options) : Nat := hammer.grindPremisesDefault.get opts def getSmtPremisesDefault (opts : Options) : Nat := hammer.smtPremisesDefault.get opts @@ -162,9 +168,13 @@ def getDisableSmtDefaultM : CoreM Bool := do let opts ← getOptions return getDisableSmtDefault opts -def getDuperPremisesDefaultM : CoreM Nat := do +def getDuperPremisesShortDefaultM : CoreM Nat := do + let opts ← getOptions + return getDuperPremisesShortDefault opts + +def getDuperPremisesLongDefaultM : CoreM Nat := do let opts ← getOptions - return getDuperPremisesDefault opts + return getDuperPremisesLongDefault opts def getAesopPremisesDefaultM : CoreM Nat := do let opts ← getOptions @@ -249,7 +259,8 @@ syntax (&"disableDuper" " := " Hammer.bool_lit) : Hammer.configOption syntax (&"disableAesop" " := " Hammer.bool_lit) : Hammer.configOption syntax (&"disableGrind" " := " Hammer.bool_lit) : Hammer.configOption syntax (&"disableSmt" " := " Hammer.bool_lit) : Hammer.configOption -syntax (&"duperPremises" " := " numLit) : Hammer.configOption -- The number of premises sent to `duper` (default: 16) +syntax (&"duperPremisesShort" " := " numLit) : Hammer.configOption -- The number of premises sent to `duper` when called as a subprocedure in Aesop (default: 16) +syntax (&"duperPremisesLong" " := " numLit) : Hammer.configOption -- The number of premises sent to `duper` when called as an independent procedure (default: 100) syntax (&"aesopPremises" " := " numLit) : Hammer.configOption -- The number of premises sent to `aesop` (default: 32) syntax (&"grindPremises" " := " numLit) : Hammer.configOption -- The number of premises sent to `grind` (default: 100) syntax (&"smtPremises" " := " numLit) : Hammer.configOption -- The number of premises sent to `lean-smt` (default: 16) @@ -273,7 +284,8 @@ structure ConfigurationOptions where aesopDuperPriority : Nat aesopGrindPriority : Nat aesopSmtPriority : Nat - duperPremises : Nat -- The number of premises sent to `duper` (default: 16) + duperPremisesShort : Nat -- The number of premises sent to `duper` when called as a subprocedure in Aesop (default: 16) + duperPremisesLong : Nat -- The number of premises sent to `duper` when called as an independent procedure (default: 100) aesopPremises : Nat -- The number of premises sent to `aesop` (default: 32) grindPremises : Nat -- The number of premises sent to `grind` (default: 100) smtPremises : Nat -- The number of premises sent to `lean-smt` (default: 16) @@ -336,7 +348,8 @@ def parseConfigOptions (configOptionsStx : TSyntaxArray `Hammer.configOption) : let mut disableAesopOpt := none let mut disableGrindOpt := none let mut disableSmtOpt := none - let mut duperPremisesOpt := none + let mut duperPremisesShortOpt := none + let mut duperPremisesLongOpt := none let mut aesopPremisesOpt := none let mut grindPremisesOpt := none let mut smtPremisesOpt := none @@ -372,9 +385,12 @@ def parseConfigOptions (configOptionsStx : TSyntaxArray `Hammer.configOption) : | `(Hammer.configOption| disableSmt := $disableSmtBoolLit:Hammer.bool_lit) => if disableSmtOpt.isNone then disableSmtOpt := some $ ← elabBoolLit disableSmtBoolLit else throwError "Erroneous invocation of hammer: The disableSmt option has been specified multiple times" - | `(Hammer.configOption| duperPremises := $userDuperPremises:num) => - if duperPremisesOpt.isNone then duperPremisesOpt := some (TSyntax.getNat userDuperPremises) - else throwError "Erroneous invocation of hammer: The duperPremises option has been specified multiple times" + | `(Hammer.configOption| duperPremisesShort := $userDuperPremisesShort:num) => + if duperPremisesShortOpt.isNone then duperPremisesShortOpt := some (TSyntax.getNat userDuperPremisesShort) + else throwError "Erroneous invocation of hammer: The duperPremisesShort option has been specified multiple times" + | `(Hammer.configOption| duperPremisesLong := $userDuperPremisesLong:num) => + if duperPremisesLongOpt.isNone then duperPremisesLongOpt := some (TSyntax.getNat userDuperPremisesLong) + else throwError "Erroneous invocation of hammer: The duperPremisesLong option has been specified multiple times" | `(Hammer.configOption| aesopPremises := $userAesopPremises:num) => if aesopPremisesOpt.isNone then aesopPremisesOpt := some (TSyntax.getNat userAesopPremises) else throwError "Erroneous invocation of hammer: The aesopPremises option has been specified multiple times" @@ -438,10 +454,14 @@ def parseConfigOptions (configOptionsStx : TSyntaxArray `Hammer.configOption) : if disableAesop && (← getPreprocessingDefaultM) == "aesop" then pure Preprocessing.no_preprocessing else elabPreprocessingDefault | some preprocessing => pure preprocessing - let duperPremises ← - match duperPremisesOpt with - | none => getDuperPremisesDefaultM - | some duperPremises => pure duperPremises + let duperPremisesShort ← + match duperPremisesShortOpt with + | none => getDuperPremisesShortDefaultM + | some duperPremisesShort => pure duperPremisesShort + let duperPremisesLong ← + match duperPremisesLongOpt with + | none => getDuperPremisesLongDefaultM + | some duperPremisesLong => pure duperPremisesLong let aesopPremises ← match aesopPremisesOpt with | none => getAesopPremisesDefaultM @@ -482,7 +502,8 @@ def parseConfigOptions (configOptionsStx : TSyntaxArray `Hammer.configOption) : solverShortTimeout := solverShortTimeout, solverLongTimeout := solverLongTimeout, wallclockTimeout := wallclockTimeout, preprocessing := preprocessing, disableDuper := disableDuper, disableGrind := disableGrind, disableAesop := disableAesop, disableSmt := disableSmt, - duperPremises := duperPremises, aesopPremises := aesopPremises, grindPremises := grindPremises, + duperPremisesShort := duperPremisesShort, duperPremisesLong := duperPremisesLong, + aesopPremises := aesopPremises, grindPremises := grindPremises, smtPremises := smtPremises, aesopPremisePriority := aesopPremisePriority, aesopDuperPriority := aesopDuperPriority, aesopGrindPriority := aesopGrindPriority, aesopSmtPriority := aesopSmtPriority, parallelism := parallelism, outputAllSuggestions := outputAllSuggestions diff --git a/Hammer/Tactic.lean b/Hammer/Tactic.lean index 83c9b57..4526584 100644 --- a/Hammer/Tactic.lean +++ b/Hammer/Tactic.lean @@ -204,7 +204,8 @@ def runHammer (stxRef : Syntax) (simpLemmas : Syntax.TSepArray [`Lean.Parser.Tac (userInputTerms premises : Array Term) (includeLCtx : Bool) (configOptions : HammerCore.ConfigurationOptions) : TacticM Unit := withMainContext do let premiseFilteringStart ← IO.monoMsNow - let mut duperPremises := userInputTerms ++ premises.take configOptions.duperPremises + let mut duperPremisesShort : Array Term := #[] + let mut duperPremisesLong : Array Term := #[] let aesopPremises := userInputTerms ++ premises.take configOptions.aesopPremises let grindPremises := userInputTerms ++ premises.take configOptions.grindPremises let mut smtPremises := userInputTerms ++ premises.take configOptions.smtPremises @@ -212,7 +213,10 @@ def runHammer (stxRef : Syntax) (simpLemmas : Syntax.TSepArray [`Lean.Parser.Tac let mut grindParamStxs : TSyntaxArray `Lean.Parser.Tactic.grindParam := #[] let mut grindPremiseNames : Array Name := #[] if !configOptions.disableDuper then - duperPremises ← duperPremises.filterM autoPremiseEligible -- Duper uses Lean-auto for preprocessing + -- Duper uses Lean-auto for preprocessing + let eligiblePremises ← premises.filterM autoPremiseEligible + duperPremisesShort := userInputTerms ++ eligiblePremises.take configOptions.duperPremisesShort + duperPremisesLong := userInputTerms ++ eligiblePremises.take configOptions.duperPremisesLong if !configOptions.disableAesop then for p in aesopPremises do -- **TODO** Add support for terms that aren't just names of premises @@ -234,16 +238,16 @@ def runHammer (stxRef : Syntax) (simpLemmas : Syntax.TSepArray [`Lean.Parser.Tac -- disabled) is included. let mut parallelTacs : List (TacticM Unit) := [] if !configOptions.disableAesop then - parallelTacs := parallelTacs ++ [runAesopWithSubprocedures duperPremises addIdentStxs grindPremiseNames smtPremises includeLCtx configOptions] + parallelTacs := parallelTacs ++ [runAesopWithSubprocedures duperPremisesShort addIdentStxs grindPremiseNames smtPremises includeLCtx configOptions] if !configOptions.disableDuper || !configOptions.disableGrind || !configOptions.disableSmt then parallelTacs := parallelTacs ++ - [runAesopWithSubprocedures duperPremises addIdentStxs grindPremiseNames smtPremises includeLCtx + [runAesopWithSubprocedures duperPremisesShort addIdentStxs grindPremiseNames smtPremises includeLCtx {configOptions with disableDuper := true, disableGrind := true, disableSmt := true}] if !configOptions.disableDuper then if configOptions.preprocessing == .aesop then -- `runDuper` shouldn't be run with Aesop preprocessing - parallelTacs := parallelTacs ++ [runDuper stxRef simpLemmas duperPremises includeLCtx {configOptions with preprocessing := .no_preprocessing}] + parallelTacs := parallelTacs ++ [runDuper stxRef simpLemmas duperPremisesLong includeLCtx {configOptions with preprocessing := .no_preprocessing}] else - parallelTacs := parallelTacs ++ [runDuper stxRef simpLemmas duperPremises includeLCtx configOptions] + parallelTacs := parallelTacs ++ [runDuper stxRef simpLemmas duperPremisesLong includeLCtx configOptions] if !configOptions.disableGrind then parallelTacs := parallelTacs ++ [evalTactic (← `(tactic| grind? [$grindParamStxs,*]))] if !configOptions.disableSmt then @@ -271,10 +275,10 @@ def runHammer (stxRef : Syntax) (simpLemmas : Syntax.TSepArray [`Lean.Parser.Tac | true, false, true, true => if hammer.singleTacticParallel.get (← getOptions) then tryAllTacsOnGoal stxRef configOptions.outputAllSuggestions configOptions.wallclockTimeout [ - runDuper stxRef simpLemmas duperPremises includeLCtx configOptions + runDuper stxRef simpLemmas duperPremisesLong includeLCtx configOptions ] else - runSingularTactic (runDuper stxRef simpLemmas duperPremises includeLCtx configOptions) + runSingularTactic (runDuper stxRef simpLemmas duperPremisesLong includeLCtx configOptions) | true, true, true, false => if hammer.singleTacticParallel.get (← getOptions) then tryAllTacsOnGoal stxRef configOptions.outputAllSuggestions configOptions.wallclockTimeout [ @@ -285,10 +289,10 @@ def runHammer (stxRef : Syntax) (simpLemmas : Syntax.TSepArray [`Lean.Parser.Tac | false, _, _, _ => if hammer.singleTacticParallel.get (← getOptions) then tryAllTacsOnGoal stxRef configOptions.outputAllSuggestions configOptions.wallclockTimeout [ - runAesopWithSubprocedures duperPremises addIdentStxs grindPremiseNames smtPremises includeLCtx configOptions + runAesopWithSubprocedures duperPremisesShort addIdentStxs grindPremiseNames smtPremises includeLCtx configOptions ] else - runSingularTactic (runAesopWithSubprocedures duperPremises addIdentStxs grindPremiseNames smtPremises includeLCtx configOptions) + runSingularTactic (runAesopWithSubprocedures duperPremisesShort addIdentStxs grindPremiseNames smtPremises includeLCtx configOptions) | true, true, true, true => throwError "Erroneous invocation of hammer: At least one of Aesop, Duper, Grind, and Lean-SMT must be enabled." | _, _, _, _ => throwError "Erroneous invocation of hammer: Aesop or parallelism is needed to enable more than one of Duper, Grind, and SMT." @@ -300,7 +304,7 @@ def evalHammerWithArgs : Tactic let goal ← getMainGoal let userInputTerms : Array Term := userInputTerms let configOptions ← parseConfigOptions configOptions - let autoPremises := if configOptions.disableDuper then 0 else configOptions.duperPremises + let autoPremises := if configOptions.disableDuper then 0 else max configOptions.duperPremisesShort configOptions.duperPremisesLong let aesopPremises := if configOptions.disableAesop then 0 else configOptions.aesopPremises let grindPremises := if configOptions.disableGrind then 0 else configOptions.grindPremises let smtPremises := if configOptions.disableSmt then 0 else configOptions.smtPremises diff --git a/README.md b/README.md index 8c17b17..0429af2 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,8 @@ Each of the `options` supplied to `hammer` have the form `option := value` and a - `disableSmt`: Can be set to `true` or `false` (default `false`). This option is used to remove the Lean-SMT pipeline (which involves calling Lean-auto and the Lean cvc5 FFI). - `preprocessing`: Can be set to `aesop`, `simp_target`, `simp_all`, or `no_preprocessing` (default `aesop`). This option determines whether the initial goal is first processed by `aesop`, `simp`, `simp_all`, or none of these. This option can only be set to a value other than `aesop` if `disableAesop` is set to `true`, and must be set to `aesop` if `disableAesop` is set to `false`. - `aesopPremises`: Can be set to any Nat (default 32). This option determines the number of lemmas from premise selection that are passed to Aesop as unsafe rules. -- `duperPremises`: Can be set to any Nat (default 16). This option determines the number of lemmas from premise selection that are passed to Lean-auto, Zipperposition, and Duper. +- `duperPremisesShort`: Can be set to any Nat (default 16). This option determines the number of lemmas from premise selection that are passed to Lean-auto, Zipperposition, and Duper when called as a subprocedure in Aesop. +- `duperPremisesLong`: Can be set to any Nat (default 100). This option determines the number of lemmas from premise selection that are passed to Lean-auto, Zipperposition, and Duper when called as an independent procedure. - `grindPremises`: Can be set to any Nat (default 100). This option determines the number of lemmas from premise selection that are passed to Grind. - `smtPremises`: Can be set to any Nat (default 16). This option determines the number of lemmas from premise selection that are passed to Lean-SMT. - `aesopPremisePriority`: Can be set to any Nat between 0 and 100 (default 20). This option determines the Aesop success priority assigned to each of the lemmas from premise selection when passed to Aesop as unsafe rules. See [Aesop's README](https://github.com/leanprover-community/aesop) for additional details on the meaning of this success priority. From 691a1a910c17c8d2b66bbe0ec6a91a9901b71e65 Mon Sep 17 00:00:00 2001 From: JOSHCLUNE Date: Wed, 26 Aug 2026 20:53:05 -0400 Subject: [PATCH 4/7] Minor refactor of runAesopWithSubprocedures --- Hammer/Tactic.lean | 81 +++++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/Hammer/Tactic.lean b/Hammer/Tactic.lean index 4526584..1319f22 100644 --- a/Hammer/Tactic.lean +++ b/Hammer/Tactic.lean @@ -77,47 +77,46 @@ def runAesopWithSubprocedures (duperPremises : Array Term) (addIdentStxs : TSynt (grindPremiseNames : Array Name) (smtPremises : Array Term) (includeLCtx : Bool) (configOptions : HammerCore.ConfigurationOptions) : TacticM Unit := withMainContext do withOptions (fun o => o.set `aesop.warn.applyIff false) do - -- Building `duperRuleTacStx` - let formulas ← withDuperOptions $ collectAssumptions duperPremises false #[] - let formulas : List (Expr × Expr × Array Name × Bool × String) := - -- **TODO** This approach prohibits handling arguments that aren't disambiguated theorem names - formulas.filterMap (fun (fact, proof, params, isFromGoal, stxOpt) => - stxOpt.map (fun stx => (fact, proof, params, isFromGoal, stx.raw.getId.toString))) - modifyEnv (duperSubprocedureInputExt.setState · (some (formulas, includeLCtx, configOptions))) - let duperRuleTacStx ← `(Aesop.rule_expr| ($(mkIdent `_root_.HammerCore.duperSingleRuleTac))) - let addAutoUnsafeRule ← - `(Aesop.tactic_clause| (add unsafe $(Syntax.mkNatLit configOptions.aesopDuperPriority):num% tactic $duperRuleTacStx)) - -- Building `grindRuleTacStx` - modifyEnv (grindSubprocedureInputExt.setState · (some grindPremiseNames)) - let grindRuleTacStx ← `(Aesop.rule_expr| ($(mkIdent `_root_.HammerCore.grindSingleRuleTac))) - let addGrindUnsafeRule ← - `(Aesop.tactic_clause| (add unsafe $(Syntax.mkNatLit configOptions.aesopGrindPriority):num% tactic $grindRuleTacStx)) - -- Building `smtRuleTacStx` - let smtHints ← smtPremises.mapM (fun n => `(Smt.Tactic.smtHintElem| $n:term)) - let (_, elabedSmtHints) ← - try - -- `Smt.Tactic.elabHints` can yield the error: "failed to elaborate eliminator, expected type is not available". Lean-SMT's internal - -- filter should handle this, but if it doesn't it is better to silently pass nothing to Lean-SMT than to throw an error. - Smt.Tactic.elabHints (← `(Smt.Tactic.smtHints| [$(smtHints),*])) - catch _ => - trace[hammer.debug] "{decl_name%} :: Failed to elab smt hints: {smtHints}, passing zero hints to Lean-SMT" - pure ({}, #[]) - let smtHintTypes ← elabedSmtHints.mapM (fun h => Meta.inferType h) - let smtHintTypesAndStx : List (Expr × Syntax) := List.zip smtHintTypes.toList $ smtPremises.toList.map (fun t => t.raw) - modifyEnv (smtSubprocedureInputExt.setState · (some (smtHintTypesAndStx, includeLCtx, configOptions))) - let smtRuleTacStx ← `(Aesop.rule_expr| ($(mkIdent `_root_.HammerCore.Smt.smtSingleRuleTac))) - let addSmtUnsafeRule ← - `(Aesop.tactic_clause| (add unsafe $(Syntax.mkNatLit configOptions.aesopSmtPriority):num% tactic $smtRuleTacStx)) - -- Calling Aesop with the set of subprocedures determined by `configOptions` - match configOptions.disableDuper, configOptions.disableGrind, configOptions.disableSmt with - | true, true, true => Aesop.evalAesop (← `(tactic| aesop? $addIdentStxs*)) - | true, true, false => Aesop.evalAesop (← `(tactic| aesop? $addIdentStxs* $addSmtUnsafeRule)) - | true, false, true => Aesop.evalAesop (← `(tactic| aesop? $addIdentStxs* $addGrindUnsafeRule)) - | true, false, false => Aesop.evalAesop (← `(tactic| aesop? $addIdentStxs* $addGrindUnsafeRule $addSmtUnsafeRule)) - | false, true, true => Aesop.evalAesop (← `(tactic| aesop? $addIdentStxs* $addAutoUnsafeRule)) - | false, true, false => Aesop.evalAesop (← `(tactic| aesop? $addIdentStxs* $addAutoUnsafeRule $addSmtUnsafeRule)) - | false, false, true => Aesop.evalAesop (← `(tactic| aesop? $addIdentStxs* $addAutoUnsafeRule $addGrindUnsafeRule)) - | false, false, false => Aesop.evalAesop (← `(tactic| aesop? $addIdentStxs* $addAutoUnsafeRule $addGrindUnsafeRule $addSmtUnsafeRule)) + let mut subprocedures : Array (TSyntax `Aesop.tactic_clause) := #[] + -- Building `duperRuleTacStx` and adding it to `subprocedures` + if !configOptions.disableDuper then + let formulas ← withDuperOptions $ collectAssumptions duperPremises false #[] + let formulas : List (Expr × Expr × Array Name × Bool × String) := + -- **TODO** This approach prohibits handling arguments that aren't disambiguated theorem names + formulas.filterMap (fun (fact, proof, params, isFromGoal, stxOpt) => + stxOpt.map (fun stx => (fact, proof, params, isFromGoal, stx.raw.getId.toString))) + modifyEnv (duperSubprocedureInputExt.setState · (some (formulas, includeLCtx, configOptions))) + let duperRuleTacStx ← `(Aesop.rule_expr| ($(mkIdent `_root_.HammerCore.duperSingleRuleTac))) + let addAutoUnsafeRule ← + `(Aesop.tactic_clause| (add unsafe $(Syntax.mkNatLit configOptions.aesopDuperPriority):num% tactic $duperRuleTacStx)) + subprocedures := subprocedures.push addAutoUnsafeRule + -- Building `grindRuleTacStx` and adding it to `subprocedures` + if !configOptions.disableGrind then + modifyEnv (grindSubprocedureInputExt.setState · (some grindPremiseNames)) + let grindRuleTacStx ← `(Aesop.rule_expr| ($(mkIdent `_root_.HammerCore.grindSingleRuleTac))) + let addGrindUnsafeRule ← + `(Aesop.tactic_clause| (add unsafe $(Syntax.mkNatLit configOptions.aesopGrindPriority):num% tactic $grindRuleTacStx)) + subprocedures := subprocedures.push addGrindUnsafeRule + -- Building `smtRuleTacStx` and adding it to `subprocedures` + if !configOptions.disableSmt then + let smtHints ← smtPremises.mapM (fun n => `(Smt.Tactic.smtHintElem| $n:term)) + let (_, elabedSmtHints) ← + try + -- `Smt.Tactic.elabHints` can yield the error: "failed to elaborate eliminator, expected type is not available". Lean-SMT's internal + -- filter should handle this, but if it doesn't it is better to silently pass nothing to Lean-SMT than to throw an error. + Smt.Tactic.elabHints (← `(Smt.Tactic.smtHints| [$(smtHints),*])) + catch _ => + trace[hammer.debug] "{decl_name%} :: Failed to elab smt hints: {smtHints}, passing zero hints to Lean-SMT" + pure ({}, #[]) + let smtHintTypes ← elabedSmtHints.mapM (fun h => Meta.inferType h) + let smtHintTypesAndStx : List (Expr × Syntax) := List.zip smtHintTypes.toList $ smtPremises.toList.map (fun t => t.raw) + modifyEnv (smtSubprocedureInputExt.setState · (some (smtHintTypesAndStx, includeLCtx, configOptions))) + let smtRuleTacStx ← `(Aesop.rule_expr| ($(mkIdent `_root_.HammerCore.Smt.smtSingleRuleTac))) + let addSmtUnsafeRule ← + `(Aesop.tactic_clause| (add unsafe $(Syntax.mkNatLit configOptions.aesopSmtPriority):num% tactic $smtRuleTacStx)) + subprocedures := subprocedures.push addSmtUnsafeRule + -- Calling Aesop with `subprocedures` + Aesop.evalAesop (← `(tactic| aesop? $addIdentStxs* $subprocedures*)) /-- Runs `Meta.isProof` on `e` and every subterm of `e`. If `Meta.isProof` ever returns `true`, then `autoPremiseTypeEligibleAux` returns `false`. From 0b40898986194f3a88aa7c5ba331758aeb76622d Mon Sep 17 00:00:00 2001 From: JOSHCLUNE Date: Fri, 28 Aug 2026 14:17:41 -0400 Subject: [PATCH 5/7] Enforce soft ordering invariant on premises sent to Lean-auto --- Hammer/DuperCore.lean | 2 +- Hammer/HammerCore.lean | 2 +- Hammer/SingleRuleTac.lean | 15 +++++++++++---- Hammer/Tactic.lean | 8 +++++--- lake-manifest.json | 4 ++-- lakefile.toml | 2 +- 6 files changed, 21 insertions(+), 12 deletions(-) diff --git a/Hammer/DuperCore.lean b/Hammer/DuperCore.lean index 436fe4a..cefe4cb 100644 --- a/Hammer/DuperCore.lean +++ b/Hammer/DuperCore.lean @@ -57,7 +57,7 @@ def getDuperCoreLemmas (unsatCoreDerivLeafStrings : Array String) (userFacts : S coreUserFacts := coreUserFacts.push factStx -- Build `formulas` to pass into `runDuperPortfolioMode` trace[hammer.debug] "{decl_name%} :: Collecting assumptions. coreUserFacts: {coreUserFacts}" - let mut formulas := (← collectAssumptions coreUserFacts includeAllLctx goalDecls).toArray + let formulas ← collectAssumptions coreUserFacts includeAllLctx goalDecls -- Try to reconstruct the proof using `runDuperPortfolioMode` let prf ← try diff --git a/Hammer/HammerCore.lean b/Hammer/HammerCore.lean index 3a8b010..04b263e 100644 --- a/Hammer/HammerCore.lean +++ b/Hammer/HammerCore.lean @@ -125,7 +125,7 @@ def runDuper (stxRef : Syntax) (simpLemmas : Syntax.TSepArray [`Lean.Parser.Tact let formulas ← withDuperOptions $ collectAssumptions premises includeLCtx goalDecls -- `runDuper` calls the solver independently as part of its own procedure, so the long timeout is used withSolverOptions configOptions.solverLongTimeout do - let lemmas ← formulasToAutoLemmas formulas (includeInSetOfSupport := true) + let lemmas ← formulasToAutoLemmas formulas.toList (includeInSetOfSupport := true) -- Calling `Auto.unfoldConstAndPreprocessLemma` is an essential step for the monomorphization procedure let lemmas ← tryCatchRuntimeEx diff --git a/Hammer/SingleRuleTac.lean b/Hammer/SingleRuleTac.lean index 3323704..f27aa4e 100644 --- a/Hammer/SingleRuleTac.lean +++ b/Hammer/SingleRuleTac.lean @@ -64,10 +64,17 @@ def duperSingleRuleTac : SingleRuleTac := λ input => do | none => some (fact, proof, params, isFromGoal, none) | some stx => some (fact, proof, params, isFromGoal, some s!"{stx}") ) - let formulas := (formulas.map (fun (fact, proof, params, isFromGoal, stxOpt) => (fact, proof, params, isFromGoal, some stxOpt))) ++ lctxFormulas + /- It is important that `lctxFormulas` (which includes assumptions from `goalDecls`) comes before the `formulas` produced + by premise selection. This is to maintain the implicit invariant that the most important/relevant lemmas come first. + + This implicit invariant is important because `Auto.Monomorphization.saturate` processes lemmas in the order that they are + provided, and `auto.mono.saturationThreshold` can cause later lemmas to not be processed (`trace.auto.mono` shows when + Monomorphization's saturation threshold has been reached). -/ + let formulas := formulas.map (fun (fact, proof, params, isFromGoal, stxOpt) => (fact, proof, params, isFromGoal, some stxOpt)) + let formulas := lctxFormulas.append formulas -- The solver is called by Aesop as a subprocedure, so the short timeout is used withSolverOptions configOptions.solverShortTimeout do - let lemmas ← formulasWithStringsToAutoLemmas formulas (includeInSetOfSupport := true) + let lemmas ← formulasWithStringsToAutoLemmas formulas.toList (includeInSetOfSupport := true) -- Calling `Auto.unfoldConstAndPreprocessLemma` is an essential step for the monomorphization procedure let lemmas ← tryCatchRuntimeEx @@ -99,9 +106,9 @@ def duperSingleRuleTac : SingleRuleTac := λ input => do -- Build a Duper call using includeLCtx and each coreUserInputFact let stx ← if !coreFormulas.isEmpty && includeLCtx then - `(tactic| duper [*, $(coreFormulas.toArray),*] {preprocessing := full}) + `(tactic| duper [*, $coreFormulas,*] {preprocessing := full}) else if !coreFormulas.isEmpty && !includeLCtx then - `(tactic| duper [$(coreFormulas.toArray),*] {preprocessing := full}) + `(tactic| duper [$coreFormulas,*] {preprocessing := full}) else if coreFormulas.isEmpty && includeLCtx then `(tactic| duper [*] {preprocessing := full}) else -- coreFormulas.isEmpty && !includeLCtx diff --git a/Hammer/Tactic.lean b/Hammer/Tactic.lean index 1319f22..141d1cc 100644 --- a/Hammer/Tactic.lean +++ b/Hammer/Tactic.lean @@ -34,10 +34,10 @@ syntax (name := hammer) "hammer" (ppSpace "[" (term),* "]")? (ppSpace "{"Hammer. set_library_suggestions open Lean.LibrarySuggestions in Cloud.premiseSelector <|> sineQuaNonSelector.intersperse currentFile /-- The Duper subprocedure invoked by Aesop takes as input: - - `formulas` : `List (Expr × Expr × Array Name × Bool × String)` + - `formulas` : `Array (Expr × Expr × Array Name × Bool × String)` - `includeLCtx` : `Bool` - `configOptions` : `HammerCore.ConfigurationOptions` -/ -abbrev duperSubprocedureInputType := (List (Expr × Expr × Array Name × Bool × String)) × Bool × HammerCore.ConfigurationOptions +abbrev duperSubprocedureInputType := (Array (Expr × Expr × Array Name × Bool × String)) × Bool × HammerCore.ConfigurationOptions /-- An environment extension that holds the input intended for the Duper subprocedure invoked by Aesop (`HammerCore.duperSingleRuleTac`). -/ initialize duperSubprocedureInputExt : EnvExtension (Option duperSubprocedureInputType) ← @@ -80,8 +80,10 @@ def runAesopWithSubprocedures (duperPremises : Array Term) (addIdentStxs : TSynt let mut subprocedures : Array (TSyntax `Aesop.tactic_clause) := #[] -- Building `duperRuleTacStx` and adding it to `subprocedures` if !configOptions.disableDuper then + -- `withAllLCtx` is set to `false` in this `collectAssumptions` call because the relevant local context to collect is the + -- local context where the Duper subprocedure is called by Aesop, not the top main context where `hammer` is being called let formulas ← withDuperOptions $ collectAssumptions duperPremises false #[] - let formulas : List (Expr × Expr × Array Name × Bool × String) := + let formulas : Array (Expr × Expr × Array Name × Bool × String) := -- **TODO** This approach prohibits handling arguments that aren't disambiguated theorem names formulas.filterMap (fun (fact, proof, params, isFromGoal, stxOpt) => stxOpt.map (fun stx => (fact, proof, params, isFromGoal, stx.raw.getId.toString))) diff --git a/lake-manifest.json b/lake-manifest.json index f121644..8e3ba3b 100644 --- a/lake-manifest.json +++ b/lake-manifest.json @@ -15,10 +15,10 @@ "type": "git", "subDir": null, "scope": "", - "rev": "845ba5f42dd94a547ab38eae0ea3b15b9f4ac026", + "rev": "549f20200c5d12d6b387677129e0cdff8b6c6c80", "name": "Duper", "manifestFile": "lake-manifest.json", - "inputRev": "v4.33.0", + "inputRev": "549f20200c5d12d6b387677129e0cdff8b6c6c80", "inherited": false, "configFile": "lakefile.lean"}, {"url": "https://github.com/leanprover-community/aesop", diff --git a/lakefile.toml b/lakefile.toml index f7cbd7d..e594b2a 100644 --- a/lakefile.toml +++ b/lakefile.toml @@ -16,7 +16,7 @@ rev = "v4.33.0" [[require]] name = "«Duper»" git = "https://github.com/leanprover-community/duper.git" -rev = "v4.33.0" +rev = "549f20200c5d12d6b387677129e0cdff8b6c6c80" [[require]] name = "«Smt»" From ea137390d8843f1a888ea257433ee78bad874f82 Mon Sep 17 00:00:00 2001 From: JOSHCLUNE Date: Sun, 30 Aug 2026 20:20:14 -0400 Subject: [PATCH 6/7] Update lakefile.toml to depend on Monomorphization.saturate_refactor branch of Lean-auto --- lake-manifest.json | 48 +++++++++++++++++++++++----------------------- lakefile.toml | 10 +++++----- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/lake-manifest.json b/lake-manifest.json index 8e3ba3b..252044f 100644 --- a/lake-manifest.json +++ b/lake-manifest.json @@ -1,24 +1,24 @@ {"version": "1.2.0", "packagesDir": ".lake/packages", "packages": - [{"url": "https://github.com/ufmg-smite/lean-smt.git", + [{"url": "https://github.com/leanprover-community/duper.git", "type": "git", "subDir": null, "scope": "", - "rev": "1fbc840e0da8b26d23cb3ac9a652635f0d4bb86b", - "name": "Smt", + "rev": "66df5b18491b3460e6ddc3646a3dc8540a26a6d2", + "name": "Duper", "manifestFile": "lake-manifest.json", - "inputRev": "1fbc840e0da8b26d23cb3ac9a652635f0d4bb86b", + "inputRev": "66df5b18491b3460e6ddc3646a3dc8540a26a6d2", "inherited": false, "configFile": "lakefile.lean"}, - {"url": "https://github.com/leanprover-community/duper.git", + {"url": "https://github.com/ufmg-smite/lean-smt.git", "type": "git", "subDir": null, "scope": "", - "rev": "549f20200c5d12d6b387677129e0cdff8b6c6c80", - "name": "Duper", + "rev": "1fbc840e0da8b26d23cb3ac9a652635f0d4bb86b", + "name": "Smt", "manifestFile": "lake-manifest.json", - "inputRev": "549f20200c5d12d6b387677129e0cdff8b6c6c80", + "inputRev": "1fbc840e0da8b26d23cb3ac9a652635f0d4bb86b", "inherited": false, "configFile": "lakefile.lean"}, {"url": "https://github.com/leanprover-community/aesop", @@ -41,46 +41,46 @@ "inputRev": "v4.33.0", "inherited": false, "configFile": "lakefile.toml"}, - {"url": "https://github.com/leanprover-community/quote4", + {"url": "https://github.com/leanprover-community/batteries", "type": "git", "subDir": null, "scope": "", - "rev": "92c15be17b7caf78c2ad767ec40f89052d908d81", - "name": "quote4", + "rev": "4488d40d070b9700d4d5a6aa342f0d40c31b2a2d", + "name": "batteries", "manifestFile": "lake-manifest.json", "inputRev": "v4.33.0", "inherited": true, "configFile": "lakefile.toml"}, - {"url": "https://github.com/abdoo8080/lean-cvc5.git", + {"url": "https://github.com/leanprover-community/lean-auto.git", "type": "git", "subDir": null, "scope": "", - "rev": "7e3365990661b697ccb30e92d6912f4cc6589322", - "name": "cvc5", + "rev": "11528abcf2e530da983536049b8f762a68e6bd9c", + "name": "auto", "manifestFile": "lake-manifest.json", - "inputRev": "7e33659", + "inputRev": "11528abcf2e530da983536049b8f762a68e6bd9c", "inherited": true, "configFile": "lakefile.lean"}, - {"url": "https://github.com/leanprover-community/lean-auto.git", + {"url": "https://github.com/leanprover-community/quote4", "type": "git", "subDir": null, "scope": "", - "rev": "a66de83d4eea7f2811613d7b24ce87cf46bb3ac2", - "name": "auto", + "rev": "92c15be17b7caf78c2ad767ec40f89052d908d81", + "name": "quote4", "manifestFile": "lake-manifest.json", "inputRev": "v4.33.0", "inherited": true, - "configFile": "lakefile.lean"}, - {"url": "https://github.com/leanprover-community/batteries", + "configFile": "lakefile.toml"}, + {"url": "https://github.com/abdoo8080/lean-cvc5.git", "type": "git", "subDir": null, "scope": "", - "rev": "4488d40d070b9700d4d5a6aa342f0d40c31b2a2d", - "name": "batteries", + "rev": "7e3365990661b697ccb30e92d6912f4cc6589322", + "name": "cvc5", "manifestFile": "lake-manifest.json", - "inputRev": "v4.33.0", + "inputRev": "7e33659", "inherited": true, - "configFile": "lakefile.toml"}], + "configFile": "lakefile.lean"}], "name": "Hammer", "lakeDir": ".lake", "fixedToolchain": false} diff --git a/lakefile.toml b/lakefile.toml index e594b2a..1bb33ed 100644 --- a/lakefile.toml +++ b/lakefile.toml @@ -13,15 +13,15 @@ name = "«aesop»" git = "https://github.com/leanprover-community/aesop" rev = "v4.33.0" -[[require]] -name = "«Duper»" -git = "https://github.com/leanprover-community/duper.git" -rev = "549f20200c5d12d6b387677129e0cdff8b6c6c80" - [[require]] name = "«Smt»" git = "https://github.com/ufmg-smite/lean-smt.git" rev = "1fbc840e0da8b26d23cb3ac9a652635f0d4bb86b" # no_mathlib branch +[[require]] +name = "«Duper»" +git = "https://github.com/leanprover-community/duper.git" +rev = "66df5b18491b3460e6ddc3646a3dc8540a26a6d2" # Points towards Monomorphization.saturate_refactor branch of Lean-auto + [[lean_lib]] name = "Hammer" From d44c0eb0050db852a4a774c440dfe42ea0c06d06 Mon Sep 17 00:00:00 2001 From: JOSHCLUNE Date: Thu, 10 Sep 2026 18:27:24 -0400 Subject: [PATCH 7/7] Update Duper/Lean-SMT --- lake-manifest.json | 12 ++++++------ lakefile.toml | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lake-manifest.json b/lake-manifest.json index 252044f..a7ebcb4 100644 --- a/lake-manifest.json +++ b/lake-manifest.json @@ -5,20 +5,20 @@ "type": "git", "subDir": null, "scope": "", - "rev": "66df5b18491b3460e6ddc3646a3dc8540a26a6d2", + "rev": "34f6d228626adf1921f9fb314b0b6bd02c1a82b5", "name": "Duper", "manifestFile": "lake-manifest.json", - "inputRev": "66df5b18491b3460e6ddc3646a3dc8540a26a6d2", + "inputRev": "34f6d228626adf1921f9fb314b0b6bd02c1a82b5", "inherited": false, "configFile": "lakefile.lean"}, {"url": "https://github.com/ufmg-smite/lean-smt.git", "type": "git", "subDir": null, "scope": "", - "rev": "1fbc840e0da8b26d23cb3ac9a652635f0d4bb86b", + "rev": "e87b5c9c88eaf669f3cb85c73a6e75972a9c1018", "name": "Smt", "manifestFile": "lake-manifest.json", - "inputRev": "1fbc840e0da8b26d23cb3ac9a652635f0d4bb86b", + "inputRev": "e87b5c9c88eaf669f3cb85c73a6e75972a9c1018", "inherited": false, "configFile": "lakefile.lean"}, {"url": "https://github.com/leanprover-community/aesop", @@ -55,10 +55,10 @@ "type": "git", "subDir": null, "scope": "", - "rev": "11528abcf2e530da983536049b8f762a68e6bd9c", + "rev": "eb9c694863439fb55800228bc4c7babe42b089bf", "name": "auto", "manifestFile": "lake-manifest.json", - "inputRev": "11528abcf2e530da983536049b8f762a68e6bd9c", + "inputRev": "eb9c694863439fb55800228bc4c7babe42b089bf", "inherited": true, "configFile": "lakefile.lean"}, {"url": "https://github.com/leanprover-community/quote4", diff --git a/lakefile.toml b/lakefile.toml index 1bb33ed..f7e9b09 100644 --- a/lakefile.toml +++ b/lakefile.toml @@ -16,12 +16,12 @@ rev = "v4.33.0" [[require]] name = "«Smt»" git = "https://github.com/ufmg-smite/lean-smt.git" -rev = "1fbc840e0da8b26d23cb3ac9a652635f0d4bb86b" # no_mathlib branch +rev = "e87b5c9c88eaf669f3cb85c73a6e75972a9c1018" # no_mathlib branch [[require]] name = "«Duper»" git = "https://github.com/leanprover-community/duper.git" -rev = "66df5b18491b3460e6ddc3646a3dc8540a26a6d2" # Points towards Monomorphization.saturate_refactor branch of Lean-auto +rev = "34f6d228626adf1921f9fb314b0b6bd02c1a82b5" [[lean_lib]] name = "Hammer"