You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
v2.2.0 was tagged and released (2026-07-05) but CHANGELOG.md had no
`## [2.2.0]` section — everything that shipped in it was stranded under
`[Unreleased]`, alongside genuinely-unreleased post-2.2.0 work, and the
`[Unreleased]` block carried two `### Added` subsections (a
Keep-a-Changelog hygiene violation and the visible symptom of the
release rollover never happening).
Split at the real release boundary (verified via `git log v2.1.0..v2.2.0`
and `git log v2.2.0..HEAD`):
- New `## [2.2.0] - 2026-07-05` (tag date) with a single `### Added`:
CelerityMultiSet (#235), SwissSet, TopKSketch (#238), and the ISet<T>
set-algebra surface (#240).
- BitWriter/BitReader (#242) — which landed *after* the v2.2.0 tag — plus
RobinHoodSet, HashCachingSet, PooledCeleritySet, and all newer work stay
under `[Unreleased]`, now collapsed into a single `### Added` block.
No change needed to release.yml: its extraction step already keys off the
`## [<version>]` heading and fails loudly on an empty section, so the root
cause was purely the missing heading. Verified the awk now emits the four
v2.2.0 entries for [2.2.0] and stops cleanly at [2.1.0].
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CHANGELOG.md
+4-2Lines changed: 4 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -106,15 +106,17 @@ All notable changes to Celerity are documented here. This project follows [Keep
106
106
107
107
(Parity note: `ROADMAP.md` carries no `RobinHoodSet` item to flip — milestone 2.0.0's Robin Hood probing work is already `done`, and it shipped the strategy as `RobinHoodDictionary` only. This closes the dictionary→set parity gap discovered while reading the source after the roadmap was otherwise exhausted, exactly the drift the parity checklist exists to prevent — the same pattern as the `SwissSet` addition. Facets that genuinely do not apply: **no AOT smoke-test entry** — `Celerity.AotSmokeTest` exercises the core sets (`IntSet` / `LongSet` / `CeleritySet`) but not the specialized `SwissSet` / `RobinHoodSet` peers, so this follows the established `SwissSet` precedent.)
108
108
109
-
### Added
110
-
111
109
- **`BitWriter` / `BitReader`** in `Celerity.Primitives` — a **sequential, bounds-safe pair of `ref struct` cursors for packing and unpacking arbitrary-width bit fields** over a caller-owned `Span<byte>` / `ReadOnlySpan<byte>`, with no stream and no allocation. They are the **sequential, sub-byte** counterpart to the existing bit primitives: `VarInt` is byte-granular (whole-byte variable-length integers) and `SpanBits` is random-access (get/set one bit at a fixed index over a `Span<ulong>`), whereas `BitWriter` / `BitReader` append and consume **whole multi-bit fields at a moving cursor** — a 3-bit flag group, a 12-bit sample, a 20-bit offset — so a record of odd-width fields occupies exactly `ceil(total_bits / 8)` bytes instead of one byte per field. **The documented BCL-gap workload is bit-packed serialization** (wire protocols, compression bitstreams, packed columnar / bitmap-index encodings, fixed-width-field records): `System.Collections.BitArray` is a heap object that sets one bit at a time and cannot append a multi-bit field, and `System.Buffers.Binary.BinaryPrimitives` is byte-granular, so there is no span-based multi-bit bit writer in the BCL. Bit order is **LSB-first** (little-endian bits, the DEFLATE convention), and the writer and reader are exact inverses when fields are read back in the same order and widths. Surface: `BitWriter(Span<byte>)` with `TryWriteBits(ulong value, int bitCount)` / `TryWriteBit(bool)` / `BitsWritten` / `BytesWritten` / `CapacityInBits` / `BitsRemaining` and a static `ByteCount(int)` buffer-sizing helper; `BitReader(ReadOnlySpan<byte>)` with `TryReadBits(int bitCount, out ulong)` / `TryReadBit(out bool)` / `BitsRead` / `BitsRemaining`. Every `TryWrite` / `TryRead` is bounds-safe (returns `false` and leaves the cursor and buffer unchanged rather than writing or consuming a partial field), each write stores only the low `bitCount` bits (an out-of-range value can never corrupt a following field) and overwrites exactly the bits it occupies (no pre-zeroing required), a `0`-bit field is a no-op success, and a `bitCount` outside `[0, 64]` throws `ArgumentOutOfRangeException`. Filed and shipped after the roadmap was otherwise exhausted (a tier-(c) primitives-gap enhancement, in a different lane from the recent set/sketch collection work).
112
110
- `BitBufferTests` (`Celerity.Tests/Utils`) — dedicated coverage mirroring `VarIntTests` / `SpanBitsTests`: `ByteCount` rounding + negative-throws, a mixed-width (3/12/1/20/28-bit) field-record round-trip, the observable LSB-first byte layout, high-bits-above-width discarded (no leak into the next field), full-width (1/8/16/31/32/63/64-bit) values round-tripped at every sub-byte start offset, the single-bit `TryWriteBit` / `TryReadBit` helpers, the zero-width no-op, the bounds-safe overfull-write / truncated-read failures (asserting the cursor and earlier bytes are unchanged), `bitCount` argument validation, clear-then-set correctness over a dirty (`0xFF`-filled) buffer, and a 3,000-trial randomized reconciliation that packs a random field plan and checks the buffer bit-for-bit against a `bool[]` model then reads every field back masked to its width. Also exercised by the Native AOT smoke test (`Celerity.AotSmokeTest`), which round-trips a mixed-width record and confirms the overfull-write guard on the native runtime.
113
111
- `BitPackingBenchmark` in `Celerity.Benchmarks`, registered in `Program.cs`'s `ExtendedBenchmarks` array (the extended, on-demand suite, mirroring `VarIntBenchmark` — an isolated microbenchmark kept out of the per-PR core regression gate) — `[MemoryDiagnoser]` `Pack` / `Unpack` categories over a 4,096-field stream of 1–32-bit values, baselined against the idiomatic BCL bit-packing path (a reused `System.Collections.BitArray` set one bit at a time, then `CopyTo` the backing bytes), so the ratio reads as the span bit codec relative to the `BitArray` path.
114
112
- Documentation for `BitWriter` / `BitReader`: a full API section in [`docs/api/utilities.md`](docs/api/utilities.md#bitwriter--bitreader-sequential-sub-byte-bit-io) (the LSB-first bit-order convention, the field-cursor mechanism, the `ref struct` constraints, the method surface, the bounds-safety and high-bit-masking contract, and a runnable pack/unpack example) mirroring the `SpanBits` / `VarInt` sections, plus a README entry in the `Celerity.Primitives` walkthrough with a runnable example positioned alongside `VarInt` and `SpanBits`.
115
113
116
114
(Parity facets that genuinely do not apply: **no gh-pages dashboard wiring** — the dashboard `COLLECTIONS` arrays and ship cards are for the *collection* types; the `Celerity.Primitives` utilities, `BitPackingBenchmark` included, are extended-suite microbenchmarks not surfaced on the per-commit collections dashboard, exactly like `VarIntBenchmark` / `SpanBitsBenchmark`. **No cross-collection shared tests** — `BitWriter` / `BitReader` are not open-addressed set/dict types, so the family-wide `Add` / `TryAdd` / `SetConstructorValidation` / `IEnumerableConstructor` suites do not apply, as they do not for `VarInt` / `SpanBits`. **No `ROADMAP.md` status flip** — milestone 2.1.0's `Celerity.Primitives` bit/span work is already `done` and shipped `VarInt` + `SpanBits`; this adds the sequential sub-byte field cursor that sits between them, after the roadmap was otherwise exhausted, the established post-roadmap tier-(c) pattern.)
117
115
116
+
## [2.2.0] - 2026-07-05
117
+
118
+
### Added
119
+
118
120
- **`TopKSketch<T, THasher>`** in `Celerity.Collections` — a **space-bounded top-k / heavy-hitters sketch** implementing the **Space-Saving** algorithm (Metwally, Agrawal & El Abbadi, 2005), completing the streaming-sketch family: **membership** (`BloomFilter`, `CuckooFilter`) → **cardinality** (`HyperLogLog`) → **frequency** (`CountMinSketch`) → **top-k** ([#238](https://github.com/marius-bughiu/Celerity/issues/238)). It reports a stream's most frequent elements from a fixed `Capacity` of *monitors* (element / count / error triples), so its memory is `O(k)` regardless of stream cardinality — **the documented BCL-beating workload is top-k over a high-cardinality stream**, where a `Dictionary<T,int>` must materialize *every distinct key* just to rank the top few (`O(distinct)` memory), whereas the sketch holds only `k` monitors. Guarantees: it **never underestimates** a monitored count, never misses an element whose true frequency exceeds `TotalCount / Capacity`, and bounds each element's true frequency to `[Count − Error, Count]`. The monitors live in an indexed binary **min-heap** keyed on count (the next eviction victim at the root) so both a repeat observation and an eviction are `O(log k)`, and the element→monitor index **dogfoods `CelerityDictionary<T, int, THasher>`** — which is where `THasher` is used and which supplies the out-of-band `default(T)` / `null` handling for free (a string hasher is never invoked with `null`). Surface: `Add(item)` / `Add(item, count)` (positive-count-validated, `long`-saturating), `TryGetCount(item, out count, out error)`, `GetTopK()` / `GetTopK(int)` (returning `TopKEntry<T>` — element / count / error — sorted by count descending), `Clear`, and `Capacity` / `Count` (distinct monitored) / `TotalCount` (stream length) properties, plus an `IEnumerable<T>` counting constructor. It is **add-and-query only**: like a Bloom filter it has no `Remove`, and unlike `CountMinSketch` / `HyperLogLog` it deliberately has **no `UnionWith`** — two bounded top-k summaries have no exact merge, so no lossy one is offered (documented). A new public readonly struct **`TopKEntry<T>`** carries a result's `Element` / `Count` / `Error`. Filed and shipped after the roadmap was otherwise exhausted (a tier-(c) enhancement), mirroring `CelerityMultiSet` ([#235](https://github.com/marius-bughiu/Celerity/issues/235)).
119
121
- `TopKSketchTests` and `TopKSketchAccuracyTests` (`Celerity.Tests/Collections`) — dedicated coverage mirroring the `CountMinSketch*` / `HyperLogLog*` sketch tests. The behaviour suite pins the core surface (empty-sketch zeros; within-capacity exact counts; the eviction path that hands the newcomer the evicted minimum as its error floor incl. the `Capacity == 1` degenerate case; `GetTopK` ordering / truncation / validation; `TryGetCount`; `Clear`-then-reuse; capacity and null-source-beats-capacity constructor validation; and the out-of-band `default(int)` zero and `null`-string elements routed through the dogfooded dictionary). The accuracy suite asserts the hard Space-Saving theorems that hold for **every** input regardless of eviction tie-breaking — exactness (and `Error == 0`) when the capacity is not exceeded, reconciled against a `Dictionary` count-and-sort oracle; never-underestimate and the `[Count − Error, Count]` frequency interval for every survivor under heavy Zipfian eviction; genuine heavy hitters (frequency `> TotalCount / Capacity`) never missed; top-1 correct when one element dominates; and `GetTopK` sorted descending.
120
122
- `TopKSketchBenchmark` in `Celerity.Benchmarks`, registered in `Program.cs`'s `CoreBenchmarks` array (so it joins the per-PR core run and the gh-pages dashboard, mirroring `CountMinSketchBenchmark`) — `[MemoryDiagnoser]` `TopKSketch<int, Int32WangHasher>(k=100)` vs a `Dictionary<int,int>` counted in full and then `OrderByDescending`-sorted to extract the top-k, over a high-cardinality stream with planted heavy hitters across `[Params(1000, 100_000)]`, split into `Add` (build) and `TopK` (extract) categories so the Allocated column shows the sketch's constant `O(k)` footprint against the dictionary's `O(distinct)` growth.
0 commit comments