Skip to content

feat(sketch): posterior (query-time) error estimation for CMS/CU-Sketch/Count-Sketch (Chen et al. IMC'21) - #248

Merged
zzylol merged 3 commits into
mainfrom
feat/posterior-sketch-error-estimation-239
Aug 23, 2026
Merged

zzylol merged 3 commits into
mainfrom
feat/posterior-sketch-error-estimation-239

Conversation

@zzylol

@zzylol zzylol commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Part of #239 — adds the posterior (query-time) sketch error estimator from
Chen, Wu, Yang, Jiang, Liu, "Precise Error Estimation for Sketch-based Flow
Measurement"

(IMC '21), as a pure, sketch-object-agnostic module, plus an opt-in
plan-time sizing option in boundary.rs motivated by the same paper.

What's implemented — the estimator (new asap_types::post_asap::error_estimation)

Fetched the paper (§3, §3.2, §3.3, and Appendix A.1 — including re-rendering
the PDF pages as images to verify formulas the text extractor garbled) and
implemented, as plain functions over &[u64]/&[i64] counter slices and
numeric params (no concrete sketch type involved — see "What's not
implemented" below for why):

  • cms_posterior_error_bound — §3.1 Algorithm 1 (CM-Sketch): given
    one row's w counters, r rows, and target confidence 1-δ, returns the
    ⌊w·δ^(1/r)⌋-th largest counter as the error bound. (The paper's own
    Algorithm 1 pseudocode box prints ⌈wp⌉ while its prose says ⌊wp⌋ — an
    internal inconsistency in the source paper; this implementation follows
    the prose, documented in the function's doc comment. The two conventions
    differ by at most one rank, within the paper's own O(1/√w) bias bound.)
  • cu_sketch_posterior_error_bound — Appendix A.1 Algorithm 2
    (CU-Sketch): the paper states this is identical to Algorithm 1 (both
    query paradigms return the min counter across rows), so this delegates to
    cms_posterior_error_bound rather than duplicating logic.
  • count_sketch_posterior_error_bound — Appendix A.1 Algorithm 3
    (Count-Sketch): the median-based variant. Verified by rendering the PDF
    page as an image (the text extractor garbled the binomial-tail formula).
    Requires odd r — Theorem A.1 only states the optimal bound for
    r = 2k+1; the paper's footnote for even r ("similar equation for an
    even r") doesn't spell out the formula, so per the issue's instruction to
    "only implement what you can read and verify," even r returns None
    rather than guessing.
  • classic_cms_sizing / traditional_a_priori_bound — the
    traditional a priori bound this issue is contrasted against (§3.3, just
    before Eq. 6): w = ⌈e/ε⌉, r = ⌈ln(1/δ)⌉, bound = ε·|F|₁. Necessarily
    a second implementation of the same formula as boundary.rs's private
    cms_width/cms_depth (asap-types sits below asap-aware-mapping in the
    workspace's dependency layering, so it can't import them) — pinned equal
    to boundary.rs's own worked example in a test so the two can't silently
    drift apart undetected.

Correctness evidence

The real, checkable property from the paper's Eq. 6 (posterior bound is
always ≤ the traditional worst-case bound for the same (w, r) sizing):
cms_posterior_bound_is_at_most_traditional_bound builds synthetic CMS rows
(uniform and skewed mass distributions, a fixed-seed deterministic PRNG —
no new crate dependency) across several (ε, δ) targets sized via
classic_cms_sizing, and asserts the posterior bound never exceeds
ε·|F|₁. A companion test nails the degenerate all-mass-in-one-counter
case, and kth_largest_pigeonhole_property checks the elementary fact the
paper's Eq. 6 proof leans on (k-th largest of w non-negative values
summing to T is ≤ T/k) directly and unconditionally.

Also: hand-computed examples, monotonicity (tighter bound as width grows
holding total mass fixed; rank grows monotonically with r), edge cases
(δ at 0/1 boundaries, all-zero counters, single row, empty row, even r
for Count-Sketch). 19 tests, all passing in
crates/types/src/post_asap/error_estimation.rs.

What I found re: sketch-runtime state (confirms the issue's premise)

Confirmed — this repo has no vendored CMS/CountSketch/CU-Sketch
implementation and no runtime counter-array data structure anywhere
.
crates/types/src/post_asap/sketch.rs's SketchKind/SketchParams are
purely planning-time sizing metadata (width/depth/precision/k numbers, never
an actual counter matrix), and
crates/asap-aware-mapping/src/boundary.rs's default_size_params/
cms_width/cms_depth only ever compute those sizing numbers. A
rg/grep sweep for struct.*Cms, impl.*Cms, and any counter-array type
across the workspace turns up nothing beyond that metadata.

So the estimator functions above take plain counter slices rather than a
concrete sketch type, specifically so a future real sketch runtime's readout
path can call them directly on its real counter arrays with zero changes
needed here — see the module's doc comment for this stated explicitly.

What's wired for real — plan-time sizing (boundary.rs::posterior_aware_size_params)

New, purely additive function alongside (never called from, never changing)
default_size_params: given a caller-supplied ExpectedCaseSizing { width_relaxation } (a fraction in (0,1) of the worst-case cms_width the
caller is betting is enough for a non-adversarial workload), it sizes a
smaller CMS-family sketch (Cms/CmsWithHeap/CountSketch/
CountSketchWithHeap) — depth is left at the worst-case formula (relaxing
depth trades away confidence exponentially, a materially different and
riskier axis, out of scope here). The doc comment spells out exactly what's
being traded: the (ε,δ) guarantee becomes conditional on the stated
assumption holding, not unconditional — this function cannot and does not
verify the assumption, since no data exists at plan time.

This is not a literal call into the posterior estimator (there's no real
counter data at plan time to feed it) — it's this crate's own plan-time
analogue of the same "expected-case workloads need less" intuition, clearly
documented as such rather than overclaiming a direct connection to the
paper's technique.

Tests: width_relaxation < 1.0 produces a strictly smaller width than
default_size_params; width_relaxation == 1.0 (or any out-of-range value:
0.0, negative, >1.0, NaN, ∞) reproduces default_size_params
exactly; depth is unaffected; every CMS-family SketchKind is covered;
every other SketchKind is byte-for-byte identical to default_size_params;
and a regression pin confirms default_size_params's own existing behavior
is unchanged.

What's deferred, and why

  • Runtime readout-time wiring (issue Add posterior (query-time) error estimation for CMS/CountSketch, per Chen et al. IMC'21 #239's integration point 1): not
    attempted — there is no real sketch runtime to wire into (see above). The
    estimator functions are ready to be called the moment one lands; that
    wiring is explicit follow-up scope for Add posterior (query-time) error estimation for CMS/CountSketch, per Chen et al. IMC'21 #239.
  • Depth relaxation in posterior_aware_size_params: explicitly out of
    scope for this PR (see that function's doc comment) — a different,
    exponential-confidence tradeoff from width's linear one.
  • Nothing from Appendix A.1 was skipped due to fetch failure — both
    CU-Sketch and Count-Sketch algorithms were verified by rendering the PDF
    pages as images after the text extractor garbled the formulas. The only
    narrowing is Count-Sketch's even-r case, deferred because the paper
    itself doesn't spell out that formula (see count_sketch_posterior_error_bound's
    doc comment).

Process

  • cargo build --workspace --all-targets — clean
  • cargo test --workspace — all passing (19 new tests in
    error_estimation.rs, 6 new tests in boundary.rs, no existing test
    changed)
  • cargo fmt --all -- --check — clean
  • cargo clippy --workspace --all-targets --all-features -- -D warnings —
    clean

🤖 Generated with Claude Code

@zzylol

zzylol commented Aug 22, 2026

Copy link
Copy Markdown
Contributor Author

Reorganized per feedback: error_estimation.rs moved into a new post_asap::query_time/ submodule with its own module doc, making the execution-time vs. planning-time boundary visible in the folder structure itself, not just prose. crates/asap-aware-mapping/src/boundary.rs::posterior_aware_size_params (the real planning-time code from this PR) stays where it is — inside the planning crate — and its docs now cross-reference #250, filed to track actually connecting the two (feeding observed query-time posterior error back into a future replan) instead of leaving ExpectedCaseSizing a bare caller guess forever.

No behavior change — public paths (asap_types::post_asap::cms_posterior_error_bound etc.) are preserved via re-exports. cargo build/test/fmt/clippy all pass clean (459 tests, 0 failed).

@zzylol

zzylol commented Aug 22, 2026

Copy link
Copy Markdown
Contributor Author

A code-review pass I ran (mistakenly targeting this PR's content while I asked it to review a different PR) surfaced 6 real findings against this branch. Verified each and fixed the legitimate ones:

  • classic_cms_sizing now genuinely matches boundary.rs's cms_width/cms_depth — formula and clamps — instead of only the in-range formula (it previously returned (0, 0) for degenerate inputs and diverged silently outside a narrow range, contradicting its own "identical" doc claim).
  • posterior_aware_size_params's match is now exhaustive over SketchKind (like default_size_params's), so a future sketch kind fails to compile here instead of silently inheriting worst-case sizing.
  • relaxed_width now calls the existing private saturating_ceil instead of reimplementing its clamp inline.
  • count_sketch_posterior_error_bound's fractile search is now O(r log w) (binary search + precomputed binomial coefficients) instead of O(w·r²).
  • kth_largest now uses select_nth_unstable_by (O(w) avg) instead of a full sort (O(w log w)).
  • query_time's internal re-export of error_estimation's functions is now a glob, so adding a function only needs one edit (the curated post_asap::mod list), not two.

All algorithmic changes verified output-preserving via the existing pinned/property tests (only the one test pinning the old degenerate-input bug needed its expected values updated). cargo build/test/fmt/clippy all pass clean (460 tests, 0 failed).

zzylol and others added 3 commits August 22, 2026 18:39
…. IMC'21

Part of #239 — implements the paper's Algorithm 1 (CM-Sketch), Algorithm 2
(CU-Sketch, identical to Algorithm 1 per the paper's own text), and
Algorithm 3 (Count-Sketch, median-based, odd rows only per Theorem A.1) as
pure, sketch-object-agnostic functions in a new
asap_types::post_asap::error_estimation module, plus the traditional a
priori bound/sizing for comparison.

Confirmed this repo has no CMS/CountSketch/CU-Sketch runtime and no
counter-array data structure anywhere — SketchKind/SketchParams
(post_asap/sketch.rs) and default_size_params/cms_width/cms_depth
(asap-aware-mapping/boundary.rs) are purely planning-time sizing metadata.
So the new estimator functions take plain counter slices, not a concrete
sketch type, ready for a future real sketch runtime's readout path to call
directly — no runtime wiring is attempted here.

Also adds boundary.rs::posterior_aware_size_params, an opt-in alternative
to default_size_params that shrinks CMS-family width under an explicit
caller-stated ExpectedCaseSizing assumption, documenting exactly what
guarantee is being traded away. default_size_params itself is untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Moves crates/types/src/post_asap/error_estimation.rs into a new
post_asap::query_time/ submodule with its own module doc, per review
feedback: the posterior sketch error estimator (issue #239) is
execution-time code (it needs a real sketch's actual counter values,
readable only once a query has run against real data), architecturally
distinct from the rest of post_asap, which is planning-time IR the
planner constructs before a query runs. That distinction previously
lived only in prose (crate doc's "no execution logic lives in this
workspace... error_estimation is the one exception"); it's now visible
in the directory listing itself.

No behavior change — public re-export paths
(asap_types::post_asap::cms_posterior_error_bound etc.) are preserved
via query_time/mod.rs's re-exports, so this is a pure move. Updates the
doc-path references in crates/types/src/lib.rs and
crates/asap-aware-mapping/src/boundary.rs accordingly, and boundary.rs's
ExpectedCaseSizing/posterior_aware_size_params docs now cross-reference
issue #250 (filed separately: feeding query-time-observed posterior
error back into a future replan, rather than a bare caller-supplied
width_relaxation guess).

Verified: cargo build --workspace --all-targets, cargo test --workspace
(459 passed, 0 failed), cargo fmt --all -- --check, cargo clippy
--workspace --all-targets --all-features -- -D warnings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Findings surfaced while reviewing a different PR (the tool mistakenly
reviewed this branch's content), verified and fixed here since this PR
is still open:

- classic_cms_sizing didn't replicate boundary.rs's cms_width/cms_depth
  saturating clamps, only their in-range formula, despite the doc
  comment's "identical" claim - silently diverging outside a narrow
  (eps, delta) range (e.g. eps=1e-10 or delta=1e-20), and returning
  (0, 0) for degenerate inputs instead of boundary.rs's real
  saturate-to-hi behavior. Now shares the exact same saturating_ceil
  clamp policy. New tests pin the extreme-range values by hand.
- posterior_aware_size_params's wildcard match arm silently gave any
  future SketchKind variant worst-case sizing with no compiler signal,
  unlike default_size_params's exhaustive match. Spelled out
  exhaustively to match.
- relaxed_width's inline clamp reimplemented boundary.rs's own private
  saturating_ceil byte-for-byte instead of calling it - now calls it.
- count_sketch_posterior_error_bound's fractile search was O(w*r^2)
  (linear scan over w fractiles, each recomputing an O(r) binomial
  coefficient from scratch): now O(r log w) via binary search (the
  tail probability is monotonic in the fractile) over coefficients
  precomputed once.
- kth_largest fully sorted (O(w log w)) to find one rank: now uses
  select_nth_unstable_by (O(w) average).
- query_time/mod.rs's re-export of error_estimation's public functions
  is now a glob, not a hand-maintained list, so a future function only
  needs adding to post_asap/mod.rs's curated public-surface list, not
  two places.

All algorithmic changes preserve identical output - verified via the
existing pinned-value and property-based tests, none of which needed
their expected values changed (only the degenerate-input test, which
was pinning the bug).

Verified: cargo build --workspace --all-targets, cargo test --workspace
(460 passed, 0 failed), cargo fmt --all -- --check, cargo clippy
--workspace --all-targets --all-features -- -D warnings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@zzylol
zzylol force-pushed the feat/posterior-sketch-error-estimation-239 branch from 10c11a9 to c2ecc53 Compare August 23, 2026 00:40
@zzylol
zzylol merged commit bb0c115 into main Aug 23, 2026
3 checks passed
@zzylol
zzylol deleted the feat/posterior-sketch-error-estimation-239 branch September 21, 2026 18:43
zzylol added a commit that referenced this pull request Oct 21, 2026
Release 0.4.0 (eb04e7f) plus the open sketchlib PRs #245 (check_weight,
try_update_weighted, rescore_heap), #246 (KLL lazy-buffer follow-ups),
#247 (Count Sketch stack medians) and #248
(DdSketch::quantile_interpolated). Moves to a release once they merge.
zzylol added a commit that referenced this pull request Oct 21, 2026
…e_cont do

The DDSketch kernel read sketchlib's nearest rank ceil(q n). PromQL
quantile_over_time and SQL percentile_cont interpolate linearly between
the order statistics at ranks floor(q (n - 1)) and ceil(q (n - 1)), so
answers missed the exact ones by more than alpha: [20, 40] at q = 0.9
read 40 against 38. The kernel now reads
DdSketch::quantile_interpolated (sketchlib #248). An empty sketch still
gives no answer, and a q outside [0, 1] (NaN included) is still an
error; the Planner never sends one, since Pass 1 prunes the sketch
candidate (QuantileOutsideUnitInterval) and the exact plan answers +-Inf.

The Planner's DDSketch rule (ddsketch_relative_error_alpha_v1) states
alpha relative error on the value read and assumes no rank convention;
the interpolated read is within alpha when both neighbours share a sign,
which the sign rule already requires before the bound composes. The
bound is unchanged.

The two integration tests that rebuilt interpolation from nearest-rank
reads now call quantile_interpolated.
zzylol added a commit that referenced this pull request Oct 21, 2026
Release 0.4.0 (eb04e7f) plus the open sketchlib PRs #245 (check_weight,
try_update_weighted, rescore_heap), #246 (KLL lazy-buffer follow-ups),
#247 (Count Sketch stack medians) and #248
(DdSketch::quantile_interpolated). Moves to a release once they merge.
zzylol added a commit that referenced this pull request Oct 21, 2026
…e_cont do

The DDSketch kernel read sketchlib's nearest rank ceil(q n). PromQL
quantile_over_time and SQL percentile_cont interpolate linearly between
the order statistics at ranks floor(q (n - 1)) and ceil(q (n - 1)), so
answers missed the exact ones by more than alpha: [20, 40] at q = 0.9
read 40 against 38. The kernel now reads
DdSketch::quantile_interpolated (sketchlib #248). An empty sketch still
gives no answer, and a q outside [0, 1] (NaN included) is still an
error; the Planner never sends one, since Pass 1 prunes the sketch
candidate (QuantileOutsideUnitInterval) and the exact plan answers +-Inf.

The Planner's DDSketch rule (ddsketch_relative_error_alpha_v1) states
alpha relative error on the value read and assumes no rank convention;
the interpolated read is within alpha when both neighbours share a sign,
which the sign rule already requires before the bound composes. The
bound is unchanged.

The two integration tests that rebuilt interpolation from nearest-rank
reads now call quantile_interpolated.
zzylol added a commit that referenced this pull request Oct 21, 2026
…n sketchlib planner-pin/0.4.0-plus (#803)

* chore: pin asap_sketchlib to planner-pin/0.4.0-plus (63e5655)

Release 0.4.0 (eb04e7f) plus the open sketchlib PRs #245 (check_weight,
try_update_weighted, rescore_heap), #246 (KLL lazy-buffer follow-ups),
#247 (Count Sketch stack medians) and #248
(DdSketch::quantile_interpolated). Moves to a release once they merge.

* fix(executor): DDSketch quantiles interpolate as PromQL and percentile_cont do

The DDSketch kernel read sketchlib's nearest rank ceil(q n). PromQL
quantile_over_time and SQL percentile_cont interpolate linearly between
the order statistics at ranks floor(q (n - 1)) and ceil(q (n - 1)), so
answers missed the exact ones by more than alpha: [20, 40] at q = 0.9
read 40 against 38. The kernel now reads
DdSketch::quantile_interpolated (sketchlib #248). An empty sketch still
gives no answer, and a q outside [0, 1] (NaN included) is still an
error; the Planner never sends one, since Pass 1 prunes the sketch
candidate (QuantileOutsideUnitInterval) and the exact plan answers +-Inf.

The Planner's DDSketch rule (ddsketch_relative_error_alpha_v1) states
alpha relative error on the value read and assumes no rank convention;
the interpolated read is within alpha when both neighbours share a sign,
which the sign rule already requires before the bound composes. The
bound is unchanged.

The two integration tests that rebuilt interpolation from nearest-rank
reads now call quantile_interpolated.

* fix(executor): top-k heaps keep the old kernel's membership

sketchlib's heap rescores only the key an update touches, so a resident
that shares a cell with it keeps a stale count, and a new key could be
admitted against that stale minimum and evict a resident whose current
estimate is higher. The old kernel rescored every candidate after every
update. Each heap update now writes the counters, calls rescore_heap
(sketchlib #245), and only then offers the updated key, so admission
compares against current estimates; O(heap_size) per update, as before.
rows() already reads every retained key at its current estimate, and a
merge re-estimates both heaps' keys, so the readout needs no extra pass.

A reference-model test (written first, failing at update 15 without the
rescore) replays colliding Count-Min and Count Sketch streams and checks
after every update that the retained keys match rescoring every
candidate, up to exact ties.

Weight checks now use sketchlib's check_weight and try_update_weighted:
the counters stay finite per probed cell, as in the old kernel, instead
of the Planner-side bound on the summed weight magnitude, so a merge
checks the merged counters instead.

* test(executor): #802's signed-sample quantiles expect PromQL's interpolated values

* style(executor): rustfmt the DDSketch quantile tests
zzylol pushed a commit that referenced this pull request Oct 29, 2026
main after release 0.4.0 carries #245–#248, which the old pin took from a
branch, plus #249 (`memory_bytes` / `estimated_memory_bytes` across the
sketches; KMV builds without `experimental`) and #251 (the public capability
table). The executor drops the `experimental` feature: KMV was the only gated
type it used.

#251 makes `Kll::merge` panic when k or m differ. The KLL kernel checked
only k, so a state adopted with another m would reach the panic; it now
checks both and returns an error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
zzylol added a commit that referenced this pull request Oct 29, 2026
…, #946, #947) (#989)

* refactor(optimizer): derive Pass 1's summary candidates from the family-capability table

`summary_candidates` was a hand-written intent → family list that repeated
the family-capability table in the other direction (#953). It now takes
the intent's statistic and keeps, in `FAMILY_PREFERENCE` order, the
families whose table row answers it, less `UNIMPLEMENTED_FAMILIES` (Theta)
and less UnivMon for a distinct-tuple count. Candidate lists and their
order are unchanged.

- The intent → statistic mapping is written once, `sketch_statistic`;
  Pass 1's `statistic` wraps it with its existing error.
- `local_guarantee` returns `None` for a sketch read for a statistic its
  table row does not answer. Plain values and exact aggregates are not
  checked: the table lists no readouts for them.
- Two estimator tests read CMS for a distinct count and Count Sketch for
  a total, which the table does not answer; they now read a statistic
  each family answers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* build: pin asap_sketchlib to main 950bb5c

main after release 0.4.0 carries #245–#248, which the old pin took from a
branch, plus #249 (`memory_bytes` / `estimated_memory_bytes` across the
sketches; KMV builds without `experimental`) and #251 (the public capability
table). The executor drops the `experimental` feature: KMV was the only gated
type it used.

#251 makes `Kll::merge` panic when k or m differ. The KLL kernel checked
only k, so a state adopted with another m would reach the panic; it now
checks both and returns an error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(types): read the sketch rows of the family-capability table from asap_sketchlib (#953)

The family-capability table kept its own copy of what each sketch family
answers and how it merges. A sketch family's row is now asap_sketchlib's
(`SketchFamily::capability`, #251), mapped from `SketchAlgorithm` and
`HydraKind` and read in the planner's vocabulary at query time: the seven
statistics `StatisticKind` names, `NoGuarantee` as `Approximate`. The
planner keeps the rows the library has none for: the exact accumulators
and Theta. `readouts` becomes a method; its readers follow.

Cells that change:
- Count-Min and UnivMon totals are `Exact`, not `AsIfBuiltOverUnion`.
  Every consumer accepts both alike.
- HydraKll merges disjoint inputs, its quantile as if built over the
  union: sketchlib's Hydra merge now requires the same k and m and returns
  an error otherwise. Nothing builds a HydraKll yet: Pass 1 offers no
  Hydra and the executor binds only HydraCms.
- UnivMon lists its readouts in sketchlib's order, so its entry in the
  executor's supported summaries does too.

sketchlib also lists DDSketch's total, the heap sketches' item frequency
and HydraCms's total, which nothing here reads yet. `NOT_READ_YET` keeps
them out, so Pass 1's candidates, the IR's estimate validation and the
executor's supported summaries are unchanged; a test checks sketchlib
still lists each entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(executor): charge DDSketch and UnivMon states sketchlib's byte counts (#947)

A DDSketch state was reserved at the trait's flat 4096 bytes whatever it
held, and its updater counted the bins in use; a UnivMon state by a hand
formula (16 bytes per counter, 256 per heap slot). Both now read
asap_sketchlib's count (#249):

- DDSketch: `memory_bytes()`, the bins its stores allocate; the updater
  charges the same.
- UnivMon: its own fields, `UnivMon::estimated_memory_bytes` for its shape
  with keys that own no bytes, and the existing bound on the
  variable-length keys its heaps retain. `memory_bytes()` walks every
  heap, and a build charges memory after every row, so the charge stays
  constant time.

KLL, KMV, Count-Min, Count Sketch (with or without a heap) and HLL already
charge what their sketch holds plus their own fields; Hydra has no
sketchlib count. They are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(plan-selection): price summary state bytes with sketchlib's estimates (#946, #947)

Plan selection priced Count-Min, Count Sketch (with or without a heap),
DDSketch, HLL, KMV and UnivMon states from hand-copied sketchlib
internals: 24 bytes per top-k heap slot (a slot holds 150–225 plus its
key), 8 per UnivMon counter, DDSketch's bucket chunks. Each is now
asap_sketchlib's `estimated_memory_bytes` (#249) for the type the
executor builds (`f64` counters, `Vector2D` storage), given what
`sketch_shape` assumes the state holds:

- Count-Min, Count Sketch, HLL: exact.
- KMV: the rows the state folds as its distinct items; exact only at
  that many distinct inputs, an upper bound below.
- DDSketch: sketchlib's worst case over insertion orders and merges, for
  the rows it folds over the assumed nine decades, with negative values
  unless proven non-negative: 37,360 bytes at α = 0.01 with rows unknown,
  where the old model gave 18,576.
- Count-Min / Count Sketch with heap: a full heap of keys of
  `ASSUMED_HEAP_KEY_BYTES`, an assumption of three 16-byte labels in the
  executor's tuple-key encoding (63 bytes). It applies to these heaps
  only.
- UnivMon: every layer's heap full of keys of `ASSUMED_VALUE_KEY_BYTES`,
  one 16-byte label value with the executor's type tag (17 bytes): Pass 1
  feeds UnivMon one column, which a distinct count over a label makes
  Utf8.

KLL keeps the planner's own formula, unchanged: sketchlib's estimate also
counts a CDF cache the executor never builds. Theta keeps its `k` hashes.
An α outside (0, 1), where sketchlib's DDSketch estimate panics, is
priced as an empty sketch.

A test builds each family the executor builds per group through its
factory, feeds it rows shaped as the pricing assumes, and compares the
executor's charge with the priced bytes plus each accumulator's own
named fields. The heap families' shapes are dominated by their heaps.
- Count-Min, Count Sketch, HLL and KMV: equal.
- KLL: equal up to k rows. Past k only a loose guard holds, at most
  sketchlib's 2 (k - 1) items of compaction scratch over the price; the
  formula can price below the charge there, a known gap until a sketchlib
  estimate without the CDF cache can price KLL.
- DDSketch, the heap sketches and UnivMon: at least the charge, and at
  most 1.5 times it once the state is full. UnivMon's price also bounds
  the bytes its sketch holds, by at most 1.6 times: its estimate
  includes digest-index growth under churn that the test's labels do not
  reach.
HydraCms is left out: plan selection prices an independent Count-Min per
group, the executor one shared grid. Plan selection's tests gain the
executor as a dev-dependency for the test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor: read asap_sketchlib's capability vocabulary directly; one executor support declaration

- SketchAlgorithm, StatisticKind and MergedError are asap_sketchlib's
  (exhaustive, serde); the Planner's copies, the Readouts enum, NOT_READ_YET
  and the conversion matches are gone. FamilyCapability's sketch rows are the
  library's rows, unchanged; the exact accumulators keep Planner rows.
- Theta is deleted (no library sketch), with UNIMPLEMENTED_FAMILIES.
- The executor declares what it evaluates in one positive list,
  EVALUATED_READOUTS, replacing EVALUATED_FAMILIES and UNEVALUATED_READOUTS;
  a test checks every entry is in the table, so a sketchlib bump cannot widen
  the declared support.
- The four hard-coded heap-family TopK checks read answers(TopK); the pcc.rs
  statistic() wrapper is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: drop deleted Theta from KMV docs; name sketchlib #254 in the pin comment

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: zzylol <zeyingz@umd.edu>
Co-authored-by: zzylol <50204836+zzylol@users.noreply.github.com>
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