waterfall is an automated theorem prover for Lean 4, inspired by ACL2. It can automatically
discharge many goals that require induction, case analysis, and simplification, and lemma application,
and uses simp_all and grind as leaf solvers. It's designed to work well for the kinds of recursive definitions and proofs that arise in programming language semantics and program verification, but is generally applicable to any Lean goal.
The accumulator invariant for an in-order traversal, adapted from Software Foundations’ VFA SearchTree chapter:
theorem fast_elements_helper (t : Tree V) (acc : List (Nat × V)) :
fastElements t acc = elements t ++ acc := by
waterfall
waterfall selects an induction scheme and searches for the case proofs. The
definitions are found automatically in this module; associativity of ++ is already a standard
simplification rule. This proof also succeeds with waterfall (mode := .committed).
Definitions and complete proof
import waterfall
inductive Tree (V : Type) where
| empty
| node (left : Tree V) (key : Nat) (value : V) (right : Tree V)
def elements : Tree V → List (Nat × V)
| .empty => []
| .node left key value right => elements left ++ (key, value) :: elements right
def fastElements : Tree V → List (Nat × V) → List (Nat × V)
| .empty, acc => acc
| .node left key value right, acc =>
fastElements left ((key, value) :: fastElements right acc)
theorem fast_elements_helper (t : Tree V) (acc : List (Nat × V)) :
fastElements t acc = elements t ++ acc := by
waterfall
Usage
waterfall uses local hypotheses, Lean’s registered simp and grind rules, and definitions
referenced in the goal or hypotheses that originate in the current module. Local hypotheses support both
backward application and forward instantiation.
An optional rule list, as in waterfall [f, h], supplies additional definitions and facts to
simp_all, grind and backward theorem application. This is useful for imported definitions
and lemmas that are not registered for rewriting or instantiation. Supplied recursive
definitions also expose candidates for fun_induction and fun_cases. Library theorem
application does not search for arbitrary rewrite rules.
The compiled tutorial gives invocation examples; the SF examples show some complete proofs.
Proof scripts
waterfall? records the accepted proof path, renders it as tactic syntax, and validates the
replacement from the original checkpoint. Backtracked branches are omitted. Leaf calls to
simp_all and grind remain in the generated script.
Operations without a tactic rendering fall back to an explicit proof term. Rendering and re-elaboration add overhead beyond discovery; both modes and parallel execution support hints.
Search and commitment
The goal of waterfall is to combine Lean-style proofs with ACL2-style search. It will automatically consider induction, case analysis, and simplification, and if one of those fails, it will backtrack and try another.
The default policy performs depth-first search over complete proof continuations, with iterative deepening in structural cost and solver strength. A checkpoint includes all sibling obligations and their shared metavariable context, so even a successful local closure remains backtrackable until the entire continuation succeeds.
(mode := .committed) instead commits to the first locally progressing transition. It exhausts
non-inductive processing across the agenda before considering induction, with one permitted
return to the original conjecture at that boundary per trial. Stalled goals are reconsidered
after progress in a sibling, since shared metavariable assignments may have changed. Discarded
alternatives are not recovered by increasing the budget.
Options
waterfall (mode := .committed) (effort := 3000)
Both waterfall and waterfall? accept individual options or a structure such as (config := {mode := .search, effort := 3000}).
| Option | Default | Effect |
|---|---|---|
mode |
.search |
.committed disables backtracking after local progress. |
effort |
1000 |
Global budget for dispatched operations and charged restarts, including failed attempts. |
cpus |
1 |
Maximum concurrent depth/strength trials, sharing the total attempt and heartbeat budgets. |
attemptHeartbeats |
20000000 |
Base raw-heartbeat slice per operation, scaled by trial strength. |
lazy |
true |
Generate each candidate batch when reached. False enumerates all batches in a phase up front. |
deferChecks |
false |
True postpones applicability checks until a candidate is considered. |
report |
false |
true prints search statistics. |
Effort and time limits
effort controls both how such search is performed, and how much effort leaf tactics like grind apply. Effort is consumed by attempted proof plans, even if they fail and backtrack. The default of 1,000 is good for interactive use, but bigger budgets will be needed in some cases.
attemptHeartbeats is the base raw-heartbeat slice per operation, scaled by trial strength and
capped by the enclosing remaining allowance. Lean’s maxHeartbeats uses thousands of raw
heartbeats. These limits are independent of the attempt budget and of maxRecDepth.
Parallel execution
waterfall (cpus := 4) (effort := 3000)
Specifying cpus allows multiple paths to be explored concurrently. Each worker shares the total budget, and the first successful proof wins. cpus := 1 selects sequential operation.
Software Foundations
Across an agent-ported version of Software Foundations, the results for waterfall are:
| Volume | Goals | Baseline | Search | Committed |
|---|---|---|---|---|
| LF | 937 | 659 | 740 | 739 |
| PLF | 744 | 230 | 325 | 354 |
| VFA | 509 | 315 | 390 | 354 |
| Total | 2,190 | 1,204 | 1,455 | 1,447 |
The baseline combines separate runs of simp_all, grind, and one structural induction with
simp_all/grind leaves. Induction tries eligible variables with and without generalization.
Lake package
waterfall 0.1.0 is available from samth/waterfall.
A Lean project's lakefile.toml can depend on the Git repository:
[[require]]
name = "waterfall"
git = "https://github.com/samth/waterfall.git"
rev = "main"
The package depends only on Lean, targets 4.33.1, and is also tested on 4.30.0. The consumer
must use a matching toolchain. Its tactics are exported by import waterfall.
Lake records the resolved Git commit in lake-manifest.json.