Skip to content

Remove the NumBits and NumVals traits - #130

Merged
kampersanda merged 4 commits into
mainfrom
claude/numbits-trait-design-716fd8
Oct 10, 2026
Merged

kampersanda merged 4 commits into
mainfrom
claude/numbits-trait-design-716fd8

Conversation

@kampersanda

@kampersanda kampersanda commented Oct 10, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Remove bit_vectors::NumBits and int_vectors::NumVals, along with their impls and prelude re-exports.
  • Add inherent num_ones() and num_zeros() to BitVector, Rank9Sel, DArray, and SArray. Lengths are available through the existing inherent len() / is_empty().
  • WaveletMatrix now stores the number of zeros in each layer (zeros) and the sequence length (len), so its layer bound becomes Access + Rank + Select.
  • Update module docs, internal call sites, and the bench crate accordingly.

Why

Both traits only reported size statistics and were an over-abstraction, in the same spirit as #129:

  • NumVals had no generic users at all. Every impl just forwarded to the inherent len().
  • NumBits::num_bits also just forwarded to len(). The trait name described only one of its three methods.
  • The cost of num_ones was not part of the contract: O(1) for Rank9Sel, DArray, and SArray, but O(u) for BitVector.
  • The only generic user was WaveletMatrix, which called num_zeros() on every layer in its query loops. These values are known at construction time, so storing them is the textbook layout (Z_l in Claude and Navarro's paper). It also avoids the O(u) scan per query when BitVector is used as a layer.

We also considered keeping a minimal length-only trait (Len) for future generic structures such as LOUDS or FM-index, but decided against it: such structures can store the values they need in the same way as WaveletMatrix.

Notes for reviewers

  • Breaking change: public traits are removed. Code that names NumBits / NumVals in imports or bounds, or calls num_bits() / num_vals(), must switch to len(). Calls to num_ones() / num_zeros() on concrete types keep working since they are now inherent methods. This should go into 0.10.0.
  • Serialization format change for WaveletMatrix: zeros and len are now serialized after layers and max_value, so data serialized by earlier versions cannot be read.
    • Deserialization returns InvalidState if zeros.len() differs from layers.len(), since zipping them would otherwise silently return wrong values (test_deserialize_inconsistent_zeros).
    • zeros could be recomputed at load time as rank0(len) if only len were stored, but that would require widening the Serializable bound to B: Serializable + Rank, cost O(u) per layer for BitVector layers, and panic at load time for DArray layers without enable_rank(). Storing zeros keeps the bound as B: Serializable. The overhead is 8 bytes per layer plus 8 bytes.
  • EliasFano::from_bits now uses bv.is_empty() instead of comparing the length to zero (clippy len_zero).

Test plan

  • cargo test --workspace
  • cargo test --no-default-features
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo clippy --no-default-features -- -D warnings
  • RUSTDOCFLAGS="-D warnings" cargo doc --no-deps
  • cargo fmt --all -- --check
  • cargo +1.62.0 check --lib (MSRV)

🤖 Generated with Claude Code

kampersanda and others added 4 commits October 10, 2026 09:32
Both traits only reported size statistics and had almost no generic
users. `NumVals` was never used as a bound, and `NumBits` was only used
by `WaveletMatrix`, which now stores the number of zeros in each layer
and the sequence length itself.

- Remove `bit_vectors::NumBits` and `int_vectors::NumVals` with their
  impls and prelude re-exports.
- Add inherent `num_ones()` and `num_zeros()` to `BitVector`,
  `Rank9Sel`, `DArray`, and `SArray`.
- Store `zeros` and `len` in `WaveletMatrix` and serialize them, so
  its layer bound is now `Access + Rank + Select`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Serialize `zeros` and `len` after `max_value`, so that reading data in
  the previous format fails with an EOF error instead of interpreting
  `max_value` as the length of `zeros` and attempting a huge allocation.
- Return `InvalidState` if `zeros.len()` differs from `layers.len()`,
  since zipping them would otherwise silently return wrong values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The old format is not something we keep maintaining.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kampersanda
kampersanda merged commit 0b555d8 into main Oct 10, 2026
7 checks passed
@kampersanda
kampersanda deleted the claude/numbits-trait-design-716fd8 branch October 10, 2026 00:45
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.

1 participant