Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ signals a backwards-compatible change.
worker's seed and the aggregator's from one seed. The unseeded constructors
draw from the thread generator. The seed is not serialized, and the ASAPv1
payload is unchanged.
- **An ASAPv1 golden fixture for CSHeap** (`0x0a 0x00`),
`csheap_i64_regular_2x4_strkeys`: the Count Sketch 2x4 signed matrix with a
string-keyed heap at `k = 5`, pinned in `tests/asapv1_golden.rs`.

### Changed

Expand All @@ -88,6 +91,19 @@ signals a backwards-compatible change.
as an empty window like any other, and that window takes a place in
`max_windows`.

### Removed

- **The portable Count-Sketch-with-heap type.** `CountSketchWithHeap` and
`CsHeapItem` (crate root) and the module
`message_pack_format::portable::countsketch_topk` with `WireHeapItem`,
`SketchlibCSHeap`, `new_sketchlib_cs_heap`,
`sketchlib_cs_heap_from_matrix_and_heap`, `matrix_from_sketchlib_cs_heap`,
`heap_to_wire`, `sketchlib_cs_heap_update`, `sketchlib_cs_heap_query`,
`CountSketchInnerWire` and `CountSketchWithHeapWire`. Its MessagePack
`{sketch, topk_heap, heap_size}` encoding is gone with it; `CSHeap` encodes
and decodes ASAPv1 only, through `serialize_to_bytes` /
`deserialize_from_bytes`.

### Fixed

