lean4-htt/tests/lean/run/grind_heapsort.lean
Joachim Breitner e575736cae
feat: fun_induction to unfold function application in the goal (#8104)
This PR makes `fun_induction` and `fun_cases` (try to) unfold the
function application of interest in the goal. The old behavior can be
enabled with `set_option tactic.fun_induction.unfolding false`. For
`fun_cases` this does not work yet when the function’s result type
depends on one of the arguments, see issue #8296.
2025-05-13 09:37:39 +00:00

76 lines
2.4 KiB
Text

import Lean
set_option grind.warning false
/-
Use `grind` as one of the tactics for array-element access and termination proofs.
-/
macro_rules | `(tactic| get_elem_tactic_trivial) => `(tactic| grind)
/-
Note: We disable model-based theory combination (-mbtc) here because `grind` can
exhaust heartbeats when exploring certain "bad" termination checker scenarios.
For example, in `heapsort.go`, the termination checker attempts `termination_by a.size` first,
and `grind` will perform excessive case splits, consuming heartbeats too quickly.
BTW, the error message must be improved. It just says heartbeats exhausted :(
-/
macro_rules | `(tactic| decreasing_trivial) => `(tactic| grind -mbtc)
-- TODO: annotate the library
attribute [grind] Array.size_swap
abbrev leftChild (i : Nat) := 2*i + 1
abbrev parent (i : Nat) := (i - 1) / 2
-- TODO: Generalize `siftDown` to arbitrary element types (not just `Int`)
-- once other issues are resolved.
def siftDown (a : Array Int) (root : Nat) (e : Nat) (h : e ≤ a.size := by grind) : Array Int :=
-- Remark: It is annoying to have to write `if _ : ...` to make sure the hypothesis is available
-- while type-checking the if-then-else body.
if _ : leftChild root < e then
let child := leftChild root
-- Remark: it would be nice to have a `p ∧ q` where we can assume `p` while type checking `q`
-- I simulated it using a nested `if-then-else`
let child := if _ : child+1 < e then
if a[child] < a[child + 1] then
child + 1
else
child
else
child
if a[root] < a[child] then
let a := a.swap root child
siftDown a child e
else
a
else
a
termination_by e - root
@[grind] theorem siftDown_size : (siftDown a root e h).size = a.size := by
fun_induction siftDown <;> grind
def heapify (a : Array Int) : Array Int :=
let start := parent (a.size - 1) + 1
go a start
where
go (a : Array Int) (start : Nat) : Array Int :=
match start with
| 0 => a
| start+1 => go (siftDown a start a.size) start
def heapsort (a : Array Int) : Array Int :=
let a := heapify a
go a a.size
where
go (a : Array Int) (e : Nat) (h : e ≤ a.size := by grind) : Array Int :=
-- Another annoying `_ :`
if _ : e > 1 then
let e := e - 1
let a := a.swap e 0
go (siftDown a 0 e) e
else
a
/-- info: #[0, 1, 2, 4, 5, 7, 8, 10] -/
#guard_msgs (info) in
#eval heapsort #[4, 1, 0, 5, 7, 10, 8, 2]