fix(theta,tuple): treat equal-theta sketches with no retained entries as identical - #246
Open
jaideeppyne wants to merge 1 commit into
Open
fix(theta,tuple): treat equal-theta sketches with no retained entries as identical#246jaideeppyne wants to merge 1 commit into
jaideeppyne wants to merge 1 commit into
Conversation
… as identical
Two non-empty sketches that share a theta and retain no entries carry the
same state, and exactly_equal already reports them as equal. The Jaccard
identity check skipped them because the union retains nothing, so compute
fell through to the ratio bounds and returned {0.0, 0.5, 1.0}.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes Jaccard similarity for Theta and Tuple sketches in the “non-empty but zero retained entries” state: when both inputs share the same theta and retain no entries, compute now returns an exact similarity of 1.0, aligning with exactly_equal and cross-language behavior.
Changes:
- Remove the
!union.entries.is_empty()guard so the identical-set shortcut applies even when both sketches retain zero entries. - Split/extend integration tests to cover: equal-theta (identical), different-theta (uncertain interval), and self-similarity for the zero-retained state.
- Document the user-visible behavior change in
CHANGELOG.md.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| datasketches/src/thetafamily/common/jaccard_similarity.rs | Removes the union-non-empty gate so equal-theta/zero-retained sketches return exact 1.0. |
| tests-integration/tests/theta_test/jaccard_similarity.rs | Updates tests to validate equal-theta identity and different-theta uncertainty for zero-retained sketches. |
| tests-integration/tests/tuple_test/jaccard_similarity.rs | Mirrors the theta test changes for tuple sketches, including self-similarity coverage. |
| CHANGELOG.md | Adds an Unreleased bug-fix entry describing the corrected Jaccard behavior. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
220
to
225
| sketch_a.update("apple"); | ||
| sketch_b.update("banana"); | ||
| different_theta.update("orange"); | ||
|
|
||
| assert!(!sketch_a.is_empty()); | ||
| assert!(!sketch_b.is_empty()); | ||
| assert_eq!(sketch_a.num_retained(), 0); | ||
| assert_eq!(sketch_b.num_retained(), 0); | ||
| assert_eq!(different_theta.num_retained(), 0); | ||
|
|
Comment on lines
+170
to
+175
| sketch_a.update("apple", 1u64); | ||
| different_theta.update("orange", 1u64); | ||
|
|
||
| assert_eq!(sketch_a.num_retained(), 0); | ||
| assert_eq!(different_theta.num_retained(), 0); | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ThetaJaccardSimilarity::computeandTupleJaccardSimilarity::computereturn{0.0, 0.5, 1.0}instead of1.0when both inputs are non-empty, share a theta, and retain no entries. You get that state from a low sampling probability. So a sketch is not similar to itself:exactly_equalalready returnstruefor that same pair, so the two operators contradict each other today.The cause is the extra
!union.entries.is_empty() &&guard in front of the sharedidentical_setscheck. When both inputs retain nothing the union retains nothing, the guard skips the identity shortcut, and it falls through to the ratio bounds with a union count of zero. C++ has no such guard injaccard_similarity_base::jaccard. Dropping it is the whole fix.I checked what C++ 5.2.0 actually returns before touching anything:
{1, 1, 1}{0, 0.5, 1}{1, 1, 1}{0, 0.5, 1}{0, 0.5, 1}{0, 0.5, 1}The C++ side was two separately deserialized objects, not the same object twice, so this is not the
&sketch_a == &sketch_bshortcut.That means the existing
test_distinct_non_empty_sketches_with_no_retained_entries_are_uncertainin both the theta and tuple suites asserted the wrong value. Its two sketches both usedsampling_probability(1e-12), so they had equal theta and were not actually distinct, and the test also assertedexactly_equal == trueright below the0.5estimate. I split it: the equal-theta pair now asserts1.0, and a new test keeps the uncertain{0, 0.5, 1}branch using the genuinely different theta (1e-12vs2e-12), which is the case C++ agrees is uncertain. Third test covers a sketch against itself.How I found it
pip install datasketchesgives the C++ core through Python. I ran the same operation in both engines over identical serialized inputs and diffed the numbers, rather than comparing serialized results.1700 randomized theta cases (random
lg_kper input and per union,nfrom 0 to 40k, overlapping and disjoint key ranges, sampling probability in{1, 0.5, 0.1, 1e-9, 1e-12}), comparingnum_retained,theta64,estimate,is_empty,is_estimation_mode, lower and upper bounds at 1/2/3 std dev for each of A, B, union, intersection, A-not-B, plus the Jaccard triple andexactly_equal. 654 of those cases hit the non-empty-with-zero-retained state. Everything else in theta matched exactly; the Jaccard triple was the only divergence, and it is 0 after this change.I ran the same harness over CPC (250 cases, unions across mismatched
lg_k, all flavors) and T-Digest (250 cases, mismatchedk, merges in both orders and 8-deep merge chains) and found no divergence, so this PR is only about Jaccard.Both new tests fail on
c8b20c8with the test change alone and pass with the source change.cargo x testandcargo x lintare clean.Claude Code wrote the harness and the patch under my review.