Skip to content

Static optimality for splay trees - #22

Open
berendsohn wants to merge 15 commits into
cslib-community:mainfrom
berendsohn:splay-static-opt
Open

berendsohn wants to merge 15 commits into
cslib-community:mainfrom
berendsohn:splay-static-opt

Conversation

@berendsohn

Copy link
Copy Markdown

A proof of static optimality for splay trees: for any sufficiently long sequence of access operations, splay trees perform as well as any static tree (up to a constant factor).
This includes a variant of Sleator and Tarjan's classical access lemma.
For the static optimality proof, I used the method outlined by David Eppstein here, which circumvents the need to work with entropy.

Some more notes:.

  • Most of the code for the access lemma is adapted from the existing log n complexity bound (or rather from the cslib pull request); it was mostly a matter of transforming the analysis from unweighted to weighted. Parts of the original analysis are now special cases of the new analysis. Not sure how much duplication is desired.
  • I added a few extra lemmas in relevant files. One slightly annoying thing is that Weighted.lean needs to import Complexity and Correctness; this could be remedied by some reorganization.
  • The variant of the access lemma is slightly weaker than the original. The original bounds the amortized running time of an access by log w(T)/w(T_x), where w(T) is the total weight of the tree and w(T_x) is the weight of the subtree rooted at the accessed node x. I proved instead the weaker log w(T)/w(x). This is because even stating the strong variant seemed to require some new definitions regarding rooted subtrees, and because the weaker variant is almost always sufficient in my experience.
  • I did not prove the entropy bound, i.e., that the total cost is the entropy of the input sequence (plus some overhead). However, the access lemma provides the basic building block for this.
  • The static optimality overhead is O(n²), instead of the original O(n log n) (where n is the number of nodes, not accesses). This is because of the method I used.
  • All accesses are assumed to be successful, like in the original proof.

I'm new to lean and this is my very first project, so all feedback is appreciated.

@sorrachai

Copy link
Copy Markdown
Collaborator

@AntoineduFresne, can you review this PR?

Fixed typos and syntax/formatting (done with an LLM), added the new files
to AlgoLib.lean, made BSTAPI.staticCost a wrapper around the Tree version,
removed unused code and simplified a few proofs.
FnLb_of_Fintype_of_FnPos is now FnLb_of_Finite_of_FnPos and takes
[Finite α].

@AntoineduFresne AntoineduFresne left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks a lot for this, really nice work! I've gone through BSTAPI.lean and Weighted.lean so far, and I'll review StaticOptimality.lean and the smaller changes (BinaryTree, Basic, Complexity) later.

I pushed a cleanup commit on top of yours (unused code, typos, formatting and a few small simplifications; details in the commit message), so please pull before you continue. One API change to be aware of: FnLb_of_Fintype_of_FnPos is now FnLb_of_Finite_of_FnPos and takes [Finite α].

For the part I reviewed, there's only one thing, please add the usual copyright header (with you under Authors) to Weighted.lean and StaticOptimality.lean.

Also there are other alternative definition (that doesn't use the forallquantifier) for FnLbOne w and FnNonneg w which are just 1 ≤ w and 0 ≤ w altough FnPos has no such notation (0 < w isn't pointwise positivity). But this is irrelevant from my side I guess.

About your question on duplication. I believe that it is fine for this PR for now. it seems that the unweighted analysis is just the case w = 1. You can maybe do a follow-up that derives Complexity.lean from Weighted.lean, which might hopefully remove the need for Weighted to import Complexity.

