Skip to content

Conversation

@Shreyas4991
Copy link
Contributor

@Shreyas4991 Shreyas4991 commented Jan 21, 2026

Generalizing @tannerduve 's query complexity model. This model of algorithmic complexity provides a lightweight approach to complexity verification of algorithms, similar to #165

However it offers several improvements over #165 :

  1. No explicit ticks needed, so the chances of making mistakes like forgetting to count specific calls is removed.
  2. The operations which are counted are made explicit as an inductive type and a model of this type.
  3. The model of a query type accepts custom cost structures so long as you can define addition and a 0 operation on them (basically additive monoids suffice).
  4. With a notion of reductions between models, this approach is more modular for the algorithm specifier, allowing high level specs of operations which can be translated into simpler query models later.

Update to the below drawback : The model requires users to choose a cost for each pure operation upfront by a typeclass instance for a given type of Cost. This is a departure from TimeMs model. But with custom Cost types with a specific field for pure, these costs can be separated from that of calls to queries.

One drawback : It is still possible to sneak in free operations inside pure. However this is unavoidable in any lightweight monadic approach. A deeply embedded DSL is the only foolproof way to avoid this. Nevertheless this approach removes annotation and review burden and ensures that any actual call to a query will be counted. Thus it is easy to notice when a monadic operation is not being called.

Zulip Discussion Thread: https://leanprover.zulipchat.com/#narrow/channel/513188-CSLib/topic/Query.20model.20for.20Algorithms.20complexity.20.3A.20updates/near/569606456

@Shreyas4991 Shreyas4991 changed the title Query complexity free m shreyas Query complexity formalisation for describing algorithmic models Jan 22, 2026
Comment on lines -349 to -351
/-- Type constructor for reader operations. -/
inductive ReaderF (σ : Type u) : Type u → Type u where
| read : ReaderF σ σ

Choose a reason for hiding this comment

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

Deleting this seems counter-productive

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That came from main. I don't use it in this PR. I am just not sure how to get rid of it.

Choose a reason for hiding this comment

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

I think "came from main" means "I did a bad merge of main"

Comment on lines 68 to 72
evalQuery q :=
match q with
| .write l i x => l.set i x
| .find l elem => l.findIdx (· = elem)
| .get l i => l[i]

Choose a reason for hiding this comment

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

Suggested change
evalQuery q :=
match q with
| .write l i x => l.set i x
| .find l elem => l.findIdx (· = elem)
| .get l i => l[i]
evalQuery
| .write l i x => l.set i x
| .find l elem => l.findIdx (· = elem)
| .get l i => l[i]

etc

Comment on lines +125 to +126
instance {Q α} : Coe (Q α) (FreeM Q α) where
coe := FreeM.lift

Choose a reason for hiding this comment

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

This belongs in the file defining FreeM

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed. Do note that the current file has a lot of things that should move to separate files.

Comment on lines 130 to 136
def eval [Add Cost] [Zero Cost]
(P : Prog Q α) (M : Model Q Cost) : α :=
match P with
| .pure x => x
| .liftBind op cont =>
let qval := M.evalQuery op
eval (cont qval) M

Choose a reason for hiding this comment

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

Suggested change
def eval [Add Cost] [Zero Cost]
(P : Prog Q α) (M : Model Q Cost) : α :=
match P with
| .pure x => x
| .liftBind op cont =>
let qval := M.evalQuery op
eval (cont qval) M
def eval [Add Cost] [Zero Cost]
(P : Prog Q α) (M : Model Q Cost) : α :=
Id.run <| P.liftM fun x => pure (M.evalQuery i)

section VectorLinearSearch

inductive VecSearch (α : Type) : TypeType where
| compare : (a : Vector α n) → (i : ℕ) → (val : α) → VecSearch α Bool
Copy link

@eric-wieser eric-wieser Jan 23, 2026

Choose a reason for hiding this comment

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

Why not write these to look more like functions,

Suggested change
| compare : (a : Vector α n) (i : ℕ) (val : α) VecSearch α Bool
| compare (a : Vector α n) (i : ℕ) (val : α) : VecSearch α Bool

Comment on lines 216 to 222
inductive VecSortOps (α : Type) : TypeType where
| swap : (a : Vector α n) → (i j : Fin n) → VecSortOps α (Vector α n)
| cmp : (a : Vector α n) → (i j : Fin n) → VecSortOps α Bool
| write : (a : Vector α n) → (i : Fin n) → (x : α) → VecSortOps α (Vector α n)
| read : (a : Vector α n) → (i : Fin n) → VecSortOps α α
| push : (a : Vector α n) → (elem : α) → VecSortOps α (Vector α (n + 1))

