Skip to content

Commit ecb8659

Browse files
kim-emclaude
andcommitted
feat(Query): query complexity framework with sorting examples
Implements Sebastian Graf's unified approach to query complexity, combining the strengths of leanprover#372 (explicit Prog/FreeM query types) and leanprover#376 (monad-parametric approach). Key design: programs are `Prog Q α` (free monad over query type Q), oracle is supplied *after* the program produces its query plan, giving anti-cheating guarantees for both upper and lower bounds. New files: - `Query/Prog.lean`: Core `Prog` type, `eval`, `queriesOn`, simp lemmas - `Query/Bounds.lean`: `UpperBound` and `LowerBound` definitions - `Query/Sort/LEQuery.lean`: Comparison query type for sorting - `Query/Sort/IsSort.lean`: Correctness specification for comparison sorts - `Query/Sort/Insertion/{Defs,Lemmas}.lean`: Insertion sort with O(n²) upper bound - `Query/Sort/Merge/{Defs,Lemmas}.lean`: Merge sort with correctness proofs Insertion sort is fully proven (correctness + n² upper bound). Merge sort has full correctness proofs; the n·⌈log₂ n⌉ upper bound is stated but left as sorry (the clog recurrence proof is nontrivial). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 11957b8 commit ecb8659

9 files changed

Lines changed: 723 additions & 0 deletions

File tree

Cslib.lean

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
module -- shake: keep-all
22