I went through the rest of the files. For syntax and spelling cleanups, I allowed myself to commit it directly (only
spacing, blank lines... line breaks (100-character limit))
(this concerns StaticOptimality.lean and the new lemmas in BinaryTree.lean)
I renamed your new lemmas to follow the Mathlib naming conventions, i.e.
nonnil_of_mem -> ne_nil_of_mem
nil_of_toKeyList_empty -> eq_nil_of_toKeyList_eq_nil
num_nodes_from_toKeyList -> num_nodes_eq_length_toKeyList
Dir.applyChild_ne_nil_of_ne_nil -> applyChild_ne_nil_of_ne_nil (applyChild is not in the Dir namespace)
and updated the places where they are used. I used AI to help me with this.
…rom this library or Mathlib) or shorter the proofs directly by providing the term.
…omplexity.lean, so instead of making it public I used Mathlib's Real.logb_le_logb_of_le directly, that way the PR doesn't need to touch Complexity.lean. I also merged splay_tree_static_optimality' into splay_tree_static_optimality: the private version only existed to take IsBST s as an extra assumption, but that follows from hkeys and IsBST init in one line, so the same statement doesn't need to be written twice. Feel free to revert if you prefer it your way.
…just intro the lets from the statement instead of writing them out again, φ_zigzig and φ_zigzag had a let c that was never used, and in φ_mirror the subtrees were named the other way round (r for the left one), so I swapped them. I also shortened size_mul_weight and search_path_len_right a bit.
…' doesn't need the cases on m (nothing below uses the split), and splay_tree_static_optimality doesn't need by_cases init = nil, since each access already gives init ≠ nil. The rest of both proofs is unchanged, just one indentation level less.
…t several times: rank_eq_logb_size in Weighted (rank is just log₂ of the size, also for nil since log₂ 0 = 0 in Mathlib), which shortens rank_le_of_size_le, rank_eq_of_toKeyList_eq and rank_mirror, and size_eq_mul_size in StaticOptimality (if two weight functions differ by a constant factor on the keys of a tree, so do the sizes), which replaces the list induction that staticWeight_size_left and staticWeight_size_right each did separately.
… at h and assumption steps can be done with a single simpa ... using h, so I used that in both places.
…:= ... and simp only [reassemble_cons]; push_cast, so I moved them once above the split_ifs.

@AntoineduFresne AntoineduFresne left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hi Benjamin,

I took the liberty of pushing a few small simplifications to your branch, I hope that's okay. I split them into several commits, one per kind of change (formatting, lemma names, shorter proofs, two small helper lemmas, ...), so it should be easy to look at them one by one and revert anything you don't like.

I also noticed two other things that might work, but I'm not sure and didn't look into them further, so feel free to ignore them:

  • In splay_total_weighted_cost', I wonder if the three inductions (hbst', hcont' and hsize) could come from a single one showing that splaying doesn't change the key list (toKeyList_splay), maybe with IsBST_of_toKeyList_eq, mem_iff_mem_toKeyList and size_from_toKeyList.
  • In staticWeight_size_left, if I'm not mistaken the exponent s.num_nodes - l.num_nodes - 1 is just r.num_nodes (and l.num_nodes in staticWeight_size_right). Maybe stating them with the natural power 3 ^ r.num_nodes would make the rpow computations in staticWeight_size_self_ub a bit simpler.

Apart from that, it looks good to me as far as I can tell. I'm seeing Sorrachai on Monday, so I'll ask him what he thinks, and then hopefully we can approve it soon. Thanks again for the PR!

@berendsohn

Copy link
Copy Markdown
Author

Thanks! I'm happy with the changes you made, the simplifications are very nice. Thanks for the informative commit messages.

For the part I reviewed, there's only one thing, please add the usual copyright header (with you under Authors) to Weighted.lean and StaticOptimality.lean.

Done. I also added you since you made some changes.

I'll also look into your two proposals, but I'm quite busy this month so it may take some time.

@berendsohn

Copy link
Copy Markdown
Author
  • In splay_total_weighted_cost', I wonder if the three inductions (hbst', hcont' and hsize) could come from a single one showing that splaying doesn't change the key list (toKeyList_splay), maybe with IsBST_of_toKeyList_eq, mem_iff_mem_toKeyList and size_from_toKeyList.

Okay, this was easy actually.

This branch has not been deployed

No deployments
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