You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datasketches.apache.org by ti...@apache.org on 2026/09/02 04:22:55 UTC
(datasketches-rust) 04/10: refactor(kll): align quantile query APIs
This is an automated email from the ASF dual-hosted git repository.
tisonkun pushed a commit to branch codex/kll-api-and-performance
in repository https://gitbox.apache.org/repos/asf/datasketches-rust.git
commit 1610ba7ed8ce79861935f70296afeac7505e5741
Author: tison <wa...@gmail.com>
AuthorDate: Wed Sep 2 12:11:54 2026 +0800
refactor(kll): align quantile query APIs
---
datasketches/src/common/mod.rs | 2 +
.../src/common/{mod.rs => search_criteria.rs} | 18 +--
datasketches/src/kll/mod.rs | 5 +-
datasketches/src/kll/sketch.rs | 66 +++++---
datasketches/src/kll/sorted_view.rs | 51 +++---
datasketches/src/req/mod.rs | 11 +-
tests-integration/tests/kll_test/sketch.rs | 177 ++++++++++++++-------
7 files changed, 214 insertions(+), 116 deletions(-)
diff --git a/datasketches/src/common/mod.rs b/datasketches/src/common/mod.rs
index 6d4c6c6..918c57b 100644
--- a/datasketches/src/common/mod.rs
+++ b/datasketches/src/common/mod.rs
@@ -19,8 +19,10 @@
mod num_std_dev;
mod resize;
+mod search_criteria;
pub use self::num_std_dev::NumStdDev;
pub use self::resize::ResizeFactor;
+pub use self::search_criteria::SearchCriteria;
#[cfg(any(feature = "cpc", feature = "hll"))]
pub(crate) mod inv_pow2;
diff --git a/datasketches/src/common/mod.rs b/datasketches/src/common/search_criteria.rs
similarity index 68%
copy from datasketches/src/common/mod.rs
copy to datasketches/src/common/search_criteria.rs
index 6d4c6c6..7f480c7 100644
--- a/datasketches/src/common/mod.rs
+++ b/datasketches/src/common/search_criteria.rs
@@ -15,12 +15,12 @@
// specific language governing permissions and limitations
// under the License.
-//! Data structures and functions that may be used across all the sketch families.
-
-mod num_std_dev;
-mod resize;
-pub use self::num_std_dev::NumStdDev;
-pub use self::resize::ResizeFactor;
-
-#[cfg(any(feature = "cpc", feature = "hll"))]
-pub(crate) mod inv_pow2;
+/// Selects the rank definition used by rank, quantile, PMF, and CDF queries.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub enum SearchCriteria {
+ /// Define rank as the fraction of values less than or equal to the boundary.
+ #[default]
+ Inclusive,
+ /// Define rank as the fraction of values strictly less than the boundary.
+ Exclusive,
+}
diff --git a/datasketches/src/kll/mod.rs b/datasketches/src/kll/mod.rs
index cf7679d..8b8e563 100644
--- a/datasketches/src/kll/mod.rs
+++ b/datasketches/src/kll/mod.rs
@@ -28,11 +28,11 @@
//! # Usage
//!
//! ```rust
-//! # use datasketches::kll::KllSketch;
+//! # use datasketches::kll::{KllSketch, SearchCriteria};
//! let mut sketch = KllSketch::<f64>::new(200).unwrap();
//! sketch.update(1.0);
//! sketch.update(2.0);
-//! let q = sketch.quantile(0.5, true).unwrap();
+//! let q = sketch.quantile(0.5, SearchCriteria::Inclusive).unwrap();
//! assert!(q >= 1.0 && q <= 2.0);
//! ```
@@ -47,6 +47,7 @@ pub use self::order::KllComparator;
pub use self::order::NaturalOrder;
pub use self::sketch::KllSketch;
pub use self::value::KllValue;
+pub use crate::common::SearchCriteria;
/// Default value of parameter k.
const DEFAULT_K: u16 = 200;
diff --git a/datasketches/src/kll/sketch.rs b/datasketches/src/kll/sketch.rs
index 8153bd0..ae9045f 100644
--- a/datasketches/src/kll/sketch.rs
+++ b/datasketches/src/kll/sketch.rs
@@ -43,6 +43,7 @@ use crate::codec::SketchSlice;
use crate::codec::assert::ensure_serial_version_is;
use crate::codec::assert::insufficient_data;
use crate::codec::family::Family;
+use crate::common::SearchCriteria;
use crate::error::Error;
/// KLL sketch for estimating quantiles and ranks.
@@ -217,49 +218,78 @@ impl<T: Clone, C: KllComparator<T>> KllSketch<T, C> {
}
/// Returns the normalized rank of the given item.
- pub fn rank(&self, item: &T, inclusive: bool) -> Option<f64> {
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the sketch is empty or `item` is outside the comparator's ordered
+ /// domain.
+ pub fn rank(&self, item: &T, criteria: SearchCriteria) -> Result<f64, Error> {
if self.is_empty() {
- return None;
+ return Err(Error::invalid_argument("cannot query an empty sketch"));
+ }
+ if !self.comparator.accepts(item) {
+ return Err(Error::invalid_argument(
+ "item must belong to the comparator's ordered domain",
+ ));
}
let view = build_sorted_view(&self.levels, self.comparator.clone());
- Some(view.rank(item, inclusive))
+ Ok(view.rank(item, criteria))
}
/// Returns the quantile for the given normalized rank.
///
- /// # Panics
+ /// # Errors
///
- /// Panics if rank is not in [0.0, 1.0].
- pub fn quantile(&self, rank: f64, inclusive: bool) -> Option<T> {
+ /// Returns an error if the sketch is empty or `rank` is outside `[0.0, 1.0]`.
+ pub fn quantile(&self, rank: f64, criteria: SearchCriteria) -> Result<T, Error> {
if self.is_empty() {
- return None;
+ return Err(Error::invalid_argument("cannot query an empty sketch"));
+ }
+ if !(0.0..=1.0).contains(&rank) {
+ return Err(Error::invalid_argument(format!(
+ "rank must be in [0.0, 1.0], got {rank}"
+ )));
}
- assert!((0.0..=1.0).contains(&rank), "rank must be in [0.0, 1.0]");
let view = build_sorted_view(&self.levels, self.comparator.clone());
- Some(view.quantile(rank, inclusive))
+ Ok(view.quantile(rank, criteria))
}
/// Returns the approximate CDF for the given split points.
- pub fn cdf(&self, split_points: &[T], inclusive: bool) -> Option<Vec<f64>> {
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the sketch is empty, a split point is outside the comparator's ordered
+ /// domain, or the split points are not unique and strictly increasing.
+ pub fn cdf(&self, split_points: &[T], criteria: SearchCriteria) -> Result<Vec<f64>, Error> {
if self.is_empty() {
- return None;
+ return Err(Error::invalid_argument("cannot query an empty sketch"));
}
let view = build_sorted_view(&self.levels, self.comparator.clone());
- Some(view.cdf(split_points, inclusive))
+ view.cdf(split_points, criteria)
}
/// Returns the approximate PMF for the given split points.
- pub fn pmf(&self, split_points: &[T], inclusive: bool) -> Option<Vec<f64>> {
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the sketch is empty, a split point is outside the comparator's ordered
+ /// domain, or the split points are not unique and strictly increasing.
+ pub fn pmf(&self, split_points: &[T], criteria: SearchCriteria) -> Result<Vec<f64>, Error> {
if self.is_empty() {
- return None;
+ return Err(Error::invalid_argument("cannot query an empty sketch"));
}
let view = build_sorted_view(&self.levels, self.comparator.clone());
- Some(view.pmf(split_points, inclusive))
+ view.pmf(split_points, criteria)
+ }
+
+ /// Returns the normalized single-sided rank error for the configured k.
+ pub fn normalized_rank_error(&self) -> f64 {
+ normalized_rank_error(self.min_k, false)
}
- /// Returns normalized rank error for the configured k.
- pub fn normalized_rank_error(&self, pmf: bool) -> f64 {
- normalized_rank_error(self.min_k, pmf)
+ /// Returns the normalized double-sided rank error for PMF queries for the configured k.
+ pub fn normalized_pmf_error(&self) -> f64 {
+ normalized_rank_error(self.min_k, true)
}
}
diff --git a/datasketches/src/kll/sorted_view.rs b/datasketches/src/kll/sorted_view.rs
index 4ab8838..5c8cd5b 100644
--- a/datasketches/src/kll/sorted_view.rs
+++ b/datasketches/src/kll/sorted_view.rs
@@ -18,6 +18,8 @@
use std::cmp::Ordering;
use super::order::KllComparator;
+use crate::common::SearchCriteria;
+use crate::error::Error;
#[derive(Debug, Clone)]
pub struct SortedView<T: Clone, C: KllComparator<T>> {
@@ -47,12 +49,12 @@ impl<T: Clone, C: KllComparator<T>> SortedView<T, C> {
}
}
- pub fn rank(&self, item: &T, inclusive: bool) -> f64 {
+ pub fn rank(&self, item: &T, criteria: SearchCriteria) -> f64 {
if self.entries.is_empty() {
return 0.0;
}
- let idx = if inclusive {
+ let idx = if criteria == SearchCriteria::Inclusive {
upper_bound(&self.entries, item, &self.comparator)
} else {
lower_bound(&self.entries, item, &self.comparator)
@@ -65,14 +67,14 @@ impl<T: Clone, C: KllComparator<T>> SortedView<T, C> {
weight as f64 / self.total_weight as f64
}
- pub fn quantile(&self, rank: f64, inclusive: bool) -> T {
- let weight = if inclusive {
+ pub fn quantile(&self, rank: f64, criteria: SearchCriteria) -> T {
+ let weight = if criteria == SearchCriteria::Inclusive {
(rank * self.total_weight as f64).ceil() as u64
} else {
(rank * self.total_weight as f64) as u64
};
- let idx = if inclusive {
+ let idx = if criteria == SearchCriteria::Inclusive {
lower_bound_by_weight(&self.entries, weight)
} else {
upper_bound_by_weight(&self.entries, weight)
@@ -84,22 +86,22 @@ impl<T: Clone, C: KllComparator<T>> SortedView<T, C> {
self.entries[idx].item.clone()
}
- pub fn cdf(&self, split_points: &[T], inclusive: bool) -> Vec<f64> {
- check_split_points(split_points, &self.comparator);
+ pub fn cdf(&self, split_points: &[T], criteria: SearchCriteria) -> Result<Vec<f64>, Error> {
+ check_split_points(split_points, &self.comparator)?;
let mut ranks = Vec::with_capacity(split_points.len() + 1);
for item in split_points {
- ranks.push(self.rank(item, inclusive));
+ ranks.push(self.rank(item, criteria));
}
ranks.push(1.0);
- ranks
+ Ok(ranks)
}
- pub fn pmf(&self, split_points: &[T], inclusive: bool) -> Vec<f64> {
- let mut buckets = self.cdf(split_points, inclusive);
+ pub fn pmf(&self, split_points: &[T], criteria: SearchCriteria) -> Result<Vec<f64>, Error> {
+ let mut buckets = self.cdf(split_points, criteria)?;
for i in (1..buckets.len()).rev() {
buckets[i] -= buckets[i - 1];
}
- buckets
+ Ok(buckets)
}
}
@@ -123,18 +125,23 @@ pub fn build_sorted_view<T: Clone, C: KllComparator<T>>(
SortedView::new(entries, comparator)
}
-#[track_caller]
-fn check_split_points<T, C: KllComparator<T>>(split_points: &[T], comparator: &C) {
- assert!(
- split_points.iter().all(|point| comparator.accepts(point)),
- "split_points must belong to the comparator's ordered domain"
- );
+fn check_split_points<T, C: KllComparator<T>>(
+ split_points: &[T],
+ comparator: &C,
+) -> Result<(), Error> {
+ if !split_points.iter().all(|point| comparator.accepts(point)) {
+ return Err(Error::invalid_argument(
+ "split points must belong to the comparator's ordered domain",
+ ));
+ }
for pair in split_points.windows(2) {
- assert!(
- comparator.compare(&pair[0], &pair[1]) == Ordering::Less,
- "split_points must be unique and monotonically increasing"
- );
+ if comparator.compare(&pair[0], &pair[1]) != Ordering::Less {
+ return Err(Error::invalid_argument(
+ "split points must be unique and monotonically increasing",
+ ));
+ }
}
+ Ok(())
}
fn lower_bound<T, C: KllComparator<T>>(entries: &[Entry<T>], item: &T, comparator: &C) -> usize {
diff --git a/datasketches/src/req/mod.rs b/datasketches/src/req/mod.rs
index 8714f5e..41d8f40 100644
--- a/datasketches/src/req/mod.rs
+++ b/datasketches/src/req/mod.rs
@@ -61,6 +61,7 @@ pub use self::sketch::ReqSketch;
pub use self::sorted_view::SortedView;
pub use self::value::ReqFloat;
pub use self::value::ReqValue;
+pub use crate::common::SearchCriteria;
/// Default value of `k` if not specified. Roughly 1% relative error at 95% confidence.
const DEFAULT_K: u16 = 12;
@@ -79,16 +80,6 @@ pub enum RankAccuracy {
LowRank,
}
-/// Selects the rank definition used by rank, quantile, PMF, and CDF queries.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
-pub enum SearchCriteria {
- /// Define rank as the fraction of values less than or equal to the boundary.
- #[default]
- Inclusive,
- /// Define rank as the fraction of values strictly less than the boundary.
- Exclusive,
-}
-
/// Number of sections in a newly created compactor. The section count and size
/// determine its capacity and compaction range; the count doubles as its state grows.
const INITIAL_SECTIONS_PER_COMPACTOR: u8 = 3;
diff --git a/tests-integration/tests/kll_test/sketch.rs b/tests-integration/tests/kll_test/sketch.rs
index 7fcca7e..afc5090 100644
--- a/tests-integration/tests/kll_test/sketch.rs
+++ b/tests-integration/tests/kll_test/sketch.rs
@@ -20,6 +20,7 @@ use std::cmp::Ordering;
use datasketches::error::ErrorKind;
use datasketches::kll::KllComparator;
use datasketches::kll::KllSketch;
+use datasketches::kll::SearchCriteria;
const DEFAULT_K: u16 = 200;
const MIN_K: u16 = 8;
@@ -35,7 +36,7 @@ fn assert_approx_eq(actual: f64, expected: f64, tolerance: f64) {
}
fn rank_eps(sketch: &KllSketch<f32>) -> f64 {
- sketch.normalized_rank_error(false)
+ sketch.normalized_rank_error()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -74,18 +75,20 @@ fn test_empty() {
assert_eq!(sketch.num_retained(), 0);
assert!(sketch.min_item().is_none());
assert!(sketch.max_item().is_none());
- assert!(sketch.rank(&0.0, true).is_none());
- assert!(sketch.quantile(0.5, true).is_none());
- assert!(sketch.pmf(&[0.0f32], true).is_none());
- assert!(sketch.cdf(&[0.0f32], true).is_none());
+ assert!(sketch.rank(&0.0, SearchCriteria::Inclusive).is_err());
+ assert!(sketch.quantile(0.5, SearchCriteria::Inclusive).is_err());
+ assert!(sketch.pmf(&[0.0f32], SearchCriteria::Inclusive).is_err());
+ assert!(sketch.cdf(&[0.0f32], SearchCriteria::Inclusive).is_err());
}
#[test]
-#[should_panic(expected = "rank must be in [0.0, 1.0]")]
-fn test_quantile_out_of_range_panics() {
+fn test_quantile_out_of_range_returns_error() {
let mut sketch = KllSketch::<f32>::new(DEFAULT_K).unwrap();
sketch.update(0.0);
- sketch.quantile(-1.0, true);
+ let error = sketch
+ .quantile(-1.0, SearchCriteria::Inclusive)
+ .unwrap_err();
+ assert_eq!(error.kind(), ErrorKind::InvalidArgument);
}
#[test]
@@ -96,12 +99,15 @@ fn test_one_item() {
assert!(!sketch.is_estimation_mode());
assert_eq!(sketch.n(), 1);
assert_eq!(sketch.num_retained(), 1);
- assert_eq!(sketch.rank(&1.0, false), Some(0.0));
- assert_eq!(sketch.rank(&1.0, true), Some(1.0));
- assert_eq!(sketch.rank(&2.0, false), Some(1.0));
+ assert_eq!(sketch.rank(&1.0, SearchCriteria::Exclusive).unwrap(), 0.0);
+ assert_eq!(sketch.rank(&1.0, SearchCriteria::Inclusive).unwrap(), 1.0);
+ assert_eq!(sketch.rank(&2.0, SearchCriteria::Exclusive).unwrap(), 1.0);
assert_eq!(sketch.min_item().cloned(), Some(1.0));
assert_eq!(sketch.max_item().cloned(), Some(1.0));
- assert_eq!(sketch.quantile(0.5, true), Some(1.0));
+ assert_eq!(
+ sketch.quantile(0.5, SearchCriteria::Inclusive).unwrap(),
+ 1.0
+ );
}
#[test]
@@ -111,12 +117,18 @@ fn test_duplicate_items_follow_inclusive_and_exclusive_semantics() {
sketch.update(item);
}
- assert_eq!(sketch.rank(&1.0, false), Some(0.0));
- assert_eq!(sketch.rank(&1.0, true), Some(0.5));
- assert_eq!(sketch.rank(&2.0, false), Some(0.5));
- assert_eq!(sketch.rank(&2.0, true), Some(1.0));
- assert_eq!(sketch.quantile(0.5, true), Some(1.0));
- assert_eq!(sketch.quantile(0.5, false), Some(2.0));
+ assert_eq!(sketch.rank(&1.0, SearchCriteria::Exclusive).unwrap(), 0.0);
+ assert_eq!(sketch.rank(&1.0, SearchCriteria::Inclusive).unwrap(), 0.5);
+ assert_eq!(sketch.rank(&2.0, SearchCriteria::Exclusive).unwrap(), 0.5);
+ assert_eq!(sketch.rank(&2.0, SearchCriteria::Inclusive).unwrap(), 1.0);
+ assert_eq!(
+ sketch.quantile(0.5, SearchCriteria::Inclusive).unwrap(),
+ 1.0
+ );
+ assert_eq!(
+ sketch.quantile(0.5, SearchCriteria::Exclusive).unwrap(),
+ 2.0
+ );
}
#[test]
@@ -141,15 +153,27 @@ fn test_many_items_exact_mode() {
assert!(!sketch.is_estimation_mode());
assert_eq!(sketch.num_retained(), n);
assert_eq!(sketch.min_item().cloned(), Some(1.0));
- assert_eq!(sketch.quantile(0.0, true), Some(1.0));
+ assert_eq!(
+ sketch.quantile(0.0, SearchCriteria::Inclusive).unwrap(),
+ 1.0
+ );
assert_eq!(sketch.max_item().cloned(), Some(n as f32));
- assert_eq!(sketch.quantile(1.0, true), Some(n as f32));
+ assert_eq!(
+ sketch.quantile(1.0, SearchCriteria::Inclusive).unwrap(),
+ n as f32
+ );
for i in 1..=n {
let inclusive_rank = i as f64 / n as f64;
- assert_eq!(sketch.rank(&(i as f32), true), Some(inclusive_rank));
+ assert_eq!(
+ sketch.rank(&(i as f32), SearchCriteria::Inclusive).unwrap(),
+ inclusive_rank
+ );
let exclusive_rank = (i - 1) as f64 / n as f64;
- assert_eq!(sketch.rank(&(i as f32), false), Some(exclusive_rank));
+ assert_eq!(
+ sketch.rank(&(i as f32), SearchCriteria::Exclusive).unwrap(),
+ exclusive_rank
+ );
}
}
@@ -159,10 +183,22 @@ fn test_ten_items_quantiles() {
for i in 1..=10 {
sketch.update(i as f32);
}
- assert_eq!(sketch.quantile(0.0, true), Some(1.0));
- assert_eq!(sketch.quantile(0.5, true), Some(5.0));
- assert_eq!(sketch.quantile(0.99, true), Some(10.0));
- assert_eq!(sketch.quantile(1.0, true), Some(10.0));
+ assert_eq!(
+ sketch.quantile(0.0, SearchCriteria::Inclusive).unwrap(),
+ 1.0
+ );
+ assert_eq!(
+ sketch.quantile(0.5, SearchCriteria::Inclusive).unwrap(),
+ 5.0
+ );
+ assert_eq!(
+ sketch.quantile(0.99, SearchCriteria::Inclusive).unwrap(),
+ 10.0
+ );
+ assert_eq!(
+ sketch.quantile(1.0, SearchCriteria::Inclusive).unwrap(),
+ 10.0
+ );
}
#[test]
@@ -171,11 +207,26 @@ fn test_hundred_items_quantiles() {
for i in 0..100 {
sketch.update(i as f32);
}
- assert_eq!(sketch.quantile(0.0, true), Some(0.0));
- assert_eq!(sketch.quantile(0.01, true), Some(0.0));
- assert_eq!(sketch.quantile(0.5, true), Some(49.0));
- assert_eq!(sketch.quantile(0.99, true), Some(98.0));
- assert_eq!(sketch.quantile(1.0, true), Some(99.0));
+ assert_eq!(
+ sketch.quantile(0.0, SearchCriteria::Inclusive).unwrap(),
+ 0.0
+ );
+ assert_eq!(
+ sketch.quantile(0.01, SearchCriteria::Inclusive).unwrap(),
+ 0.0
+ );
+ assert_eq!(
+ sketch.quantile(0.5, SearchCriteria::Inclusive).unwrap(),
+ 49.0
+ );
+ assert_eq!(
+ sketch.quantile(0.99, SearchCriteria::Inclusive).unwrap(),
+ 98.0
+ );
+ assert_eq!(
+ sketch.quantile(1.0, SearchCriteria::Inclusive).unwrap(),
+ 99.0
+ );
}
#[test]
@@ -193,7 +244,7 @@ fn test_many_items_estimation_mode_rank_error() {
let rank_eps = rank_eps(&sketch);
for i in (0..n).step_by(10) {
let true_rank = i as f64 / n as f64;
- let rank = sketch.rank(&(i as f32), false).unwrap();
+ let rank = sketch.rank(&(i as f32), SearchCriteria::Exclusive).unwrap();
assert_approx_eq(rank, true_rank, rank_eps);
}
@@ -210,12 +261,12 @@ fn test_rank_cdf_pmf_consistency() {
values.push(i as f32);
}
- let ranks = sketch.cdf(&values, false).unwrap();
- let pmf = sketch.pmf(&values, false).unwrap();
+ let ranks = sketch.cdf(&values, SearchCriteria::Exclusive).unwrap();
+ let pmf = sketch.pmf(&values, SearchCriteria::Exclusive).unwrap();
let mut subtotal = 0.0;
for i in 0..n {
- let rank = sketch.rank(&values[i], false).unwrap();
+ let rank = sketch.rank(&values[i], SearchCriteria::Exclusive).unwrap();
assert_eq!(rank, ranks[i]);
subtotal += pmf[i];
assert!(
@@ -224,12 +275,12 @@ fn test_rank_cdf_pmf_consistency() {
);
}
- let ranks = sketch.cdf(&values, true).unwrap();
- let pmf = sketch.pmf(&values, true).unwrap();
+ let ranks = sketch.cdf(&values, SearchCriteria::Inclusive).unwrap();
+ let pmf = sketch.pmf(&values, SearchCriteria::Inclusive).unwrap();
let mut subtotal = 0.0;
for i in 0..n {
- let rank = sketch.rank(&values[i], true).unwrap();
+ let rank = sketch.rank(&values[i], SearchCriteria::Inclusive).unwrap();
assert_eq!(rank, ranks[i]);
subtotal += pmf[i];
assert!(
@@ -240,21 +291,25 @@ fn test_rank_cdf_pmf_consistency() {
}
#[test]
-#[should_panic(expected = "split_points must be unique and monotonically increasing")]
-fn test_out_of_order_split_points_panics() {
+fn test_out_of_order_split_points_return_error() {
let mut sketch = KllSketch::<f32>::new(DEFAULT_K).unwrap();
sketch.update(0.0);
let split_points = [1.0, 0.0];
- let _ = sketch.cdf(&split_points, true);
+ let error = sketch
+ .cdf(&split_points, SearchCriteria::Inclusive)
+ .unwrap_err();
+ assert_eq!(error.kind(), ErrorKind::InvalidArgument);
}
#[test]
-#[should_panic(expected = "split_points must belong to the comparator's ordered domain")]
-fn test_nan_split_point_panics() {
+fn test_nan_split_point_returns_error() {
let mut sketch = KllSketch::<f32>::new(DEFAULT_K).unwrap();
sketch.update(0.0);
let split_points = [f32::NAN];
- let _ = sketch.cdf(&split_points, true);
+ let error = sketch
+ .cdf(&split_points, SearchCriteria::Inclusive)
+ .unwrap_err();
+ assert_eq!(error.kind(), ErrorKind::InvalidArgument);
}
#[test]
@@ -278,7 +333,7 @@ fn test_merge() {
assert_eq!(sketch1.n(), (2 * n) as u64);
assert_eq!(sketch1.min_item().cloned(), Some(0.0));
assert_eq!(sketch1.max_item().cloned(), Some((2 * n - 1) as f32));
- let median = sketch1.quantile(0.5, true).unwrap();
+ let median = sketch1.quantile(0.5, SearchCriteria::Inclusive).unwrap();
let rank_eps = rank_eps(&sketch1);
assert_approx_eq(median as f64, n as f64, n as f64 * rank_eps);
}
@@ -299,14 +354,14 @@ fn test_merge_lower_k() {
assert_eq!(sketch1.min_item().cloned(), Some(0.0));
assert_eq!(sketch1.max_item().cloned(), Some((2 * n - 1) as f32));
assert_eq!(
- sketch1.normalized_rank_error(false),
- sketch2.normalized_rank_error(false)
+ sketch1.normalized_rank_error(),
+ sketch2.normalized_rank_error()
);
assert_eq!(
- sketch1.normalized_rank_error(true),
- sketch2.normalized_rank_error(true)
+ sketch1.normalized_pmf_error(),
+ sketch2.normalized_pmf_error()
);
- let median = sketch1.quantile(0.5, true).unwrap();
+ let median = sketch1.quantile(0.5, SearchCriteria::Inclusive).unwrap();
let rank_eps = rank_eps(&sketch1);
assert_approx_eq(median as f64, n as f64, n as f64 * rank_eps);
}
@@ -320,14 +375,14 @@ fn test_merge_exact_mode_lower_k() {
sketch1.update(i as f32);
}
- let err_before = sketch1.normalized_rank_error(true);
+ let err_before = sketch1.normalized_pmf_error();
sketch1.merge(&sketch2);
- assert_eq!(sketch1.normalized_rank_error(true), err_before);
+ assert_eq!(sketch1.normalized_pmf_error(), err_before);
assert_eq!(sketch1.n(), n as u64);
assert_eq!(sketch1.min_item().cloned(), Some(0.0));
assert_eq!(sketch1.max_item().cloned(), Some((n - 1) as f32));
- let median = sketch1.quantile(0.5, true).unwrap();
+ let median = sketch1.quantile(0.5, SearchCriteria::Inclusive).unwrap();
let rank_eps = rank_eps(&sketch1);
assert_approx_eq(median as f64, (n / 2) as f64, (n as f64 / 2.0) * rank_eps);
}
@@ -386,7 +441,13 @@ fn test_custom_comparator_roundtrip() {
assert_eq!(sketch.min_item().map(String::as_str), Some("1"));
assert_eq!(sketch.max_item().map(String::as_str), Some("10"));
- assert_eq!(sketch.quantile(0.5, true).as_deref(), Some("2"));
+ assert_eq!(
+ sketch
+ .quantile(0.5, SearchCriteria::Inclusive)
+ .as_deref()
+ .unwrap(),
+ "2"
+ );
let bytes = sketch.serialize();
let decoded = KllSketch::<String, NumericStringOrder>::deserialize_with_comparator(
@@ -397,5 +458,11 @@ fn test_custom_comparator_roundtrip() {
assert_eq!(decoded.n(), sketch.n());
assert_eq!(decoded.min_item().map(String::as_str), Some("1"));
assert_eq!(decoded.max_item().map(String::as_str), Some("10"));
- assert_eq!(decoded.quantile(0.5, true).as_deref(), Some("2"));
+ assert_eq!(
+ decoded
+ .quantile(0.5, SearchCriteria::Inclusive)
+ .as_deref()
+ .unwrap(),
+ "2"
+ );
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datasketches.apache.org
For additional commands, e-mail: commits-help@datasketches.apache.org