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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ 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 CountL2HH (`0x19 0x00`), `count_l2hh_2x4_seed7`:
a non-zero seed index, and cells and `l2` accumulators set apart from each
other that together reach every msgpack integer width, positive fixint
through uint64 and negative fixint through int64.

### Changed

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

### Removed

- **CountL2HH's `MessagePackCodec` impl** and its module,
`message_pack_format::native::countsketch_topk`. Use
`CountL2HH::serialize_to_bytes` / `CountL2HH::deserialize_from_bytes`, which
emit the same ASAPv1 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.
The [Coverage section](https://github.com/ProjectASAP/sketchlib-golden-bytes#coverage) of `asapv1_golden/README.md` lists the `kind_id`s the fixtures cover. For a kind without a fixture, 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
7 changes: 3 additions & 4 deletions docs/message_pack_format.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,9 @@ 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 a fixture exists for;
[its README](https://github.com/ProjectASAP/sketchlib-golden-bytes#coverage) lists them. A kind
without a fixture has **no ASAPv1 drift guard**.
The older `portable` path is guarded separately, by the `sketchlib-go` goldens
of its own listed above.

Expand Down
1 change: 1 addition & 0 deletions docs/tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -2123,6 +2123,7 @@ Test file: [`tests/asapv1_golden.rs`](../tests/asapv1_golden.rs)
| --- | --- | --- |
| `cms_f64_fast_2x3_matches_golden` | A known f64 fast-path Count-Min matrix serializes to its checked-in golden bytes. | Verifies `CountMin::<Vector2D<f64>, FastPath>::from_storage` over the `2x3` matrix `[[0.0, 1.5, 2.25], [3.75, 4.125, 5.0625]]` serializes to `asapv1_golden/cms_f64_fast_2x3.hex`, and that decoding that fixture recovers the six cells in row-major order and re-serializes to the same bytes. |
| `cms_i64_regular_2x3_matches_golden` | A known i64 regular-path Count-Min matrix serializes to its checked-in golden bytes. | Verifies `CountMin::<Vector2D<i64>, RegularPath>::from_storage` over the `2x3` matrix `[[0, 1, 127], [128, 300, 65536]]` serializes to `asapv1_golden/cms_i64_regular_2x3.hex`, and that decoding the fixture reports `rows()` `2`, `cols()` `3`, the six cells in row-major order, and a byte-identical re-serialization. |
| `count_l2hh_2x4_seed7_matches_golden` | A known CountL2HH state, its `l2` accumulators and seed index included, serializes to its checked-in golden bytes. | Builds `CountL2HH` from its named serde form with counts `[[127, 128, 65535, -32768], [-32, -33, -2147483648, i64::MIN]]`, `l2` `[65536, i64::MAX]` and `seed_idx` `7`, verifies it serializes to `asapv1_golden/count_l2hh_2x4_seed7.hex`, and that decoding the fixture reports `rows()` `2`, `cols()` `4`, `seed_idx()` `7`, the eight cells, the two `l2` accumulators, and a byte-identical re-serialization. |
| `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. |
Expand Down
15 changes: 0 additions & 15 deletions src/message_pack_format/native/countsketch_topk.rs

This file was deleted.

1 change: 0 additions & 1 deletion src/message_pack_format/native/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

pub mod countminsketch;
pub mod countsketch;
pub mod countsketch_topk;
pub mod ddsketch;
pub mod hll;
pub mod kll;
Expand Down
77 changes: 75 additions & 2 deletions tests/asapv1_golden.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
//! See `asapv1_golden/README.md`.

use asap_sketchlib::{
Classic, Count, CountMin, ErtlMLE, FastPath, HllSketch, HllVariant, HyperLogLogHIPP12,
HyperLogLogP12, KLL, MessagePackCodec, RegularPath, Vector2D,
Classic, Count, CountL2HH, CountMin, ErtlMLE, FastPath, HllSketch, HllVariant,
HyperLogLogHIPP12, HyperLogLogP12, KLL, MessagePackCodec, RegularPath, Vector2D,
};

fn decode_hex(s: &str) -> Vec<u8> {
Expand All @@ -30,6 +30,7 @@ const GOLDEN_CMS_F64: &str = include_str!("../asapv1_golden/cms_f64_fast_2x3.hex
const GOLDEN_CS_REGULAR: &str = include_str!("../asapv1_golden/cs_i64_regular_2x4.hex");
const GOLDEN_CS_FAST: &str = include_str!("../asapv1_golden/cs_i64_fast_2x4.hex");
const GOLDEN_CS_I32: &str = include_str!("../asapv1_golden/cs_i32_regular_2x4.hex");
const GOLDEN_L2HH: &str = include_str!("../asapv1_golden/count_l2hh_2x4_seed7.hex");
const GOLDEN_KLL_F64: &str = include_str!("../asapv1_golden/kll_f64_k200.hex");
const GOLDEN_KLL_I64: &str = include_str!("../asapv1_golden/kll_i64_k200.hex");

Expand Down Expand Up @@ -287,3 +288,75 @@ fn kll_i64_k200_matches_golden() {
assert_eq!(decoded.quantile(0.0), 1.0);
assert_eq!(decoded.quantile(1.0), 50.0);
}

// ---------------------------------------------------------------------------
// CountL2HH: counts, l2 accumulators and seed index set through the sketch's
// serde form -> serialize == golden, and golden round-trips.
// ---------------------------------------------------------------------------

/// CountL2HH cells are signed: row 0 is positive fixint max / uint8 / uint16 /
/// int16, row 1 is negative fixint min / int8 / int32 / int64.
const L2HH_COUNTS: [[i64; 4]; 2] = [[127, 128, 65535, -32768], [-32, -33, -2147483648, i64::MIN]];
/// One accumulator per row, set independently of the cells: uint32 / uint64.
const L2HH_L2: [i64; 2] = [65536, i64::MAX];
const L2HH_SEED_INDEX: usize = 7;

#[derive(serde::Serialize, serde::Deserialize)]
struct L2hhMatrixForm {
data: Vec<i64>,
rows: usize,
cols: usize,
}

#[derive(serde::Serialize, serde::Deserialize)]
struct L2hhVectorForm {
data: Vec<i64>,
}

/// The named serde form of `CountL2HH`.
#[derive(serde::Serialize, serde::Deserialize)]
struct L2hhForm {
counts: L2hhMatrixForm,
l2: L2hhVectorForm,
row: usize,
col: usize,
seed_idx: usize,
}

fn l2hh_form(counts: &[[i64; 4]; 2], l2: &[i64; 2], seed_idx: usize) -> L2hhForm {
L2hhForm {
counts: L2hhMatrixForm {
data: counts.iter().flatten().copied().collect(),
rows: 2,
cols: 4,
},
l2: L2hhVectorForm { data: l2.to_vec() },
row: 2,
col: 4,
seed_idx,
}
}

#[test]
fn count_l2hh_2x4_seed7_matches_golden() {
let want = decode_hex(GOLDEN_L2HH);

let form = l2hh_form(&L2HH_COUNTS, &L2HH_L2, L2HH_SEED_INDEX);
let sketch: CountL2HH =
rmp_serde::from_slice(&rmp_serde::to_vec_named(&form).expect("serde form"))
.expect("CountL2HH from its serde form");
let got = sketch.serialize_to_bytes().expect("serialize");
assert_eq!(got, want, "CountL2HH bytes diverge from golden");

let decoded: CountL2HH = CountL2HH::deserialize_from_bytes(&want).expect("decode");
let flat: Vec<i64> = L2HH_COUNTS.iter().flatten().copied().collect();
assert_eq!(decoded.as_storage().as_slice(), flat.as_slice());
assert_eq!(decoded.rows(), 2);
assert_eq!(decoded.cols(), 4);
assert_eq!(decoded.seed_idx(), L2HH_SEED_INDEX);
let state: L2hhForm =
rmp_serde::from_slice(&rmp_serde::to_vec_named(&decoded).expect("serde form"))
.expect("decoded serde form");
assert_eq!(state.l2.data, L2HH_L2);
assert_eq!(decoded.serialize_to_bytes().expect("re-serialize"), want);
}
Loading