- `KLLDynamic` panicked on every quantile query once a NaN had entered the
Expand Down
4 changes: 2 additions & 2 deletions docs/asapv1_wire_format.md
Original file line number Diff line number Diff line change
Expand Up @@ -1341,14 +1341,14 @@ Good direction (more compact, higher fidelity, less Rust-internal duplication),
2. **Golden byte-vector fixtures** in one shared repo, [`sketchlib-golden-bytes`](https://github.com/ProjectASAP/sketchlib-golden-bytes), which each implementation mounts at `asapv1_golden/`; both languages decode and re-encode them byte-identically. These replace the `portable`-as-oracle round-trip test.
3. **This registry**, mirrored, never independently allocated.

Fixtures exist for six `kind_id`s — HLL's three estimators, Count-Min, Count Sketch and compact KLL — and `sketchlib-go` mirrors those. Every other kind here has a payload and **no fixture**, so this document is its only contract; `asapv1_golden/README.md` lists the gap. `portable` carries its own Go goldens.
`asapv1_golden/README.md`'s Coverage section lists which `kind_id`s have fixtures; for every other kind, this document is the only contract. `portable` carries its own Go goldens.

**Hash profile on the Go side.**
Rust derives the hash spec from a generic `HashProfile` bound on the hasher type; Go has no generic hasher type, so there is nothing to derive from.
On the Go side the profile is simply **written into** the metadata on encode and **read from** it on decode.
Go MUST validate the profile it reads (same fail-closed intent as Rust): a sketch is only mergeable/queryable if its `hash_profile_id` + seeds match the profile Go is prepared to reproduce.

Sequencing: (2) covers six `kind_id`s, and the one `native bytes == portable bytes` test is HLL-only, since no other `portable` type emits ASAPv1 bytes.
Sequencing: the one `native bytes == portable bytes` test is HLL-only, since no other `portable` type emits ASAPv1 bytes.
Retire `portable` once the fixtures cover the rest.

---
Expand Down
8 changes: 4 additions & 4 deletions docs/message_pack_format.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,10 @@ decode → re-encode them byte-identically. The `kind_id` registry is mirrored
verbatim with Go's `wire/asapmsgpack/magic_ids.go`, never independently
allocated.

That proof covers the six `kind_id`s a fixture exists for — HLL's three
estimators, Count-Min, Count Sketch and compact KLL. Every other implemented
kind has **no ASAPv1 golden and therefore no ASAPv1 drift guard**;
[its README](https://github.com/ProjectASAP/sketchlib-golden-bytes#coverage) lists what is covered.
That proof covers only the `kind_id`s with a fixture, which
[its README's Coverage section](https://github.com/ProjectASAP/sketchlib-golden-bytes#coverage)
lists. Every other implemented kind has **no ASAPv1 golden and therefore no
ASAPv1 drift guard**.
The older `portable` path is guarded separately, by the `sketchlib-go` goldens
of its own listed above.

Expand Down
17 changes: 1 addition & 16 deletions docs/tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -1279,21 +1279,6 @@ Test file: [`src/message_pack_format/portable/countsketch.rs`](../src/message_pa
| `test_compute_delta_then_apply_matches_current` | A delta between two non-empty snapshots reconstructs the later one. | For a `3x64` base fed 40 updates of `x{i % 8}` and a current fed 30 more, verifies the delta applied to the base reproduces the current matrix. |
| `test_compute_delta_matches_go_golden_bytes` | The packed delta bytes and per-row L2 are pinned by a golden. | For a `3x512` sketch fed the 25 `k-a`..`k-e` updates, verifies the delta against an empty sketch at threshold `1.0` equals a 93-byte inline golden carrying the packed cell arrays and a per-row L2 of `125.0`. |

### Portable CountSketchWithHeap

Test file: [`src/message_pack_format/portable/countsketch_topk.rs`](../src/message_pack_format/portable/countsketch_topk.rs)

| test_name | test_description | what_is_tested |
| --- | --- | --- |
| `test_creation` | A fresh composite has the requested matrix and an empty heap. | For `CountSketchWithHeap::new(4, 1000, 20)`, verifies `rows` `4`, `cols` `1000`, `heap_size` `20`, a `sketch_matrix()` of 4 rows of 1,000, and no heap items. |
| `test_query_empty` | An empty composite answers zero. | Verifies a `2x10` composite with a 5-slot heap reports `estimate("anything")` of `0.0`. |
| `test_merge` | Merge sums the matrices and adopts the peer's heap bound. | Merges a `2x10` composite holding `5.0` at `(0,0)`, `15.0` at `(1,1)`, heap `{key3: 75.0, key1: 80.0}` and `heap_size` `3` into one holding `10.0` at `(0,0)`, `20.0` at `(1,1)`, heap `{key1: 100.0, key2: 50.0}` and `heap_size` `5`, and verifies the merged cells read `15.0` and `35.0`, `heap_size` becomes `3`, and the heap holds at most 3 items. |
| `test_merge_dimension_mismatch` | Mismatched row counts are refused. | Verifies merging a `3x10` composite into a `2x10` one fails. |
| `test_msgpack_round_trip` | The round trip carries the matrix, the heap bound, and the heap itself. | Round-trips a `4x128` composite with a 3-slot heap fed `"hot"` at `100.0` and `"cold"` at `1.0`, and verifies the decode reports `rows` `4`, `cols` `128`, `heap_size` `3`, a non-empty heap holding `"hot"` at or above `100.0`, and estimates at or above `100.0` and `1.0`. |
| `test_aggregate_topk` | `aggregate_topk` builds a decodable composite honouring its heap bound. | For `aggregate_topk(4, 100, 2, ["a", "b", "a", "c"], [1.0, 2.0, 3.0, 0.5])`, verifies the decode reports `heap_size` `2` and at most 2 heap items; the heap contents themselves are not checked. |
| `test_aggregate_topk_empty` | An empty stream produces nothing. | Verifies `aggregate_topk(4, 100, 10, &[], &[])` returns `None`. |
| `test_median_estimator_differs_from_cms_style_min` | The median-over-rows estimator tracks the true count rather than inflating. | For a `5x64` composite with a 10-slot heap fed `"heavy"` 50 times at weight `1.0` and 20 keys `light-0`..`light-19` once each, verifies `estimate("heavy")` falls in `40.0..=60.0`. |

### Portable DdSketch

Test file: [`src/message_pack_format/portable/ddsketch.rs`](../src/message_pack_format/portable/ddsketch.rs)
Expand Down Expand Up @@ -1957,7 +1942,6 @@ Test file: [`tests/e2e/composition.rs`](../tests/e2e/composition.rs)
| test_name | test_description | what_is_tested |
| --- | --- | --- |
| `portable_count_min_with_heap_satisfies_the_count_min_bound_through_merge_and_wire` | The portable Count-Min-plus-heap holds Count-Min's bound and an agreeing heap through a merge and a MessagePack round trip. | Feeds `40_000` Zipf(1.1) keys over a `2_048` domain (seed `0xC090_5101`) as `"k{key}"` strings into `CountMinSketchWithHeap::new(3, 4_096, 32)`, plus even/odd halves into two more. Runs `CountMinSpec::new(3, 4_096).assert_contract` on the single sketch, asserts every `topk_heap_items()` entry's `value` equals `estimate(&item.key)` with zero violations, re-runs the contract on `CountMinSketchWithHeap::merge_refs(&[&left, &right])` against the full truth, and after `to_msgpack`/`from_msgpack` asserts the decoded sketch's `estimate` is exactly equal to the original's for every observed key and still satisfies the contract. |
| `portable_count_sketch_with_heap_satisfies_the_l2_bound_through_merge_and_wire` | The portable Count-Sketch-plus-heap holds the L2 bound and an agreeing heap through a merge and a MessagePack round trip. | Feeds the same `40_000`-key Zipf(1.1) stream (domain `2_048`, seed `0xC090_5101`) as `"k{key}"` strings into `CountSketchWithHeap::new(5, 4_096, 32)`, plus even/odd halves into two more, and runs `CountSketchSpec::new(5, 4_096).assert_contract` (simultaneous L2 band at `delta = 1e-3` with zero violations, marginal `sqrt(3/4096)*\|\|f_-i\|\|_2` rate pinned at `P[Bin(5, 1/3) >= 3] = 51/243`) on the single sketch, on `merge_refs(&[&left, &right])` and on the `from_msgpack` decode; every `topk_heap_items()` entry's `value` must equal `estimate(&item.key)`. Unlike the Count-Min twin, the wire round trip is only re-checked against the band - no per-key equality across the encode/decode is asserted. |
| `portable_kll_sketch_satisfies_the_rank_error_characterization_through_merge_and_wire` | The portable KLL facade holds the rank-error characterization single-pass and after a two-shard merge, and its wire round trip is bit-for-bit. | Over `12` compaction seeds `0x5EED_0400 + t * 0x9E37_79B9_7F4A_7C15`, builds `KllSketch::with_seed(200, seed)` plus even/odd shards at `seed ^ 0xAAAA` and `seed ^ 0x5555`, feeds all of them `40_000` uniform values over `1_000_000` (seed `0xC090_5101`), and merges the shards with `merge`. Each seed records two trials - the single-pass sketch and the merged pair - of the worst normalized rank error over the grid `[0.1, 0.25, 0.5, 0.75, 0.9]` against `KllRankSpec::datasketches(200)`'s `eps = 2.446/200^0.9433 = 0.0165`, and the `24` outcomes are accepted by `assert_independent_binomial` at `p = 0.01`. Per seed it also asserts `to_msgpack`/`from_msgpack` preserves `k() == 200`, `count()`, and every grid quantile exactly, and that merging a `k = 400` sketch into the single-pass one errs. |

### Composition: Documented Input Matrix
Expand Down Expand Up @@ -2126,6 +2110,7 @@ Test file: [`tests/asapv1_golden.rs`](../tests/asapv1_golden.rs)
| `cs_i32_regular_2x4_matches_golden` | The i32 counter type reaches the metadata, and an i32 golden is not an i64 sketch. | Verifies `Count::<Vector2D<i32>, RegularPath>::from_storage` over `[[0, 127, 128, 65536], [-1, -33, -32768, -2147483648]]` cast to `i32` serializes to `asapv1_golden/cs_i32_regular_2x4.hex`, that decoding it recovers the eight cells and re-serializes identically, that the i32 and i64 regular fixtures have equal length yet differ, and that the i32 fixture fails to decode as `Count<Vector2D<i64>, RegularPath>`. |
| `cs_i64_fast_2x4_matches_golden` | The fast-path mode reaches the bytes, so the same matrix encodes differently per path. | Verifies `Count::<Vector2D<i64>, FastPath>::from_storage` over `[[0, 127, 128, 65536], [-1, -33, -32768, -2147483648]]` serializes to `asapv1_golden/cs_i64_fast_2x4.hex`, that decoding it recovers the eight cells and re-serializes identically, and that the fixture differs from `cs_i64_regular_2x4.hex` built from the same matrix. |
| `cs_i64_regular_2x4_matches_golden` | A known signed Count-Sketch matrix serializes to its checked-in golden bytes. | Verifies `Count::<Vector2D<i64>, RegularPath>::from_storage` over `[[0, 127, 128, 65536], [-1, -33, -32768, -2147483648]]` — sweeping the msgpack integer widths in both directions — serializes to `asapv1_golden/cs_i64_regular_2x4.hex`, and that decoding the fixture reports `rows()` `2`, `cols()` `4`, the eight cells, and a byte-identical re-serialization. |
| `csheap_i64_regular_2x4_strkeys_matches_golden` | A known CSHeap matrix and heap serialize to their checked-in golden bytes. | Builds `CSHeap::<Vector2D<i64>, RegularPath>::from_storage` over `[[0, 127, 128, 65536], [-1, -33, -32768, -2147483648]]` at `k` `5` and seats the heap directly through `heap_mut().update` with `Str` keys `gamma` `-33`, `delta` `127`, `alpha` `4294967296`, `beta` `127`; verifies the bytes equal `asapv1_golden/csheap_i64_regular_2x4_strkeys.hex`, that decoding recovers the eight cells, `rows()` `2`, `cols()` `4`, capacity `5`, four entries each at its count, and a byte-identical re-serialization, and that the fixture fails to decode as `Count<Vector2D<i64>, RegularPath>`. |
| `hll_classic_p12_matches_golden` | A known P12 Classic register pattern serializes to its golden bytes on both the portable and native paths. | Builds `HllSketch::from_raw(HllVariant::Regular, 12, regs, 0.0, 0.0, 0.0)` where `regs` is 4,096 zeros with `[0] = 1`, `[1] = 7`, `[100] = 42` and `[4095] = 3`, verifies `to_msgpack()` equals `asapv1_golden/hll_classic_p12.hex`, that `HyperLogLogP12::<Classic>::deserialize_from_bytes` on the fixture recovers those registers and re-serializes identically, and that `HllSketch::from_msgpack` reports the same registers, `precision` `12` and `HllVariant::Regular`. |
| `hll_ertl_mle_p12_matches_golden` | The Datafusion variant tag reaches the bytes for the same P12 register pattern. | Builds `HllSketch::from_raw(HllVariant::Datafusion, 12, regs, 0.0, 0.0, 0.0)` over the shared P12 pattern (`[0] = 1`, `[1] = 7`, `[100] = 42`, `[4095] = 3` in 4,096 registers), verifies `to_msgpack()` equals `asapv1_golden/hll_ertl_mle_p12.hex`, that `HyperLogLogP12::<ErtlMLE>::deserialize_from_bytes` recovers the registers and re-serializes identically, and that `HllSketch::from_msgpack` reports `HllVariant::Datafusion`. |
| `hll_hip_p12_matches_golden` | The HIP variant's three running scalars travel on the wire beside its registers. | Builds `HllSketch::from_raw(HllVariant::Hip, 12, regs, 1.5, 2.5, 3.0)` over the shared P12 pattern, verifies `to_msgpack()` equals `asapv1_golden/hll_hip_p12.hex`, that `HyperLogLogHIPP12::deserialize_from_bytes` re-serializes the fixture identically, and that `HllSketch::from_msgpack` reports the registers, `HllVariant::Hip`, `hip_kxq0` `1.5`, `hip_kxq1` `2.5` and `hip_est` `3.0`. |
Expand Down
1 change: 0 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ pub use message_pack_format::portable::countminsketch_topk::{CmsHeapItem, CountM
pub use message_pack_format::portable::countsketch::{
COUNT_SKETCH_TOPK_CAPACITY, CountSketch, CountSketchDelta,
};
pub use message_pack_format::portable::countsketch_topk::{CountSketchWithHeap, CsHeapItem};
pub use message_pack_format::portable::ddsketch::{DDSKETCH_GROW_CHUNK, DdSketch, DdSketchDelta};
pub use message_pack_format::portable::delta_set_aggregator::DeltaResult;
pub use message_pack_format::portable::hll::{HllSketch, HllSketchDelta, HllVariant};
Expand Down
Loading
Loading