Choose a reason for hiding this comment

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

This is unsafe, as it lets the algorithm access the underlying data structure. At minimum you need

Suggested change
inductive VecSortOps (α : Type) : TypeType where
| swap : (a : Vector α n) → (i j : Fin n) → VecSortOps α (Vector α n)
| cmp : (a : Vector α n) → (i j : Fin n) → VecSortOps α Bool
| write : (a : Vector α n) → (i : Fin n) → (x : α) → VecSortOps α (Vector α n)
| read : (a : Vector α n) → (i : Fin n) → VecSortOps α α
| push : (a : Vector α n) → (elem : α) → VecSortOps α (Vector α (n + 1))
inductive VecSortOps (VecType : Nat → Type) (α : Type) : TypeType where
| swap : (a : VecType n) → (i j : Fin n) → VecSortOps VecType α (VecType n)
| cmp : (a : VecType n) → (i j : Fin n) → VecSortOps VecType α Bool
| write : (a : VecType n) → (i : Fin n) → (x : α) → VecSortOps VecType α (VecType n)
| read : (a : VecType n) → (i : Fin n) → VecSortOps VecType α α
| push : (a : VecType n) → (elem : α) → VecSortOps VecType α (VecType (n + 1))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. I'll get back to this tomorrow noon. A Finvec would be the better option.

Copy link

@eric-wieser eric-wieser Jan 23, 2026

Choose a reason for hiding this comment

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

On second thoughts, this still doesn't help; you can't let the algorithm have access to a at all, otherwise it can do classical if VecType = Vector then cheat else alg

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Any monadic dsl will have this issue.

Choose a reason for hiding this comment

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

I think the issue goes away if the dsl can only access the vector through opaque variable indices?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That was my data structure hiding idea. Make the vector opaque. That is, remove the vector from the query and add it to the model. The problem is removing it means we can't write monadic Progs that are recursive on specific subvectors.

Copy link
Contributor Author

@Shreyas4991 Shreyas4991 Jan 24, 2026

Choose a reason for hiding this comment

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

Another issue with this idea is that currently the second parameter of the query type is the return type of the operation. If we can't have that, then we can't have the monad structure. Without flexibility on the return type of the query, we lose a lot of flexibility in our queries

Cslib.lean Outdated
public import Cslib.Foundations.Control.Monad.Free.Fold
public import Cslib.Foundations.Data.FinFun
public import Cslib.Foundations.Control.Monad.Time
import Cslib.Foundations.Data.FinFun
Copy link
Collaborator

Choose a reason for hiding this comment

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

Make sure with the move to the module system you now run lake exe mk_all --module

Comment on lines +15 to +16
`TimeM` is a monad that tracks execution time alongside computations, using natural numbers
as a simple cost model. As plain types it is isomorphic to `WriterT Nat Id`.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you clarify what you envision happening to this module? If what you are proposing subsumes this, I vote that we replace it outright with the new framework. I think we can make room for a lightweight monadic DSL and Boole's deep embedding, but I don't want to encourage any more fragmentation than that.

Copy link
Contributor Author

@Shreyas4991 Shreyas4991 Jan 24, 2026

Choose a reason for hiding this comment

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

This PR largely subsumes TimeM. In principle, because TimeM allows arbitrary (occurrences of) operations to be checkmarked, it is more flexible, but as I argued in December, this flexibility is a footgun and a limitation in so many ways. As long as we are systematically counting operations, this model clearly subsumes TimeM.

There is one footgun any monadic DSL will have, namely, sneaking in of computations with pure. This is fundamental to a monad. However even here, I have just managed to improve the situation a bit, by adding a notion of PureCosts as a typeclass and adding custom pure fields in some cost models. As you can see in the #evals as lines 222 and 401, this model can capture how many pure operations are used as well. This breaks the equivalence with TimeM's model of assigning 0 cost to pure, but gives us room for a sanity check as reviewers of code.

Overall I agree with you that this model can entirely replace TimeM.

Comment on lines +59 to +63
if id ∉ s₂
then
id :: s₂
else
s₂

Choose a reason for hiding this comment

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

Suggested change
if id ∉ s₂
then
id :: s₂
else
s₂
insert id s₂

Comment on lines +54 to +55
| .const id _ =>
if id ∉ countedIDs then id :: countedIDs else countedIDs
Copy link

@eric-wieser eric-wieser Jan 26, 2026

Choose a reason for hiding this comment

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

Suggested change
| .const id _ =>
if id ∉ countedIDs then id :: countedIDs else countedIDs
| .const id _ => insert id countedIDs

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.

4 participants