33
public import Cslib.Algorithms.Lean.MergeSort.MergeSort
4+
public import Cslib.Algorithms.Lean.Query.Bounds
5+
public import Cslib.Algorithms.Lean.Query.Prog
6+
public import Cslib.Algorithms.Lean.Query.Sort.Insertion.Defs
7+
public import Cslib.Algorithms.Lean.Query.Sort.Insertion.Lemmas
8+
public import Cslib.Algorithms.Lean.Query.Sort.IsSort
9+
public import Cslib.Algorithms.Lean.Query.Sort.LEQuery
10+
public import Cslib.Algorithms.Lean.Query.Sort.Merge.Defs
11+
public import Cslib.Algorithms.Lean.Query.Sort.Merge.Lemmas
412
public import Cslib.Algorithms.Lean.TimeM
513
public import Cslib.Computability.Automata.Acceptors.Acceptor
614
public import Cslib.Computability.Automata.Acceptors.OmegaAcceptor
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/-
2+
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Kim Morrison, Sebastian Graf
5+
-/
6+
module
7+
8+
public import Cslib.Algorithms.Lean.Query.Prog
9+
10+
/-! # Upper and Lower Bounds for Query Complexity
11+
12+
Definitions of upper and lower bounds on the number of queries a program makes,
13+
quantified over oracles. The oracle is supplied *after* the program produces its query
14+
plan (the `Prog` tree), so implementations cannot cheat.
15+
-/
16+
17+
open Cslib.Query
18+
19+
public section
20+
21+
namespace Cslib.Query
22+
23+
/-- Upper bound: for all oracles, inputs of size ≤ n make at most `bound n` queries. -/
24+
@[expose] def UpperBound (prog : α → Prog Q β)
25+
(size : α → Nat) (bound : Nat → Nat) : Prop :=
26+
∀ (oracle : {ι : Type} → Q ι → ι) (n : Nat) (x : α),
27+
size x ≤ n → (prog x).queriesOn oracle ≤ bound n
28+
29+
/-- Lower bound: for every size n, there exists an input and oracle
30+
making the program perform ≥ `bound n` queries. -/
31+
@[expose] def LowerBound (prog : α → Prog Q β)
32+
(size : α → Nat) (bound : Nat → Nat) : Prop :=
33+
∀ (n : Nat), ∃ (x : α), size x ≤ n ∧
34+
∃ (oracle : {ι : Type} → Q ι → ι), bound n ≤ (prog x).queriesOn oracle
35+
36+
end Cslib.Query
37+
38+
end -- public section
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/-
2+
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Kim Morrison, Sebastian Graf
5+
-/
6+
module
7+
8+
public import Cslib.Foundations.Control.Monad.Free
9+
10+
/-! # Prog: Programs as Free Monads over Query Types
11+
12+
`Prog Q α` is an alias for `FreeM Q α`, representing a program that makes queries of type `Q`
13+
and returns a result of type `α`. A query type `Q : Type → Type` maps each query to its
14+
response type.
15+
16+
The key operations are:
17+
- `Prog.eval oracle p`: evaluate `p` by answering each query using `oracle`
18+
- `Prog.queriesOn oracle p`: count the queries along the oracle-determined path
19+
20+
Because the oracle is supplied *after* the program produces its query plan (the `Prog` tree),
21+
a sound implementation of `prog` has no way to "guess" what the oracle would respond.
22+
This is the foundation of the anti-cheating guarantee for both upper and lower bounds.
23+
-/
24+
25+
open Cslib
26+
27+
public section
28+
29+
namespace Cslib.Query
30+
31+
/-- A program that makes queries of type `Q` and returns a result of type `α`.
32+
This is `FreeM Q α`, the free monad over the query type. -/
33+
abbrev Prog (Q : TypeType) (α : Type) := FreeM Q α
34+
35+
namespace Prog
36+
37+
variable {Q : TypeType} {α β : Type}
38+
39+
/-- Evaluate a program by answering each query using `oracle`. -/
40+
@[expose] def eval (oracle : {ι : Type} → Q ι → ι) : Prog Q α → α
41+
| .pure a => a
42+
| .liftBind op cont => eval oracle (cont (oracle op))
43+
44+
/-- Count the number of queries along the path determined by `oracle`. -/
45+
@[expose] def queriesOn (oracle : {ι : Type} → Q ι → ι) : Prog Q α → Nat
46+
| .pure _ => 0
47+
| .liftBind op cont => 1 + queriesOn oracle (cont (oracle op))
48+
49+
-- Simp lemmas for eval
50+
51+
@[simp] theorem eval_pure (oracle : {ι : Type} → Q ι → ι) (a : α) :
52+
eval oracle (.pure a : Prog Q α) = a := rfl
53+
54+
@[simp] theorem eval_liftBind (oracle : {ι : Type} → Q ι → ι)
55+
{ι : Type} (op : Q ι) (cont : ι → Prog Q α) :
56+
eval oracle (.liftBind op cont) = eval oracle (cont (oracle op)) := rfl
57+
58+
@[simp] theorem eval_bind (oracle : {ι : Type} → Q ι → ι)
59+
(t : Prog Q α) (f : α → Prog Q β) :
60+
eval oracle (t.bind f) = eval oracle (f (eval oracle t)) := by
61+
induction t with
62+
| pure a => rfl
63+
| liftBind op cont ih => exact ih (oracle op)
64+
65+
-- Simp lemmas for queriesOn
66+
67+
@[simp] theorem queriesOn_pure (oracle : {ι : Type} → Q ι → ι) (a : α) :
68+
queriesOn oracle (.pure a : Prog Q α) = 0 := rfl
69+
70+
@[simp] theorem queriesOn_liftBind (oracle : {ι : Type} → Q ι → ι)
71+
{ι : Type} (op : Q ι) (cont : ι → Prog Q α) :
72+
queriesOn oracle (.liftBind op cont) = 1 + queriesOn oracle (cont (oracle op)) := rfl
73+
74+
@[simp] theorem queriesOn_bind (oracle : {ι : Type} → Q ι → ι)
75+
(t : Prog Q α) (f : α → Prog Q β) :
76+
queriesOn oracle (t.bind f) =
77+
queriesOn oracle t + queriesOn oracle (f (eval oracle t)) := by
78+
induction t with
79+
| pure a => simp [FreeM.bind]
80+
| liftBind op cont ih =>
81+
simp only [FreeM.bind, queriesOn_liftBind, eval_liftBind, ih (oracle op)]
82+
omega
83+
84+
end Prog
85+
86+
end Cslib.Query
87+
88+
end -- public section
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/-
2+
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Kim Morrison, Sebastian Graf
5+
-/
6+
module
7+
8+
public import Cslib.Algorithms.Lean.Query.Sort.LEQuery
9+
10+
/-! # Insertion Sort as a Query Program
11+
12+
Insertion sort implemented as a `Prog (LEQuery α)`, making all comparison queries explicit.
13+
-/
14+
15+
open Cslib.Query
16+
17+
public section
18+
19+
namespace Cslib.Query
20+
21+
/-- Insert `x` into a sorted list using comparison queries. -/
22+
@[expose] def orderedInsert (x : α) : List α → Prog (LEQuery α) (List α)
23+
| [] => pure [x]
24+
| y :: ys => do
25+
let le ← LEQuery.ask x y
26+
if le then
27+
pure (x :: y :: ys)
28+
else do
29+
let rest ← orderedInsert x ys
30+
pure (y :: rest)
31+
32+
/-- Sort a list using insertion sort with comparison queries. -/
33+
@[expose] def insertionSort : List α → Prog (LEQuery α) (List α)
34+
| [] => pure []
35+
| x :: xs => do
36+
let sorted ← insertionSort xs
37+
orderedInsert x sorted
38+
39+
end Cslib.Query
40+
41+
end -- public section
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/-
2+
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Kim Morrison, Sebastian Graf
5+
-/
6+
module
7+
8+
public import Cslib.Algorithms.Lean.Query.Bounds
9+
public import Cslib.Algorithms.Lean.Query.Sort.IsSort
10+
public import Cslib.Algorithms.Lean.Query.Sort.Insertion.Defs
11+
import Mathlib.Data.List.Sort
12+
import Mathlib.Tactic.Ring
13+
public import Mathlib.Algebra.Group.Defs
14+
15+
/-! # Insertion Sort: Correctness and Upper Bound
16+
17+
Proofs that `insertionSort` is a correct comparison sort and uses at most `n²` queries.
18+
All proofs are by plain equational reasoning on `Prog.eval` and `Prog.queriesOn`.
19+
-/
20+
21+
open Cslib.Query
22+
23+
public section
24+
25+
namespace Cslib.Query
26+
27+
variable {α : Type}
28+
29+
-- ## Evaluation simp lemmas for orderedInsert
30+
31+
@[simp] theorem eval_orderedInsert_nil (oracle : {ι : Type} → LEQuery α ι → ι) (x : α) :
32+
(orderedInsert x ([] : List α)).eval oracle = [x] := by
33+
simp [orderedInsert]
34+
35+
@[simp] theorem eval_orderedInsert_cons (oracle : {ι : Type} → LEQuery α ι → ι) (x y : α)
36+
(ys : List α) :
37+
(orderedInsert x (y :: ys)).eval oracle =
38+
if oracle (.le x y) then x :: y :: ys
39+
else y :: (orderedInsert x ys).eval oracle := by
40+
simp [orderedInsert, LEQuery.ask]
41+
split <;> simp_all
42+
43+
-- ## Evaluation simp lemmas for insertionSort
44+
45+
@[simp] theorem eval_insertionSort_nil (oracle : {ι : Type} → LEQuery α ι → ι) :
46+
(insertionSort (α := α) []).eval oracle = [] := by
47+
simp [insertionSort]
48+
49+
@[simp] theorem eval_insertionSort_cons (oracle : {ι : Type} → LEQuery α ι → ι)
50+
(x : α) (xs : List α) :
51+
(insertionSort (x :: xs)).eval oracle =
52+
(orderedInsert x ((insertionSort xs).eval oracle)).eval oracle := by
53+
simp [insertionSort]
54+
55+
-- ## Permutation proofs
56+
57+
theorem orderedInsert_perm (oracle : {ι : Type} → LEQuery α ι → ι) (x : α) (xs : List α) :
58+
((orderedInsert x xs).eval oracle).Perm (x :: xs) := by
59+
induction xs with
60+
| nil => simp
61+
| cons y ys ih =>
62+
simp only [eval_orderedInsert_cons]
63+
split
64+
· exact List.Perm.refl _
65+
· exact (List.Perm.cons _ ih).trans (List.Perm.swap _ _ _)
66+
67+
theorem insertionSort_perm (oracle : {ι : Type} → LEQuery α ι → ι) (xs : List α) :
68+
((insertionSort xs).eval oracle).Perm xs := by
69+
induction xs with
70+
| nil => simp
71+
| cons x xs ih =>
72+
simp only [eval_insertionSort_cons]
73+
exact (orderedInsert_perm oracle x _).trans (List.Perm.cons _ ih)
74+
75+
-- ## Sortedness proofs
76+
77+
theorem orderedInsert_sorted
78+
(r : α → α → Prop) [DecidableRel r] [Std.Total r] [IsTrans α r]
79+
(oracle : {ι : Type} → LEQuery α ι → ι)
80+
(horacle : ∀ a b, oracle (.le a b) = decide (r a b))
81+
(x : α) (xs : List α) (hxs : xs.Pairwise r) :
82+
((orderedInsert x xs).eval oracle).Pairwise r := by
83+
induction xs with
84+
| nil => simp
85+
| cons y ys ih =>
86+
simp only [eval_orderedInsert_cons, horacle]
87+
split
88+
next h =>
89+
have hle : r x y := by simpa [decide_eq_true_eq] using h
90+
exact List.pairwise_cons.mpr ⟨fun z hz =>
91+
match List.mem_cons.mp hz with
92+
| .inl h => h ▸ hle
93+
| .inr h => _root_.trans hle (List.rel_of_pairwise_cons hxs h), hxs⟩
94+
next h =>
95+
have hle : ¬ r x y := by simpa [decide_eq_true_eq] using h
96+
have hyx : r y x := (Std.Total.total y x).resolve_right hle
97+
have ih' := ih hxs.of_cons
98+
have hperm := orderedInsert_perm oracle x ys
99+
exact List.pairwise_cons.mpr ⟨fun z hz =>
100+
match List.mem_cons.mp (hperm.mem_iff.mp hz) with
101+
| .inl h => h ▸ hyx
102+
| .inr h => List.rel_of_pairwise_cons hxs h, ih'⟩
103+
104+
theorem insertionSort_sorted
105+
(r : α → α → Prop) [DecidableRel r] [Std.Total r] [IsTrans α r]
106+
(oracle : {ι : Type} → LEQuery α ι → ι)
107+
(horacle : ∀ a b, oracle (.le a b) = decide (r a b))
108+
(xs : List α) :
109+
((insertionSort xs).eval oracle).Pairwise r := by
110+
induction xs with
111+
| nil => simp
112+
| cons x xs ih =>
113+
simp only [eval_insertionSort_cons]
114+
exact orderedInsert_sorted r oracle horacle x _ ih
115+
116+
-- ## Query count proofs
117+
118+
theorem orderedInsert_queriesOn_le (oracle : {ι : Type} → LEQuery α ι → ι)
119+
(x : α) (xs : List α) :
120+
(orderedInsert x xs).queriesOn oracle ≤ xs.length := by
121+
induction xs with
122+
| nil => simp [orderedInsert]
123+
| cons y ys ih =>
124+
unfold orderedInsert LEQuery.ask
125+
simp
126+
split
127+
· simp_all
128+
· simp_all; omega
129+
130+
theorem insertionSort_queriesOn_le (oracle : {ι : Type} → LEQuery α ι → ι)
131+
(xs : List α) :
132+
(insertionSort xs).queriesOn oracle ≤ xs.length ^ 2 := by
133+
induction xs with
134+
| nil => simp [insertionSort]
135+
| cons x xs ih =>
136+
have hq : (insertionSort (x :: xs)).queriesOn oracle =
137+
(insertionSort xs).queriesOn oracle +
138+
(orderedInsert x ((insertionSort xs).eval oracle)).queriesOn oracle := by
139+
simp [insertionSort]
140+
rw [hq]
141+
have hlen : ((insertionSort xs).eval oracle).length = xs.length :=
142+
(insertionSort_perm oracle xs).length_eq
143+
have hord := orderedInsert_queriesOn_le oracle x ((insertionSort xs).eval oracle)
144+
rw [hlen] at hord
145+
have h1 := Nat.add_le_add ih hord
146+
have hpow : xs.length ^ 2 + xs.length ≤ (xs.length + 1) ^ 2 := by
147+
have : (xs.length + 1) ^ 2 = xs.length ^ 2 + 2 * xs.length + 1 := by ring
148+
omega
149+
simp only [List.length_cons]
150+
exact Nat.le_trans h1 hpow
151+
152+
-- ## UpperBound and IsSort instances
153+
154+
public theorem insertionSort_upperBound :
155+
UpperBound (insertionSort (α := α)) List.length (· ^ 2) := by
156+
intro oracle n x hle
157+
exact Nat.le_trans (insertionSort_queriesOn_le oracle x)
158+
(Nat.pow_le_pow_left hle 2)
159+
160+
public theorem insertionSort_isSort : IsSort (insertionSort (α := α)) where
161+
perm xs oracle := insertionSort_perm oracle xs
162+
sorted := by
163+
intro xs oracle r _ _ _ horacle
164+
exact insertionSort_sorted r oracle horacle xs
165+
166+
end Cslib.Query
167+
168+
end -- public section
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/-
2+
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Kim Morrison, Sebastian Graf
5+
-/
6+
module
7+
8+
public import Cslib.Algorithms.Lean.Query.Sort.LEQuery
9+
10+
/-! # IsSort: Specification for Comparison Sorts
11+
12+
`IsSort sort` asserts that `sort` is a correct comparison sort when viewed as a `Prog`
13+
over `LEQuery α`. Correctness means: for any oracle, the result is a permutation of the
14+
input; and for any oracle implementing a total order, the result is sorted.
15+
16+
Because the oracle is supplied *after* the program produces its query plan, the sort
17+
cannot cheat by using any built-in ordering on the element type.
18+
-/
19+
20+
open Cslib.Query
21+
22+
public section
23+
24+
namespace Cslib.Query
25+
26+
/-- A `Prog`-based function is a correct comparison sort if it always produces a permutation
27+
of its input, and produces a sorted list when the oracle implements a total order. -/
28+
structure IsSort (sort : List α → Prog (LEQuery α) (List α)) : Prop where
29+
/-- The sort produces a permutation of its input, for any oracle. -/
30+
perm : ∀ (xs : List α) (oracle : {ι : Type} → LEQuery α ι → ι),
31+
((sort xs).eval oracle).Perm xs
32+
/-- The sort produces a sorted list, when the oracle implements a total order. -/
33+
sorted : ∀ (xs : List α) (oracle : {ι : Type} → LEQuery α ι → ι)
34+
(r : α → α → Prop) [DecidableRel r] [Std.Total r] [IsTrans α r]
35+
(_ : ∀ a b, oracle (.le a b) = decide (r a b)),
36+
((sort xs).eval oracle).Pairwise r
37+
38+
end Cslib.Query
39+
40+
end -- public section

0 commit comments

Comments
 (0)