Skip to content

feat(Query): query complexity framework with sorting examples#401

Draft
kim-em wants to merge 3 commits intoleanprover:mainfrom
kim-em:combined-query-complexity
Draft

feat(Query): query complexity framework with sorting examples#401
kim-em wants to merge 3 commits intoleanprover:mainfrom
kim-em:combined-query-complexity

Conversation

@kim-em
Copy link
Collaborator

@kim-em kim-em commented Mar 5, 2026

This PR implements Sebastian Graf's unified approach to query complexity (discussed in the CSLib Algorithm frameworks thread), combining the strengths of #372 (explicit Prog/FreeM query types) and #376 (monad-parametric approach).

Programs are Prog Q α (free monad over query type Q), and the oracle is supplied after the program produces its query plan — giving anti-cheating guarantees for both upper and lower bounds. No WP/Hoare triple machinery is needed: correctness is just equations about Prog.eval oracle, and cost is just equations about Prog.queriesOn oracle.

This provides an alternative to the TimeM-based cost analysis already in the repo: here query counting is structural (derived from the Prog tree) rather than annotation-based.

New files

File Contents
Query/Prog.lean Core Prog type, eval, queriesOn, simp lemmas
Query/Bounds.lean UpperBound and LowerBound definitions
Query/QueryTree.lean Decision trees with fixed response type, for lower bound proofs
Query/Sort/LEQuery.lean Comparison query type for sorting
Query/Sort/IsSort.lean IsSort correctness specification
Query/Sort/Insertion/{Defs,Lemmas}.lean Insertion sort: correctness + O(n²) upper bound
Query/Sort/Merge/{Defs,Lemmas}.lean Merge sort: correctness + n·⌈log₂ n⌉ upper bound
Query/Sort/QueryTree.lean Prog-to-QueryTree bridge + pigeonhole depth lemma
Query/Sort/LowerBound.lean Any correct comparison sort needs ≥ ⌈log₂(n!)⌉ queries

Results

  • Insertion sort: correctness (permutation + sortedness), n² upper bound, IsSort instance
  • Merge sort: correctness, n·⌈log₂ n⌉ upper bound, IsSort instance
  • Lower bound: any IsSort on an infinite type makes ≥ ⌈log₂(n!)⌉ queries. The proof constructs n! distinct total orders via permutations of embedded elements, shows they force distinct sorted outputs, then applies an adversarial pigeonhole argument on QueryTree depth.

🤖 Prepared with Claude Code

@kim-em kim-em force-pushed the combined-query-complexity branch 2 times, most recently from dc724df to 8371c12 Compare March 5, 2026 08:34
Copy link
Contributor

@Shreyas4991 Shreyas4991 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check author lists.

I didn't intend to GitHub-approve it. I wanted to click "Comment"

/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sorrachai Yingchareonthawornchai, Kim Morrison, Sebastian Graf, Shreyas Srinivas
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorrachai is not an author on this file, only on the TimeM files. Further on all prog files I am first author

@kim-em kim-em force-pushed the combined-query-complexity branch from 8371c12 to dbb9e41 Compare March 5, 2026 08:46
Add a framework for proving upper and lower bounds on query complexity
of comparison-based algorithms, using `Prog` (free monad over query
types) with oracle-parametric evaluation and structural query counting.

Results:
- Insertion sort: correctness + O(n²) upper bound
- Merge sort: correctness + n·⌈log₂ n⌉ upper bound
- Lower bound: any correct comparison sort on an infinite type
  needs ≥ ⌈log₂(n!)⌉ queries (via adversarial pigeonhole on
  QueryTree depth)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@kim-em kim-em force-pushed the combined-query-complexity branch from dbb9e41 to 9acdbbd Compare March 5, 2026 08:50
Add `Prog.cost`, a weighted generalization of `Prog.queriesOn` where
each query type can have a different cost. Demonstrate this with complex
multiplication: naive (4 muls + 2 adds) vs Gauss's trick (3 muls + 5 adds),
proving correctness, exact parametric costs, and the crossover condition.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Authors: Kim Morrison
-/
module

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the Arith Prog from my CslibTests files?

Copy link
Contributor

@Shreyas4991 Shreyas4991 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thus arith query is similar to mine in the the CslibTests folder

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@Shreyas4991 Shreyas4991 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A non-comprehensive comparison with previous PR and authorship issues that arise therein.

namespace Cslib.Query

/-- Insert `x` into a sorted list using comparison queries. -/
@[expose] def orderedInsert (x : α) : List α → Prog (LEQuery α) (List α)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is identical to the Prog version upto query type.


-- ## Query count proofs

theorem orderedInsert_queriesOn_le (oracle : {ι : Type} → LEQuery α ι → ι)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You oracle is just my Model.evalQuery. Likewise for time.

toQTOracle (fromQTOracle f) = f := rfl

/-- Convert a `Prog (LEQuery α)` program to a `QueryTree (α × α) Bool` decision tree. -/
@[expose] def Prog.toQueryTree : Prog (LEQuery α) β → QueryTree (α × α) Bool β
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is essentially traceSort from cslib#372

/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sorrachai Yingchareonthawornchai, Kim Morrison
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The authorship claims on this go to an equal extent for me and Sorrachai. We both use a different recursive structure and I provide the Prog version of his code. Since the recursion structure is different here (and more akin to the upstream version), potentially neither of us are co-authors or both of us are.

@@ -0,0 +1,107 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I published a similar proof of model wide lower bound a few hours before cslib#376 got there.

/-- Evaluate a program by answering each query using `oracle`. -/
@[expose] def eval (oracle : {ι : Type} → Q ι → ι) : Prog Q α → α
| .pure a => a
| .liftBind op cont => eval oracle (cont (oracle op))
Copy link
Collaborator

@eric-wieser eric-wieser Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I claim that pattern matching on the free monad is exploiting an implementation detail, and that everything should really go through the universal property, FreeM.liftM. (this is what #372 does)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants