From 19f4e35f2d0d09fd43df7866776cfafb72fb44b7 Mon Sep 17 00:00:00 2001 From: Rusty Conover Date: Wed, 28 Oct 2026 16:01:35 -0400 Subject: [PATCH 1/2] fix: density_sketch did not work with a custom allocator gaussian_kernel only accepted std::vector, but the sketch's points are std::vector, so a sketch with any other allocator did not compile (get_estimate and compaction call the kernel on them). The kernel now accepts any two vectors of T. The sketch also had an unused Allocator member that neither constructor initialized, so it was always default-constructed, which fails for allocators that need an instance. It is removed; get_allocator() already returns the levels' allocator. Co-Authored-By: Claude Opus 5.5 --- density/include/density_sketch.hpp | 5 +++-- density/test/density_sketch_test.cpp | 29 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/density/include/density_sketch.hpp b/density/include/density_sketch.hpp index e5674c6c..57f29eaf 100755 --- a/density/include/density_sketch.hpp +++ b/density/include/density_sketch.hpp @@ -32,7 +32,9 @@ namespace datasketches { template struct gaussian_kernel { - T operator()(const std::vector& v1, const std::vector& v2) const { + // any two vectors of T, so the sketch's points can use its allocator + template + T operator()(const V1& v1, const V2& v2) const { return exp(-std::inner_product(v1.begin(), v1.end(), v2.begin(), 0.0, std::plus(), [](T a, T b){return (a-b)*(a-b);})); } }; @@ -203,7 +205,6 @@ class density_sketch { static const uint8_t SERIAL_VERSION = 1; static const size_t LEVELS_ARRAY_START = 5; - Allocator allocator_; Kernel kernel_; uint16_t k_; uint32_t dim_; diff --git a/density/test/density_sketch_test.cpp b/density/test/density_sketch_test.cpp index 2a09d529..fba6e955 100755 --- a/density/test/density_sketch_test.cpp +++ b/density/test/density_sketch_test.cpp @@ -21,6 +21,7 @@ #include #include +#include namespace datasketches { @@ -241,4 +242,32 @@ TEST_CASE("serialize stream", "[density_sketch]") { } } +TEST_CASE("density sketch: custom allocator", "[density_sketch]") { + using alloc = test_allocator; + using sketch = density_sketch, alloc>; + using point = std::vector; + test_allocator_total_bytes = 0; + test_allocator_net_allocations = 0; + { + sketch s(10, 3, gaussian_kernel(), alloc(0)); + for (int i = 0; i < 1000; ++i) s.update(point({static_cast(i), 1.0, 2.0}, alloc(0))); + REQUIRE(s.get_n() == 1000); + REQUIRE(s.is_estimation_mode()); + REQUIRE(s.get_estimate({500.0, 1.0, 2.0}) > 0); + REQUIRE(test_allocator_total_bytes > 0); + + sketch other(10, 3, gaussian_kernel(), alloc(0)); + for (int i = 0; i < 100; ++i) other.update(point({static_cast(i), 1.0, 2.0}, alloc(0))); + s.merge(other); + REQUIRE(s.get_n() == 1100); + + auto bytes = s.serialize(); + auto copy = sketch::deserialize(bytes.data(), bytes.size(), gaussian_kernel(), alloc(0)); + REQUIRE(copy.get_n() == s.get_n()); + REQUIRE(copy.get_estimate({500.0, 1.0, 2.0}) == s.get_estimate({500.0, 1.0, 2.0})); + } + REQUIRE(test_allocator_total_bytes == 0); + REQUIRE(test_allocator_net_allocations == 0); +} + } /* namespace datasketches */ From dd37b4fb7c67ff00aa0300c403fc09bf34b56bae Mon Sep 17 00:00:00 2001 From: Rusty Conover Date: Thu, 29 Oct 2026 12:06:02 -0400 Subject: [PATCH 2/2] test: avoid density allocator fixture underflow --- density/test/density_sketch_test.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/density/test/density_sketch_test.cpp b/density/test/density_sketch_test.cpp index fba6e955..f8c5d528 100755 --- a/density/test/density_sketch_test.cpp +++ b/density/test/density_sketch_test.cpp @@ -250,21 +250,22 @@ TEST_CASE("density sketch: custom allocator", "[density_sketch]") { test_allocator_net_allocations = 0; { sketch s(10, 3, gaussian_kernel(), alloc(0)); - for (int i = 0; i < 1000; ++i) s.update(point({static_cast(i), 1.0, 2.0}, alloc(0))); + // Keep every retained point close to the query so the Gaussian kernel cannot underflow to zero. + for (int i = 0; i < 1000; ++i) s.update(point({i / 1000.0, 1.0, 2.0}, alloc(0))); REQUIRE(s.get_n() == 1000); REQUIRE(s.is_estimation_mode()); - REQUIRE(s.get_estimate({500.0, 1.0, 2.0}) > 0); + REQUIRE(s.get_estimate({0.5, 1.0, 2.0}) > 0); REQUIRE(test_allocator_total_bytes > 0); sketch other(10, 3, gaussian_kernel(), alloc(0)); - for (int i = 0; i < 100; ++i) other.update(point({static_cast(i), 1.0, 2.0}, alloc(0))); + for (int i = 0; i < 100; ++i) other.update(point({i / 1000.0, 1.0, 2.0}, alloc(0))); s.merge(other); REQUIRE(s.get_n() == 1100); auto bytes = s.serialize(); auto copy = sketch::deserialize(bytes.data(), bytes.size(), gaussian_kernel(), alloc(0)); REQUIRE(copy.get_n() == s.get_n()); - REQUIRE(copy.get_estimate({500.0, 1.0, 2.0}) == s.get_estimate({500.0, 1.0, 2.0})); + REQUIRE(copy.get_estimate({0.5, 1.0, 2.0}) == s.get_estimate({0.5, 1.0, 2.0})); } REQUIRE(test_allocator_total_bytes == 0); REQUIRE(test_allocator_net_allocations == 0);