Skip to content

Commit 3c2ab0f

Browse files
authored
feat: model-based theory combination tactic and action (#10950)
This PR adds the `mbtc` tactic to the `grind` interactive mode. It implements model-based theory combination. It also ensures `finish?` is capable of generating it.
1 parent 1643fd7 commit 3c2ab0f

File tree

5 files changed

+73
-7
lines changed

5 files changed

+73
-7
lines changed

src/Init/Grind/Interactive.lean

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,5 +225,10 @@ Proves `<term>` using the current `grind` state and default search strategy.
225225
-/
226226
syntax (name := haveSilent) "have" (ppSpace ident)? ppSpace ": " term : grind
227227

228+
/--
229+
Adds new case-splits using model-based theory combination.
230+
-/
231+
syntax (name := mbtc) "mbtc" : grind
232+
228233
end Grind
229234
end Lean.Parser.Tactic

src/Lean/Elab/Tactic/Grind/BuiltinTactic.lean

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,4 +429,13 @@ where
429429
let options ← Elab.elabSetOption stx[1] stx[3]
430430
withOptions (fun _ => options) do evalGrindTactic stx[5]
431431

432+
@[builtin_grind_tactic mbtc] def elabMBTC : GrindTactic := fun _ => do
433+
liftGoalM do
434+
discard <| Solvers.check
435+
if (← get).inconsistent then
436+
return ()
437+
let progress ← Solvers.mbtc
438+
unless progress do
439+
throwError "`mbtc` tactic failed to generate new candidate case-splits."
440+
432441
end Lean.Elab.Tactic.Grind

src/Lean/Elab/Tactic/Grind/Trace.lean

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ def withTracing (x : GrindTacticM α) : GrindTacticM α := do
2020

2121
def mkFinish (maxIterations : Nat) : IO Action := do
2222
let solvers ← Solvers.mkAction
23-
return Action.checkTactic >> (Action.done <|> solvers <|> Action.instantiate <|> Action.splitNext).loop maxIterations
23+
let step : Action := Action.done <|> solvers <|> Action.instantiate <|> Action.splitNext <|> Action.mbtc
24+
return Action.checkTactic (warnOnly := true) >> step.loop maxIterations
2425

2526
def maxIterations := 1000 -- **TODO**: Add option
2627

src/Lean/Meta/Tactic/Grind/Action.lean

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ Appends a new tactic syntax to a successful result.
225225
Used by leaf actions to record the tactic that produced progress.
226226
If `(← getConfig).trace` is `false`, it just returns `r`.
227227
-/
228-
def concatTactic (r : ActionResult) (mk : GrindM (TSyntax `grind)) : GrindM ActionResult := do
228+
def concatTactic (r : ActionResult) (mk : GrindM TGrind) : GrindM ActionResult := do
229229
if (← getConfig).trace then
230230
match r with
231231
| .closed seq =>
@@ -236,7 +236,7 @@ def concatTactic (r : ActionResult) (mk : GrindM (TSyntax `grind)) : GrindM Acti
236236
return r
237237

238238
/-- Returns `.closed [← mk]` if tracing is enabled, and `.closed []` otherwise. -/
239-
def closeWith (mk : GrindM (TSyntax `grind)) : GrindM ActionResult := do
239+
def closeWith (mk : GrindM TGrind) : GrindM ActionResult := do
240240
if (← getConfig).trace then
241241
return .closed [(← mk)]
242242
else
@@ -247,7 +247,7 @@ A terminal action which closes the goal or not.
247247
This kind of action may make progress, but we only include `mkTac` into the resulting tactic sequence
248248
if it closed the goal.
249249
-/
250-
def terminalAction (check : GoalM Bool) (mkTac : GrindM (TSyntax `grind)) : Action := fun goal kna kp => do
250+
def terminalAction (check : GoalM Bool) (mkTac : GrindM TGrind) : Action := fun goal kna kp => do
251251
let (progress, goal') ← GoalM.run goal check
252252
if progress then
253253
if goal'.inconsistent then
@@ -282,21 +282,26 @@ def checkSeqAt (s? : Option SavedState) (goal : Goal) (seq : List TGrind) : Grin
282282
/--
283283
Helper action that checks whether the resulting tactic script produced by its continuation
284284
can close the original goal.
285+
If `warnOnly = true`, just generates a warning message instead of an error
285286
-/
286-
def checkTactic : Action := fun goal _ kp => do
287+
def checkTactic (warnOnly : Bool) : Action := fun goal _ kp => do
287288
let s ← saveStateIfTracing
288289
let r ← kp goal
289290
match r with
290291
| .closed seq =>
291292
unless (← checkSeqAt s goal seq) do
292-
throwError "generated tactic cannot close the goal{indentD (← mkGrindNext seq)}\nInitial goal\n{goal.mvarId}"
293+
let m := m!"generated tactic cannot close the goal{indentD (← mkGrindNext seq)}\nInitial goal\n{goal.mvarId}"
294+
if warnOnly then
295+
logWarning m
296+
else
297+
throwError m
293298
return r
294299
| _ => return r
295300

296301
/--
297302
Helper action for satellite solvers that use `CheckResult`.
298303
-/
299-
def solverAction (check : GoalM CheckResult) (mkTac : GrindM (TSyntax `grind)) : Action := fun goal kna kp => do
304+
def solverAction (check : GoalM CheckResult) (mkTac : GrindM TGrind) : Action := fun goal kna kp => do
300305
let saved? ← saveStateIfTracing
301306
let (result, goal') ← GoalM.run goal check
302307
match result with
@@ -327,6 +332,24 @@ def solverAction (check : GoalM CheckResult) (mkTac : GrindM (TSyntax `grind)) :
327332
kp goal'
328333
| .closed => closeWith mkTac
329334

335+
def mbtc : Action := fun goal kna kp => do
336+
let saved? ← saveStateIfTracing
337+
let (progress, goal') ← GoalM.run goal Solvers.mbtc
338+
if progress then
339+
if (← getConfig).trace then
340+
match (← kp goal') with
341+
| .closed seq =>
342+
if (← checkSeqAt saved? goal seq) then
343+
return .closed seq
344+
else
345+
let tac ← `(grind| mbtc)
346+
return .closed (tac :: seq)
347+
| r => return r
348+
else
349+
kp goal'
350+
else
351+
kna goal'
352+
330353
section
331354
/-!
332355
Some sanity check properties.

tests/lean/run/grind_finish_trace.lean

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,31 @@ info: Try this:
134134
example (a : Array (BitVec 64)) (i : Nat) (v : BitVec 64)
135135
: (_ : i < a.size) → (_ : i + 1 < a.size) → (a.set i v)[i+1] + a[i+1] = 2*a[i+1] := by
136136
grind => finish?
137+
138+
/--
139+
info: Try this:
140+
[apply]
141+
mbtc
142+
cases #a6c8
143+
-/
144+
#guard_msgs in
145+
example (f : Nat → Nat) (x : Nat)
146+
: x ≠ 0 → x ≤ 1 → f x = 2 → f 1 = 2 := by
147+
grind => finish?
148+
149+
/--
150+
info: Try this:
151+
[apply]
152+
mbtc
153+
cases #beb4
154+
-/
155+
#guard_msgs in
156+
example (f : Int → Int → Int) (x y : Int)
157+
: 0 ≤ x → x ≠ 0 → x ≤ 1 → f x y = 2 → f 1 y = 2 := by
158+
grind => finish?
159+
160+
example (f : Int → Int → Int) (x y : Int)
161+
: 0 ≤ x → x ≠ 0 → x ≤ 1 → f x y = 2 → f 1 y = 2 := by
162+
grind =>
163+
-- We can use `have` to golf proofs using `mbtc` and `cases`
164+
have : x = 1

0 commit comments

Comments
 (0)